1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.recents.views; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorListenerAdapter; 24 import android.annotation.Nullable; 25 import android.app.AppGlobals; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.pm.ActivityInfo; 29 import android.content.res.Resources; 30 import android.graphics.Canvas; 31 import android.graphics.Color; 32 import android.graphics.ColorFilter; 33 import android.graphics.Paint; 34 import android.graphics.PixelFormat; 35 import android.graphics.Rect; 36 import android.graphics.drawable.Drawable; 37 import android.graphics.drawable.RippleDrawable; 38 import android.os.CountDownTimer; 39 import androidx.core.graphics.ColorUtils; 40 import android.util.AttributeSet; 41 import android.util.IconDrawableFactory; 42 import android.view.Gravity; 43 import android.view.View; 44 import android.view.ViewAnimationUtils; 45 import android.view.ViewDebug; 46 import android.view.ViewGroup; 47 import android.widget.FrameLayout; 48 import android.widget.ImageView; 49 import android.widget.ProgressBar; 50 import android.widget.TextView; 51 52 import com.android.internal.logging.MetricsLogger; 53 import com.android.systemui.Interpolators; 54 import com.android.systemui.R; 55 import com.android.systemui.recents.Constants; 56 import com.android.systemui.recents.LegacyRecentsImpl; 57 import com.android.systemui.recents.events.EventBus; 58 import com.android.systemui.recents.events.activity.LaunchTaskEvent; 59 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; 60 import com.android.systemui.recents.misc.SystemServicesProxy; 61 import com.android.systemui.shared.system.ActivityManagerWrapper; 62 import com.android.systemui.shared.system.PackageManagerWrapper; 63 import com.android.systemui.recents.utilities.Utilities; 64 import com.android.systemui.shared.recents.model.Task; 65 66 /* The task bar view */ 67 public class TaskViewHeader extends FrameLayout 68 implements View.OnClickListener, View.OnLongClickListener { 69 70 private static IconDrawableFactory sDrawableFactory; 71 72 private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.075f; 73 private static final float OVERLAY_LIGHTNESS_INCREMENT = -0.0625f; 74 private static final int OVERLAY_REVEAL_DURATION = 250; 75 private static final long FOCUS_INDICATOR_INTERVAL_MS = 30; 76 77 /** 78 * A color drawable that draws a slight highlight at the top to help it stand out. 79 */ 80 private class HighlightColorDrawable extends Drawable { 81 82 private Paint mHighlightPaint = new Paint(); 83 private Paint mBackgroundPaint = new Paint(); 84 private int mColor; 85 private float mDimAlpha; 86 HighlightColorDrawable()87 public HighlightColorDrawable() { 88 mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0)); 89 mBackgroundPaint.setAntiAlias(true); 90 mHighlightPaint.setColor(Color.argb(255, 255, 255, 255)); 91 mHighlightPaint.setAntiAlias(true); 92 } 93 setColorAndDim(int color, float dimAlpha)94 public void setColorAndDim(int color, float dimAlpha) { 95 if (mColor != color || Float.compare(mDimAlpha, dimAlpha) != 0) { 96 mColor = color; 97 mDimAlpha = dimAlpha; 98 if (mShouldDarkenBackgroundColor) { 99 color = getSecondaryColor(color, false /* useLightOverlayColor */); 100 } 101 mBackgroundPaint.setColor(color); 102 103 ColorUtils.colorToHSL(color, mTmpHSL); 104 // TODO: Consider using the saturation of the color to adjust the lightness as well 105 mTmpHSL[2] = Math.min(1f, 106 mTmpHSL[2] + HIGHLIGHT_LIGHTNESS_INCREMENT * (1.0f - dimAlpha)); 107 mHighlightPaint.setColor(ColorUtils.HSLToColor(mTmpHSL)); 108 109 invalidateSelf(); 110 } 111 } 112 113 @Override setColorFilter(@ullable ColorFilter colorFilter)114 public void setColorFilter(@Nullable ColorFilter colorFilter) { 115 // Do nothing 116 } 117 118 @Override setAlpha(int alpha)119 public void setAlpha(int alpha) { 120 // Do nothing 121 } 122 123 @Override draw(Canvas canvas)124 public void draw(Canvas canvas) { 125 // Draw the highlight at the top edge (but put the bottom edge just out of view) 126 canvas.drawRoundRect(0, 0, mTaskViewRect.width(), 127 2 * Math.max(mHighlightHeight, mCornerRadius), 128 mCornerRadius, mCornerRadius, mHighlightPaint); 129 130 // Draw the background with the rounded corners 131 canvas.drawRoundRect(0, mHighlightHeight, mTaskViewRect.width(), 132 getHeight() + mCornerRadius, 133 mCornerRadius, mCornerRadius, mBackgroundPaint); 134 } 135 136 @Override getOpacity()137 public int getOpacity() { 138 return PixelFormat.OPAQUE; 139 } 140 getColor()141 public int getColor() { 142 return mColor; 143 } 144 } 145 146 Task mTask; 147 148 // Header views 149 ImageView mIconView; 150 TextView mTitleView; 151 ImageView mMoveTaskButton; 152 ImageView mDismissButton; 153 FrameLayout mAppOverlayView; 154 ImageView mAppIconView; 155 ImageView mAppInfoView; 156 TextView mAppTitleView; 157 ProgressBar mFocusTimerIndicator; 158 159 // Header drawables 160 @ViewDebug.ExportedProperty(category="recents") 161 Rect mTaskViewRect = new Rect(); 162 int mHeaderBarHeight; 163 int mHeaderButtonPadding; 164 int mCornerRadius; 165 int mHighlightHeight; 166 @ViewDebug.ExportedProperty(category="recents") 167 float mDimAlpha; 168 Drawable mLightDismissDrawable; 169 Drawable mDarkDismissDrawable; 170 Drawable mLightFullscreenIcon; 171 Drawable mDarkFullscreenIcon; 172 Drawable mLightInfoIcon; 173 Drawable mDarkInfoIcon; 174 int mTaskBarViewLightTextColor; 175 int mTaskBarViewDarkTextColor; 176 int mDisabledTaskBarBackgroundColor; 177 String mDismissDescFormat; 178 String mAppInfoDescFormat; 179 int mTaskWindowingMode = WINDOWING_MODE_UNDEFINED; 180 181 // Header background 182 private HighlightColorDrawable mBackground; 183 private HighlightColorDrawable mOverlayBackground; 184 private float[] mTmpHSL = new float[3]; 185 186 // Header dim, which is only used when task view hardware layers are not used 187 private Paint mDimLayerPaint = new Paint(); 188 189 // Whether the background color should be darkened to differentiate from the primary color. 190 // Used in grid layout. 191 private boolean mShouldDarkenBackgroundColor = false; 192 193 private CountDownTimer mFocusTimerCountDown; 194 TaskViewHeader(Context context)195 public TaskViewHeader(Context context) { 196 this(context, null); 197 } 198 TaskViewHeader(Context context, AttributeSet attrs)199 public TaskViewHeader(Context context, AttributeSet attrs) { 200 this(context, attrs, 0); 201 } 202 TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr)203 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) { 204 this(context, attrs, defStyleAttr, 0); 205 } 206 TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)207 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 208 super(context, attrs, defStyleAttr, defStyleRes); 209 setWillNotDraw(false); 210 211 // Load the dismiss resources 212 Resources res = context.getResources(); 213 mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light); 214 mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark); 215 mCornerRadius = LegacyRecentsImpl.getConfiguration().isGridEnabled ? 216 res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) : 217 res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius); 218 mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight); 219 mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color); 220 mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color); 221 mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light); 222 mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark); 223 mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light); 224 mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark); 225 mDisabledTaskBarBackgroundColor = 226 context.getColor(R.color.recents_task_bar_disabled_background_color); 227 mDismissDescFormat = mContext.getString( 228 R.string.accessibility_recents_item_will_be_dismissed); 229 mAppInfoDescFormat = mContext.getString(R.string.accessibility_recents_item_open_app_info); 230 231 // Configure the background and dim 232 mBackground = new HighlightColorDrawable(); 233 mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f); 234 setBackground(mBackground); 235 mOverlayBackground = new HighlightColorDrawable(); 236 mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0)); 237 mDimLayerPaint.setAntiAlias(true); 238 } 239 240 /** 241 * Resets this header along with the TaskView. 242 */ reset()243 public void reset() { 244 hideAppOverlay(true /* immediate */); 245 } 246 247 @Override onFinishInflate()248 protected void onFinishInflate() { 249 SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices(); 250 251 // Initialize the icon and description views 252 mIconView = findViewById(R.id.icon); 253 mIconView.setOnLongClickListener(this); 254 mTitleView = findViewById(R.id.title); 255 mDismissButton = findViewById(R.id.dismiss_task); 256 257 onConfigurationChanged(); 258 } 259 260 /** 261 * Programmatically sets the layout params for a header bar layout. This is necessary because 262 * we can't get resources based on the current configuration, but instead need to get them 263 * based on the device configuration. 264 */ updateLayoutParams(View icon, View title, View secondaryButton, View button)265 private void updateLayoutParams(View icon, View title, View secondaryButton, View button) { 266 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( 267 ViewGroup.LayoutParams.MATCH_PARENT, mHeaderBarHeight, Gravity.TOP); 268 setLayoutParams(lp); 269 lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.START); 270 icon.setLayoutParams(lp); 271 lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 272 ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL); 273 lp.setMarginStart(mHeaderBarHeight); 274 lp.setMarginEnd(mMoveTaskButton != null 275 ? 2 * mHeaderBarHeight 276 : mHeaderBarHeight); 277 title.setLayoutParams(lp); 278 if (secondaryButton != null) { 279 lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END); 280 lp.setMarginEnd(mHeaderBarHeight); 281 secondaryButton.setLayoutParams(lp); 282 secondaryButton.setPadding(mHeaderButtonPadding, mHeaderButtonPadding, 283 mHeaderButtonPadding, mHeaderButtonPadding); 284 } 285 lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END); 286 button.setLayoutParams(lp); 287 button.setPadding(mHeaderButtonPadding, mHeaderButtonPadding, mHeaderButtonPadding, 288 mHeaderButtonPadding); 289 } 290 291 /** 292 * Update the header view when the configuration changes. 293 */ onConfigurationChanged()294 public void onConfigurationChanged() { 295 // Update the dimensions of everything in the header. We do this because we need to use 296 // resources for the display, and not the current configuration. 297 Resources res = getResources(); 298 int headerBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(), 299 R.dimen.recents_task_view_header_height, 300 R.dimen.recents_task_view_header_height, 301 R.dimen.recents_task_view_header_height, 302 R.dimen.recents_task_view_header_height_tablet_land, 303 R.dimen.recents_task_view_header_height, 304 R.dimen.recents_task_view_header_height_tablet_land, 305 R.dimen.recents_grid_task_view_header_height); 306 int headerButtonPadding = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(), 307 R.dimen.recents_task_view_header_button_padding, 308 R.dimen.recents_task_view_header_button_padding, 309 R.dimen.recents_task_view_header_button_padding, 310 R.dimen.recents_task_view_header_button_padding_tablet_land, 311 R.dimen.recents_task_view_header_button_padding, 312 R.dimen.recents_task_view_header_button_padding_tablet_land, 313 R.dimen.recents_grid_task_view_header_button_padding); 314 if (headerBarHeight != mHeaderBarHeight || headerButtonPadding != mHeaderButtonPadding) { 315 mHeaderBarHeight = headerBarHeight; 316 mHeaderButtonPadding = headerButtonPadding; 317 updateLayoutParams(mIconView, mTitleView, mMoveTaskButton, mDismissButton); 318 if (mAppOverlayView != null) { 319 updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView); 320 } 321 } 322 } 323 324 @Override onLayout(boolean changed, int left, int top, int right, int bottom)325 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 326 super.onLayout(changed, left, top, right, bottom); 327 328 // Since we update the position of children based on the width of the parent and this view 329 // recompute these changes with the new view size 330 onTaskViewSizeChanged(mTaskViewRect.width(), mTaskViewRect.height()); 331 } 332 333 /** 334 * Called when the task view frame changes, allowing us to move the contents of the header 335 * to match the frame changes. 336 */ onTaskViewSizeChanged(int width, int height)337 public void onTaskViewSizeChanged(int width, int height) { 338 mTaskViewRect.set(0, 0, width, height); 339 340 boolean showTitle = true; 341 boolean showMoveIcon = true; 342 boolean showDismissIcon = true; 343 int rightInset = width - getMeasuredWidth(); 344 345 mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE); 346 if (mMoveTaskButton != null) { 347 mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE); 348 mMoveTaskButton.setTranslationX(rightInset); 349 } 350 mDismissButton.setVisibility(showDismissIcon ? View.VISIBLE : View.INVISIBLE); 351 mDismissButton.setTranslationX(rightInset); 352 353 setLeftTopRightBottom(0, 0, width, getMeasuredHeight()); 354 } 355 356 @Override onDrawForeground(Canvas canvas)357 public void onDrawForeground(Canvas canvas) { 358 super.onDrawForeground(canvas); 359 360 // Draw the dim layer with the rounded corners 361 canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius, 362 mCornerRadius, mCornerRadius, mDimLayerPaint); 363 } 364 365 /** Starts the focus timer. */ startFocusTimerIndicator(int duration)366 public void startFocusTimerIndicator(int duration) { 367 if (mFocusTimerIndicator == null) { 368 return; 369 } 370 371 mFocusTimerIndicator.setVisibility(View.VISIBLE); 372 mFocusTimerIndicator.setMax(duration); 373 mFocusTimerIndicator.setProgress(duration); 374 if (mFocusTimerCountDown != null) { 375 mFocusTimerCountDown.cancel(); 376 } 377 mFocusTimerCountDown = new CountDownTimer(duration, 378 FOCUS_INDICATOR_INTERVAL_MS) { 379 public void onTick(long millisUntilFinished) { 380 mFocusTimerIndicator.setProgress((int) millisUntilFinished); 381 } 382 383 public void onFinish() { 384 // Do nothing 385 } 386 }.start(); 387 } 388 389 /** Cancels the focus timer. */ cancelFocusTimerIndicator()390 public void cancelFocusTimerIndicator() { 391 if (mFocusTimerIndicator == null) { 392 return; 393 } 394 395 if (mFocusTimerCountDown != null) { 396 mFocusTimerCountDown.cancel(); 397 mFocusTimerIndicator.setProgress(0); 398 mFocusTimerIndicator.setVisibility(View.INVISIBLE); 399 } 400 } 401 402 /** Only exposed for the workaround for b/27815919. */ getIconView()403 public ImageView getIconView() { 404 return mIconView; 405 } 406 407 /** Returns the secondary color for a primary color. */ getSecondaryColor(int primaryColor, boolean useLightOverlayColor)408 int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) { 409 int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK; 410 return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f); 411 } 412 413 /** 414 * Sets the dim alpha, only used when we are not using hardware layers. 415 * (see RecentsConfiguration.useHardwareLayers) 416 */ setDimAlpha(float dimAlpha)417 public void setDimAlpha(float dimAlpha) { 418 if (Float.compare(mDimAlpha, dimAlpha) != 0) { 419 mDimAlpha = dimAlpha; 420 mTitleView.setAlpha(1f - dimAlpha); 421 updateBackgroundColor(mBackground.getColor(), dimAlpha); 422 } 423 } 424 425 /** 426 * Updates the background and highlight colors for this header. 427 */ updateBackgroundColor(int color, float dimAlpha)428 private void updateBackgroundColor(int color, float dimAlpha) { 429 if (mTask != null) { 430 mBackground.setColorAndDim(color, dimAlpha); 431 // TODO: Consider using the saturation of the color to adjust the lightness as well 432 ColorUtils.colorToHSL(color, mTmpHSL); 433 mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha)); 434 mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha); 435 mDimLayerPaint.setAlpha((int) (dimAlpha * 255)); 436 invalidate(); 437 } 438 } 439 440 /** 441 * Sets whether the background color should be darkened to differentiate from the primary color. 442 */ setShouldDarkenBackgroundColor(boolean flag)443 public void setShouldDarkenBackgroundColor(boolean flag) { 444 mShouldDarkenBackgroundColor = flag; 445 } 446 447 /** 448 * Binds the bar view to the task. 449 */ bindToTask(Task t, boolean touchExplorationEnabled, boolean disabledInSafeMode)450 public void bindToTask(Task t, boolean touchExplorationEnabled, boolean disabledInSafeMode) { 451 mTask = t; 452 453 int primaryColor = disabledInSafeMode 454 ? mDisabledTaskBarBackgroundColor 455 : t.colorPrimary; 456 if (mBackground.getColor() != primaryColor) { 457 updateBackgroundColor(primaryColor, mDimAlpha); 458 } 459 if (!mTitleView.getText().toString().equals(t.title)) { 460 mTitleView.setText(t.title); 461 } 462 mTitleView.setContentDescription(t.titleDescription); 463 mTitleView.setTextColor(t.useLightOnPrimaryColor ? 464 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor); 465 mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? 466 mLightDismissDrawable : mDarkDismissDrawable); 467 mDismissButton.setContentDescription(String.format(mDismissDescFormat, t.titleDescription)); 468 mDismissButton.setOnClickListener(this); 469 mDismissButton.setClickable(false); 470 ((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true); 471 472 // In accessibility, a single click on the focused app info button will show it 473 if (touchExplorationEnabled) { 474 mIconView.setContentDescription(String.format(mAppInfoDescFormat, t.titleDescription)); 475 mIconView.setOnClickListener(this); 476 mIconView.setClickable(true); 477 } 478 } 479 480 /** 481 * Called when the bound task's data has loaded and this view should update to reflect the 482 * changes. 483 */ onTaskDataLoaded()484 public void onTaskDataLoaded() { 485 if (mTask != null && mTask.icon != null) { 486 mIconView.setImageDrawable(mTask.icon); 487 } 488 } 489 490 /** Unbinds the bar view from the task */ unbindFromTask(boolean touchExplorationEnabled)491 void unbindFromTask(boolean touchExplorationEnabled) { 492 mTask = null; 493 mIconView.setImageDrawable(null); 494 if (touchExplorationEnabled) { 495 mIconView.setClickable(false); 496 } 497 } 498 499 /** Animates this task bar if the user does not interact with the stack after a certain time. */ startNoUserInteractionAnimation()500 void startNoUserInteractionAnimation() { 501 int duration = getResources().getInteger(R.integer.recents_task_enter_from_app_duration); 502 mDismissButton.setVisibility(View.VISIBLE); 503 mDismissButton.setClickable(true); 504 if (mDismissButton.getVisibility() == VISIBLE) { 505 mDismissButton.animate() 506 .alpha(1f) 507 .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN) 508 .setDuration(duration) 509 .start(); 510 } else { 511 mDismissButton.setAlpha(1f); 512 } 513 if (mMoveTaskButton != null) { 514 if (mMoveTaskButton.getVisibility() == VISIBLE) { 515 mMoveTaskButton.setVisibility(View.VISIBLE); 516 mMoveTaskButton.setClickable(true); 517 mMoveTaskButton.animate() 518 .alpha(1f) 519 .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN) 520 .setDuration(duration) 521 .start(); 522 } else { 523 mMoveTaskButton.setAlpha(1f); 524 } 525 } 526 } 527 528 /** 529 * Mark this task view that the user does has not interacted with the stack after a certain 530 * time. 531 */ setNoUserInteractionState()532 public void setNoUserInteractionState() { 533 mDismissButton.setVisibility(View.VISIBLE); 534 mDismissButton.animate().cancel(); 535 mDismissButton.setAlpha(1f); 536 mDismissButton.setClickable(true); 537 if (mMoveTaskButton != null) { 538 mMoveTaskButton.setVisibility(View.VISIBLE); 539 mMoveTaskButton.animate().cancel(); 540 mMoveTaskButton.setAlpha(1f); 541 mMoveTaskButton.setClickable(true); 542 } 543 } 544 545 /** 546 * Resets the state tracking that the user has not interacted with the stack after a certain 547 * time. 548 */ resetNoUserInteractionState()549 void resetNoUserInteractionState() { 550 mDismissButton.setVisibility(View.INVISIBLE); 551 mDismissButton.setAlpha(0f); 552 mDismissButton.setClickable(false); 553 if (mMoveTaskButton != null) { 554 mMoveTaskButton.setVisibility(View.INVISIBLE); 555 mMoveTaskButton.setAlpha(0f); 556 mMoveTaskButton.setClickable(false); 557 } 558 } 559 560 @Override onCreateDrawableState(int extraSpace)561 protected int[] onCreateDrawableState(int extraSpace) { 562 563 // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged. 564 // This is to prevent layer trashing when the view is pressed. 565 return new int[] {}; 566 } 567 568 @Override onClick(View v)569 public void onClick(View v) { 570 if (v == mIconView) { 571 // In accessibility, a single click on the focused app info button will show it 572 EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); 573 } else if (v == mDismissButton) { 574 TaskView tv = Utilities.findParent(this, TaskView.class); 575 tv.dismissTask(); 576 577 // Keep track of deletions by the dismiss button 578 MetricsLogger.histogram(getContext(), "overview_task_dismissed_source", 579 Constants.Metrics.DismissSourceHeaderButton); 580 } else if (v == mMoveTaskButton) { 581 TaskView tv = Utilities.findParent(this, TaskView.class); 582 EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null, false, 583 mTaskWindowingMode, ACTIVITY_TYPE_UNDEFINED)); 584 } else if (v == mAppInfoView) { 585 EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); 586 } else if (v == mAppIconView) { 587 hideAppOverlay(false /* immediate */); 588 } 589 } 590 591 @Override onLongClick(View v)592 public boolean onLongClick(View v) { 593 if (v == mIconView) { 594 showAppOverlay(); 595 return true; 596 } else if (v == mAppIconView) { 597 hideAppOverlay(false /* immediate */); 598 return true; 599 } 600 return false; 601 } 602 603 /** 604 * Shows the application overlay. 605 */ showAppOverlay()606 private void showAppOverlay() { 607 // Skip early if the task is invalid 608 SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices(); 609 ComponentName cn = mTask.key.getComponent(); 610 int userId = mTask.key.userId; 611 ActivityInfo activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, userId); 612 if (activityInfo == null) { 613 return; 614 } 615 616 // Inflate the overlay if necessary 617 if (mAppOverlayView == null) { 618 mAppOverlayView = (FrameLayout) Utilities.findViewStubById(this, 619 R.id.app_overlay_stub).inflate(); 620 mAppOverlayView.setBackground(mOverlayBackground); 621 mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon); 622 mAppIconView.setOnClickListener(this); 623 mAppIconView.setOnLongClickListener(this); 624 mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info); 625 mAppInfoView.setOnClickListener(this); 626 mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title); 627 updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView); 628 } 629 630 // Update the overlay contents for the current app 631 mAppTitleView.setText(ActivityManagerWrapper.getInstance().getBadgedApplicationLabel( 632 activityInfo.applicationInfo, userId)); 633 mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ? 634 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor); 635 mAppIconView.setImageDrawable(getIconDrawableFactory().getBadgedIcon( 636 activityInfo.applicationInfo, userId)); 637 mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor 638 ? mLightInfoIcon 639 : mDarkInfoIcon); 640 mAppOverlayView.setVisibility(View.VISIBLE); 641 642 int x = mIconView.getLeft() + mIconView.getWidth() / 2; 643 int y = mIconView.getTop() + mIconView.getHeight() / 2; 644 Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0, 645 getWidth()); 646 revealAnim.setDuration(OVERLAY_REVEAL_DURATION); 647 revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 648 revealAnim.start(); 649 } 650 651 /** 652 * Hide the application overlay. 653 */ hideAppOverlay(boolean immediate)654 private void hideAppOverlay(boolean immediate) { 655 // Skip if we haven't even loaded the overlay yet 656 if (mAppOverlayView == null) { 657 return; 658 } 659 660 if (immediate) { 661 mAppOverlayView.setVisibility(View.GONE); 662 } else { 663 int x = mIconView.getLeft() + mIconView.getWidth() / 2; 664 int y = mIconView.getTop() + mIconView.getHeight() / 2; 665 Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 666 getWidth(), 0); 667 revealAnim.setDuration(OVERLAY_REVEAL_DURATION); 668 revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 669 revealAnim.addListener(new AnimatorListenerAdapter() { 670 @Override 671 public void onAnimationEnd(Animator animation) { 672 mAppOverlayView.setVisibility(View.GONE); 673 } 674 }); 675 revealAnim.start(); 676 } 677 } 678 getIconDrawableFactory()679 private static IconDrawableFactory getIconDrawableFactory() { 680 if (sDrawableFactory == null) { 681 sDrawableFactory = IconDrawableFactory.newInstance(AppGlobals.getInitialApplication()); 682 } 683 return sDrawableFactory; 684 } 685 } 686