TaskViewHeader.java revision 670ea71f1b4ae59a0cd6608ae44b0fb78a1144c4
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 17package com.android.systemui.recents.views; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.annotation.Nullable; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.pm.ActivityInfo; 25import android.content.res.Resources; 26import android.graphics.Canvas; 27import android.graphics.Color; 28import android.graphics.ColorFilter; 29import android.graphics.Paint; 30import android.graphics.PixelFormat; 31import android.graphics.PorterDuff; 32import android.graphics.Rect; 33import android.graphics.drawable.Drawable; 34import android.os.CountDownTimer; 35import android.support.v4.graphics.ColorUtils; 36import android.util.AttributeSet; 37import android.view.Gravity; 38import android.view.View; 39import android.view.ViewAnimationUtils; 40import android.view.ViewDebug; 41import android.view.ViewGroup; 42import android.widget.FrameLayout; 43import android.widget.ImageView; 44import android.widget.ProgressBar; 45import android.widget.TextView; 46 47import com.android.internal.logging.MetricsLogger; 48import com.android.systemui.Interpolators; 49import com.android.systemui.R; 50import com.android.systemui.recents.Constants; 51import com.android.systemui.recents.Recents; 52import com.android.systemui.recents.events.EventBus; 53import com.android.systemui.recents.events.activity.LaunchTaskEvent; 54import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; 55import com.android.systemui.recents.misc.SystemServicesProxy; 56import com.android.systemui.recents.misc.Utilities; 57import com.android.systemui.recents.model.Task; 58 59import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; 60import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; 61import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 62 63/* The task bar view */ 64public class TaskViewHeader extends FrameLayout 65 implements View.OnClickListener, View.OnLongClickListener { 66 67 private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.075f; 68 private static final float OVERLAY_LIGHTNESS_INCREMENT = -0.0625f; 69 private static final int OVERLAY_REVEAL_DURATION = 250; 70 private static final long FOCUS_INDICATOR_INTERVAL_MS = 30; 71 72 /** 73 * A color drawable that draws a slight highlight at the top to help it stand out. 74 */ 75 private class HighlightColorDrawable extends Drawable { 76 77 private Paint mHighlightPaint = new Paint(); 78 private Paint mBackgroundPaint = new Paint(); 79 private int mColor; 80 private float mDimAlpha; 81 82 public HighlightColorDrawable() { 83 mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0)); 84 mBackgroundPaint.setAntiAlias(true); 85 mHighlightPaint.setColor(Color.argb(255, 255, 255, 255)); 86 mHighlightPaint.setAntiAlias(true); 87 } 88 89 public void setColorAndDim(int color, float dimAlpha) { 90 if (mColor != color || Float.compare(mDimAlpha, dimAlpha) != 0) { 91 mColor = color; 92 mDimAlpha = dimAlpha; 93 mBackgroundPaint.setColor(color); 94 95 ColorUtils.colorToHSL(color, mTmpHSL); 96 // TODO: Consider using the saturation of the color to adjust the lightness as well 97 mTmpHSL[2] = Math.min(1f, 98 mTmpHSL[2] + HIGHLIGHT_LIGHTNESS_INCREMENT * (1.0f - dimAlpha)); 99 mHighlightPaint.setColor(ColorUtils.HSLToColor(mTmpHSL)); 100 101 invalidateSelf(); 102 } 103 } 104 105 @Override 106 public void setColorFilter(@Nullable ColorFilter colorFilter) { 107 // Do nothing 108 } 109 110 @Override 111 public void setAlpha(int alpha) { 112 // Do nothing 113 } 114 115 @Override 116 public void draw(Canvas canvas) { 117 // Draw the highlight at the top edge (but put the bottom edge just out of view) 118 canvas.drawRoundRect(0, 0, mTaskViewRect.width(), 119 2 * Math.max(mHighlightHeight, mCornerRadius), 120 mCornerRadius, mCornerRadius, mHighlightPaint); 121 122 // Draw the background with the rounded corners 123 canvas.drawRoundRect(0, mHighlightHeight, mTaskViewRect.width(), 124 getHeight() + mCornerRadius, 125 mCornerRadius, mCornerRadius, mBackgroundPaint); 126 } 127 128 @Override 129 public int getOpacity() { 130 return PixelFormat.OPAQUE; 131 } 132 133 public int getColor() { 134 return mColor; 135 } 136 } 137 138 Task mTask; 139 140 // Header views 141 ImageView mIconView; 142 TextView mTitleView; 143 ImageView mMoveTaskButton; 144 ImageView mDismissButton; 145 FrameLayout mAppOverlayView; 146 ImageView mAppIconView; 147 ImageView mAppInfoView; 148 TextView mAppTitleView; 149 ProgressBar mFocusTimerIndicator; 150 151 // Header drawables 152 @ViewDebug.ExportedProperty(category="recents") 153 Rect mTaskViewRect = new Rect(); 154 int mHeaderBarHeight; 155 int mHeaderButtonPadding; 156 int mCornerRadius; 157 int mHighlightHeight; 158 @ViewDebug.ExportedProperty(category="recents") 159 float mDimAlpha; 160 Drawable mLightDismissDrawable; 161 Drawable mDarkDismissDrawable; 162 Drawable mLightFreeformIcon; 163 Drawable mDarkFreeformIcon; 164 Drawable mLightFullscreenIcon; 165 Drawable mDarkFullscreenIcon; 166 Drawable mLightInfoIcon; 167 Drawable mDarkInfoIcon; 168 int mTaskBarViewLightTextColor; 169 int mTaskBarViewDarkTextColor; 170 int mDisabledTaskBarBackgroundColor; 171 int mMoveTaskTargetStackId = INVALID_STACK_ID; 172 173 // Header background 174 private HighlightColorDrawable mBackground; 175 private HighlightColorDrawable mOverlayBackground; 176 private float[] mTmpHSL = new float[3]; 177 178 // Header dim, which is only used when task view hardware layers are not used 179 private Paint mDimLayerPaint = new Paint(); 180 181 private CountDownTimer mFocusTimerCountDown; 182 183 public TaskViewHeader(Context context) { 184 this(context, null); 185 } 186 187 public TaskViewHeader(Context context, AttributeSet attrs) { 188 this(context, attrs, 0); 189 } 190 191 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) { 192 this(context, attrs, defStyleAttr, 0); 193 } 194 195 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 196 super(context, attrs, defStyleAttr, defStyleRes); 197 setWillNotDraw(false); 198 199 // Load the dismiss resources 200 Resources res = context.getResources(); 201 mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light); 202 mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark); 203 mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius); 204 mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight); 205 mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color); 206 mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color); 207 mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light); 208 mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark); 209 mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light); 210 mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark); 211 mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light); 212 mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark); 213 mDisabledTaskBarBackgroundColor = 214 context.getColor(R.color.recents_task_bar_disabled_background_color); 215 216 // Configure the background and dim 217 mBackground = new HighlightColorDrawable(); 218 mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f); 219 setBackground(mBackground); 220 mOverlayBackground = new HighlightColorDrawable(); 221 mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0)); 222 mDimLayerPaint.setAntiAlias(true); 223 } 224 225 /** 226 * Resets this header along with the TaskView. 227 */ 228 public void reset() { 229 hideAppOverlay(true /* immediate */); 230 } 231 232 @Override 233 protected void onFinishInflate() { 234 SystemServicesProxy ssp = Recents.getSystemServices(); 235 236 // Initialize the icon and description views 237 mIconView = (ImageView) findViewById(R.id.icon); 238 mIconView.setClickable(false); 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 */ 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 */ 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(res, 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(res, 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 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 */ 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 355 @Override 356 public void onDrawForeground(Canvas canvas) { 357 super.onDrawForeground(canvas); 358 359 // Draw the dim layer with the rounded corners 360 canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius, 361 mCornerRadius, mCornerRadius, mDimLayerPaint); 362 } 363 364 /** Starts the focus timer. */ 365 public void startFocusTimerIndicator(int duration) { 366 if (mFocusTimerIndicator == null) { 367 return; 368 } 369 370 mFocusTimerIndicator.setVisibility(View.VISIBLE); 371 mFocusTimerIndicator.setMax(duration); 372 mFocusTimerIndicator.setProgress(duration); 373 if (mFocusTimerCountDown != null) { 374 mFocusTimerCountDown.cancel(); 375 } 376 mFocusTimerCountDown = new CountDownTimer(duration, 377 FOCUS_INDICATOR_INTERVAL_MS) { 378 public void onTick(long millisUntilFinished) { 379 mFocusTimerIndicator.setProgress((int) millisUntilFinished); 380 } 381 382 public void onFinish() { 383 // Do nothing 384 } 385 }.start(); 386 } 387 388 /** Cancels the focus timer. */ 389 public void cancelFocusTimerIndicator() { 390 if (mFocusTimerIndicator == null) { 391 return; 392 } 393 394 if (mFocusTimerCountDown != null) { 395 mFocusTimerCountDown.cancel(); 396 mFocusTimerIndicator.setProgress(0); 397 mFocusTimerIndicator.setVisibility(View.INVISIBLE); 398 } 399 } 400 401 /** Only exposed for the workaround for b/27815919. */ 402 public ImageView getIconView() { 403 return mIconView; 404 } 405 406 /** Returns the secondary color for a primary color. */ 407 int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) { 408 int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK; 409 return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f); 410 } 411 412 /** 413 * Sets the dim alpha, only used when we are not using hardware layers. 414 * (see RecentsConfiguration.useHardwareLayers) 415 */ 416 public void setDimAlpha(float dimAlpha) { 417 if (Float.compare(mDimAlpha, dimAlpha) != 0) { 418 mDimAlpha = dimAlpha; 419 mTitleView.setAlpha(1f - dimAlpha); 420 updateBackgroundColor(mBackground.getColor(), dimAlpha); 421 } 422 } 423 424 /** 425 * Updates the background and highlight colors for this header. 426 */ 427 private void updateBackgroundColor(int color, float dimAlpha) { 428 if (mTask != null) { 429 mBackground.setColorAndDim(color, dimAlpha); 430 // TODO: Consider using the saturation of the color to adjust the lightness as well 431 ColorUtils.colorToHSL(color, mTmpHSL); 432 mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha)); 433 mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha); 434 mDimLayerPaint.setAlpha((int) (dimAlpha * 255)); 435 invalidate(); 436 } 437 } 438 439 /** Binds the bar view to the task */ 440 public void rebindToTask(Task t, boolean touchExplorationEnabled, boolean disabledInSafeMode) { 441 mTask = t; 442 443 // If an activity icon is defined, then we use that as the primary icon to show in the bar, 444 // otherwise, we fall back to the application icon 445 int primaryColor = disabledInSafeMode 446 ? mDisabledTaskBarBackgroundColor 447 : t.colorPrimary; 448 if (mBackground.getColor() != primaryColor) { 449 updateBackgroundColor(primaryColor, mDimAlpha); 450 } 451 if (t.icon != null) { 452 mIconView.setImageDrawable(t.icon); 453 } 454 if (!mTitleView.getText().toString().equals(t.title)) { 455 mTitleView.setText(t.title); 456 } 457 mTitleView.setContentDescription(t.titleDescription); 458 mTitleView.setTextColor(t.useLightOnPrimaryColor ? 459 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor); 460 mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? 461 mLightDismissDrawable : mDarkDismissDrawable); 462 mDismissButton.setContentDescription(t.dismissDescription); 463 464 // When freeform workspaces are enabled, then update the move-task button depending on the 465 // current task 466 if (mMoveTaskButton != null) { 467 if (t.isFreeformTask()) { 468 mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID; 469 mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor 470 ? mLightFullscreenIcon 471 : mDarkFullscreenIcon); 472 } else { 473 mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID; 474 mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor 475 ? mLightFreeformIcon 476 : mDarkFreeformIcon); 477 } 478 } 479 480 if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) { 481 if (mFocusTimerIndicator == null) { 482 mFocusTimerIndicator = (ProgressBar) Utilities.findViewStubById(this, 483 R.id.focus_timer_indicator_stub).inflate(); 484 } 485 mFocusTimerIndicator.getProgressDrawable() 486 .setColorFilter( 487 getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor), 488 PorterDuff.Mode.SRC_IN); 489 } 490 491 // In accessibility, a single click on the focused app info button will show it 492 if (touchExplorationEnabled) { 493 mIconView.setContentDescription(t.appInfoDescription); 494 mIconView.setOnClickListener(this); 495 } 496 } 497 498 /** Unbinds the bar view from the task */ 499 void unbindFromTask(boolean touchExplorationEnabled) { 500 mTask = null; 501 mIconView.setImageDrawable(null); 502 if (touchExplorationEnabled) { 503 mIconView.setOnClickListener(null); 504 } 505 } 506 507 /** Animates this task bar if the user does not interact with the stack after a certain time. */ 508 void startNoUserInteractionAnimation() { 509 int duration = getResources().getInteger(R.integer.recents_task_enter_from_app_duration); 510 mDismissButton.setOnClickListener(this); 511 mDismissButton.setVisibility(View.VISIBLE); 512 if (mDismissButton.getVisibility() == VISIBLE) { 513 mDismissButton.animate() 514 .alpha(1f) 515 .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN) 516 .setDuration(duration) 517 .start(); 518 } else { 519 mDismissButton.setAlpha(1f); 520 } 521 if (mMoveTaskButton != null) { 522 if (mMoveTaskButton.getVisibility() == VISIBLE) { 523 mMoveTaskButton.setOnClickListener(this); 524 mMoveTaskButton.setVisibility(View.VISIBLE); 525 mMoveTaskButton.animate() 526 .alpha(1f) 527 .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN) 528 .setDuration(duration) 529 .start(); 530 } else { 531 mMoveTaskButton.setAlpha(1f); 532 } 533 } 534 } 535 536 /** 537 * Mark this task view that the user does has not interacted with the stack after a certain 538 * time. 539 */ 540 void setNoUserInteractionState() { 541 mDismissButton.setVisibility(View.VISIBLE); 542 mDismissButton.animate().cancel(); 543 mDismissButton.setAlpha(1f); 544 mDismissButton.setOnClickListener(this); 545 if (mMoveTaskButton != null) { 546 mMoveTaskButton.setVisibility(View.VISIBLE); 547 mMoveTaskButton.animate().cancel(); 548 mMoveTaskButton.setAlpha(1f); 549 mMoveTaskButton.setOnClickListener(this); 550 } 551 } 552 553 /** 554 * Resets the state tracking that the user has not interacted with the stack after a certain 555 * time. 556 */ 557 void resetNoUserInteractionState() { 558 mDismissButton.setVisibility(View.INVISIBLE); 559 mDismissButton.setAlpha(0f); 560 mDismissButton.setOnClickListener(null); 561 if (mMoveTaskButton != null) { 562 mMoveTaskButton.setVisibility(View.INVISIBLE); 563 mMoveTaskButton.setAlpha(0f); 564 mMoveTaskButton.setOnClickListener(null); 565 } 566 } 567 568 @Override 569 protected int[] onCreateDrawableState(int extraSpace) { 570 571 // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged. 572 // This is to prevent layer trashing when the view is pressed. 573 return new int[] {}; 574 } 575 576 @Override 577 public void onClick(View v) { 578 if (v == mIconView) { 579 // In accessibility, a single click on the focused app info button will show it 580 EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); 581 } else if (v == mDismissButton) { 582 TaskView tv = Utilities.findParent(this, TaskView.class); 583 tv.dismissTask(); 584 585 // Keep track of deletions by the dismiss button 586 MetricsLogger.histogram(getContext(), "overview_task_dismissed_source", 587 Constants.Metrics.DismissSourceHeaderButton); 588 } else if (v == mMoveTaskButton) { 589 TaskView tv = Utilities.findParent(this, TaskView.class); 590 Rect bounds = mMoveTaskTargetStackId == FREEFORM_WORKSPACE_STACK_ID 591 ? new Rect(mTaskViewRect) 592 : new Rect(); 593 EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, bounds, 594 mMoveTaskTargetStackId, false)); 595 } else if (v == mAppInfoView) { 596 EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); 597 } else if (v == mAppIconView) { 598 hideAppOverlay(false /* immediate */); 599 } 600 } 601 602 @Override 603 public boolean onLongClick(View v) { 604 if (v == mIconView) { 605 showAppOverlay(); 606 return true; 607 } else if (v == mAppIconView) { 608 hideAppOverlay(false /* immediate */); 609 return true; 610 } 611 return false; 612 } 613 614 /** 615 * Shows the application overlay. 616 */ 617 private void showAppOverlay() { 618 // Skip early if the task is invalid 619 SystemServicesProxy ssp = Recents.getSystemServices(); 620 ComponentName cn = mTask.key.getComponent(); 621 int userId = mTask.key.userId; 622 ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId); 623 if (activityInfo == null) { 624 return; 625 } 626 627 // Inflate the overlay if necessary 628 if (mAppOverlayView == null) { 629 mAppOverlayView = (FrameLayout) Utilities.findViewStubById(this, 630 R.id.app_overlay_stub).inflate(); 631 mAppOverlayView.setBackground(mOverlayBackground); 632 mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon); 633 mAppIconView.setOnClickListener(this); 634 mAppIconView.setOnLongClickListener(this); 635 mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info); 636 mAppInfoView.setOnClickListener(this); 637 mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title); 638 updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView); 639 } 640 641 // Update the overlay contents for the current app 642 mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId)); 643 mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ? 644 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor); 645 mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo, 646 userId)); 647 mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor 648 ? mLightInfoIcon 649 : mDarkInfoIcon); 650 mAppOverlayView.setVisibility(View.VISIBLE); 651 652 int x = mIconView.getLeft() + mIconView.getWidth() / 2; 653 int y = mIconView.getTop() + mIconView.getHeight() / 2; 654 Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0, 655 getWidth()); 656 revealAnim.setDuration(OVERLAY_REVEAL_DURATION); 657 revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 658 revealAnim.start(); 659 } 660 661 /** 662 * Hide the application overlay. 663 */ 664 private void hideAppOverlay(boolean immediate) { 665 // Skip if we haven't even loaded the overlay yet 666 if (mAppOverlayView == null) { 667 return; 668 } 669 670 if (immediate) { 671 mAppOverlayView.setVisibility(View.GONE); 672 } else { 673 int x = mIconView.getLeft() + mIconView.getWidth() / 2; 674 int y = mIconView.getTop() + mIconView.getHeight() / 2; 675 Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 676 getWidth(), 0); 677 revealAnim.setDuration(OVERLAY_REVEAL_DURATION); 678 revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 679 revealAnim.addListener(new AnimatorListenerAdapter() { 680 @Override 681 public void onAnimationEnd(Animator animation) { 682 mAppOverlayView.setVisibility(View.GONE); 683 } 684 }); 685 revealAnim.start(); 686 } 687 } 688} 689