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