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