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.ObjectAnimator; 21 import android.animation.ValueAnimator; 22 import android.content.Context; 23 import android.graphics.*; 24 import android.util.AttributeSet; 25 import android.view.accessibility.AccessibilityManager; 26 import android.view.View; 27 import android.view.ViewOutlineProvider; 28 import android.view.animation.AccelerateInterpolator; 29 import android.widget.FrameLayout; 30 import com.android.internal.logging.MetricsLogger; 31 import com.android.systemui.R; 32 import com.android.systemui.recents.Constants; 33 import com.android.systemui.recents.RecentsConfiguration; 34 import com.android.systemui.recents.misc.Utilities; 35 import com.android.systemui.recents.model.Task; 36 import com.android.systemui.statusbar.phone.PhoneStatusBar; 37 38 /* A task view */ 39 public class TaskView extends FrameLayout implements Task.TaskCallbacks, 40 View.OnClickListener, View.OnLongClickListener { 41 42 /** The TaskView callbacks */ 43 interface TaskViewCallbacks { 44 public void onTaskViewAppIconClicked(TaskView tv); 45 public void onTaskViewAppInfoClicked(TaskView tv); 46 public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask); 47 public void onTaskViewDismissed(TaskView tv); 48 public void onTaskViewClipStateChanged(TaskView tv); 49 public void onTaskViewFocusChanged(TaskView tv, boolean focused); 50 51 public void onTaskResize(TaskView tv); 52 } 53 54 RecentsConfiguration mConfig; 55 56 float mTaskProgress; 57 ObjectAnimator mTaskProgressAnimator; 58 float mMaxDimScale; 59 int mDimAlpha; 60 AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1f); 61 PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP); 62 Paint mDimLayerPaint = new Paint(); 63 float mActionButtonTranslationZ; 64 65 Task mTask; 66 boolean mTaskDataLoaded; 67 boolean mIsFocused; 68 boolean mFocusAnimationsEnabled; 69 boolean mClipViewInStack; 70 AnimateableViewBounds mViewBounds; 71 72 View mContent; 73 TaskViewThumbnail mThumbnailView; 74 TaskViewHeader mHeaderView; 75 View mActionButtonView; 76 TaskViewCallbacks mCb; 77 78 // Optimizations 79 ValueAnimator.AnimatorUpdateListener mUpdateDimListener = 80 new ValueAnimator.AnimatorUpdateListener() { 81 @Override 82 public void onAnimationUpdate(ValueAnimator animation) { 83 setTaskProgress((Float) animation.getAnimatedValue()); 84 } 85 }; 86 87 88 public TaskView(Context context) { 89 this(context, null); 90 } 91 92 public TaskView(Context context, AttributeSet attrs) { 93 this(context, attrs, 0); 94 } 95 96 public TaskView(Context context, AttributeSet attrs, int defStyleAttr) { 97 this(context, attrs, defStyleAttr, 0); 98 } 99 100 public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 101 super(context, attrs, defStyleAttr, defStyleRes); 102 mConfig = RecentsConfiguration.getInstance(); 103 mMaxDimScale = mConfig.taskStackMaxDim / 255f; 104 mClipViewInStack = true; 105 mViewBounds = new AnimateableViewBounds(this, mConfig.taskViewRoundedCornerRadiusPx); 106 setTaskProgress(getTaskProgress()); 107 setDim(getDim()); 108 if (mConfig.fakeShadows) { 109 setBackground(new FakeShadowDrawable(context.getResources(), mConfig)); 110 } 111 setOutlineProvider(mViewBounds); 112 } 113 114 /** Set callback */ 115 void setCallbacks(TaskViewCallbacks cb) { 116 mCb = cb; 117 } 118 119 /** Resets this TaskView for reuse. */ 120 void reset() { 121 resetViewProperties(); 122 resetNoUserInteractionState(); 123 setClipViewInStack(false); 124 setCallbacks(null); 125 } 126 127 /** Gets the task */ 128 Task getTask() { 129 return mTask; 130 } 131 132 /** Returns the view bounds. */ 133 AnimateableViewBounds getViewBounds() { 134 return mViewBounds; 135 } 136 137 @Override 138 protected void onFinishInflate() { 139 // Bind the views 140 mContent = findViewById(R.id.task_view_content); 141 mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar); 142 mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail); 143 mThumbnailView.updateClipToTaskBar(mHeaderView); 144 mActionButtonView = findViewById(R.id.lock_to_app_fab); 145 mActionButtonView.setOutlineProvider(new ViewOutlineProvider() { 146 @Override 147 public void getOutline(View view, Outline outline) { 148 // Set the outline to match the FAB background 149 outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight()); 150 } 151 }); 152 mActionButtonTranslationZ = mActionButtonView.getTranslationZ(); 153 } 154 155 @Override 156 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 157 int width = MeasureSpec.getSize(widthMeasureSpec); 158 int height = MeasureSpec.getSize(heightMeasureSpec); 159 160 int widthWithoutPadding = width - mPaddingLeft - mPaddingRight; 161 int heightWithoutPadding = height - mPaddingTop - mPaddingBottom; 162 163 // Measure the content 164 mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 165 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY)); 166 167 // Measure the bar view, and action button 168 mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 169 MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY)); 170 mActionButtonView.measure( 171 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST), 172 MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST)); 173 // Measure the thumbnail to be square 174 mThumbnailView.measure( 175 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 176 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY)); 177 setMeasuredDimension(width, height); 178 invalidateOutline(); 179 } 180 181 /** Synchronizes this view's properties with the task's transform */ 182 void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) { 183 updateViewPropertiesToTaskTransform(toTransform, duration, null); 184 } 185 186 void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration, 187 ValueAnimator.AnimatorUpdateListener updateCallback) { 188 // Apply the transform 189 toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator, false, 190 !mConfig.fakeShadows, updateCallback); 191 192 // Update the task progress 193 Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator); 194 if (duration <= 0) { 195 setTaskProgress(toTransform.p); 196 } else { 197 mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p); 198 mTaskProgressAnimator.setDuration(duration); 199 mTaskProgressAnimator.addUpdateListener(mUpdateDimListener); 200 mTaskProgressAnimator.start(); 201 } 202 } 203 204 /** Resets this view's properties */ 205 void resetViewProperties() { 206 setDim(0); 207 setLayerType(View.LAYER_TYPE_NONE, null); 208 TaskViewTransform.reset(this); 209 if (mActionButtonView != null) { 210 mActionButtonView.setScaleX(1f); 211 mActionButtonView.setScaleY(1f); 212 mActionButtonView.setAlpha(1f); 213 mActionButtonView.setTranslationZ(mActionButtonTranslationZ); 214 } 215 } 216 217 /** 218 * When we are un/filtering, this method will set up the transform that we are animating to, 219 * in order to hide the task. 220 */ 221 void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) { 222 // Fade the view out and slide it away 223 toTransform.alpha = 0f; 224 toTransform.translationY += 200; 225 toTransform.translationZ = 0; 226 } 227 228 /** 229 * When we are un/filtering, this method will setup the transform that we are animating from, 230 * in order to show the task. 231 */ 232 void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) { 233 // Fade the view in 234 fromTransform.alpha = 0f; 235 } 236 237 /** Prepares this task view for the enter-recents animations. This is called earlier in the 238 * first layout because the actual animation into recents may take a long time. */ 239 void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, 240 boolean occludesLaunchTarget, int offscreenY) { 241 int initialDim = getDim(); 242 if (mConfig.launchedHasConfigurationChanged) { 243 // Just load the views as-is 244 } else if (mConfig.launchedFromAppWithThumbnail) { 245 if (isTaskViewLaunchTargetTask) { 246 // Set the dim to 0 so we can animate it in 247 initialDim = 0; 248 // Hide the action button 249 mActionButtonView.setAlpha(0f); 250 } else if (occludesLaunchTarget) { 251 // Move the task view off screen (below) so we can animate it in 252 setTranslationY(offscreenY); 253 } 254 255 } else if (mConfig.launchedFromHome) { 256 // Move the task view off screen (below) so we can animate it in 257 setTranslationY(offscreenY); 258 setTranslationZ(0); 259 setScaleX(1f); 260 setScaleY(1f); 261 } 262 // Apply the current dim 263 setDim(initialDim); 264 // Prepare the thumbnail view alpha 265 mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask); 266 } 267 268 /** Animates this task view as it enters recents */ 269 void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) { 270 final TaskViewTransform transform = ctx.currentTaskTransform; 271 int startDelay = 0; 272 273 if (mConfig.launchedFromAppWithThumbnail) { 274 if (mTask.isLaunchTarget) { 275 // Animate the dim/overlay 276 if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) { 277 // Animate the thumbnail alpha before the dim animation (to prevent updating the 278 // hardware layer) 279 mThumbnailView.startEnterRecentsAnimation(mConfig.transitionEnterFromAppDelay, 280 new Runnable() { 281 @Override 282 public void run() { 283 animateDimToProgress(0, mConfig.taskViewEnterFromAppDuration, 284 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 285 } 286 }); 287 } else { 288 // Immediately start the dim animation 289 animateDimToProgress(mConfig.transitionEnterFromAppDelay, 290 mConfig.taskViewEnterFromAppDuration, 291 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 292 } 293 ctx.postAnimationTrigger.increment(); 294 295 // Animate the action button in 296 fadeInActionButton(mConfig.transitionEnterFromAppDelay, 297 mConfig.taskViewEnterFromAppDuration); 298 } else { 299 // Animate the task up if it was occluding the launch target 300 if (ctx.currentTaskOccludesLaunchTarget) { 301 setTranslationY(transform.translationY + mConfig.taskViewAffiliateGroupEnterOffsetPx); 302 setAlpha(0f); 303 animate().alpha(1f) 304 .translationY(transform.translationY) 305 .setStartDelay(mConfig.transitionEnterFromAppDelay) 306 .setUpdateListener(null) 307 .setInterpolator(mConfig.fastOutSlowInInterpolator) 308 .setDuration(mConfig.taskViewEnterFromHomeDuration) 309 .withEndAction(new Runnable() { 310 @Override 311 public void run() { 312 // Decrement the post animation trigger 313 ctx.postAnimationTrigger.decrement(); 314 } 315 }) 316 .start(); 317 ctx.postAnimationTrigger.increment(); 318 } 319 } 320 startDelay = mConfig.transitionEnterFromAppDelay; 321 322 } else if (mConfig.launchedFromHome) { 323 // Animate the tasks up 324 int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1); 325 int delay = mConfig.transitionEnterFromHomeDelay + 326 frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay; 327 328 setScaleX(transform.scale); 329 setScaleY(transform.scale); 330 if (!mConfig.fakeShadows) { 331 animate().translationZ(transform.translationZ); 332 } 333 animate() 334 .translationY(transform.translationY) 335 .setStartDelay(delay) 336 .setUpdateListener(ctx.updateListener) 337 .setInterpolator(mConfig.quintOutInterpolator) 338 .setDuration(mConfig.taskViewEnterFromHomeDuration + 339 frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay) 340 .withEndAction(new Runnable() { 341 @Override 342 public void run() { 343 // Decrement the post animation trigger 344 ctx.postAnimationTrigger.decrement(); 345 } 346 }) 347 .start(); 348 ctx.postAnimationTrigger.increment(); 349 startDelay = delay; 350 } 351 352 // Enable the focus animations from this point onwards so that they aren't affected by the 353 // window transitions 354 postDelayed(new Runnable() { 355 @Override 356 public void run() { 357 enableFocusAnimations(); 358 } 359 }, startDelay); 360 } 361 362 public void fadeInActionButton(int delay, int duration) { 363 // Hide the action button 364 mActionButtonView.setAlpha(0f); 365 366 // Animate the action button in 367 mActionButtonView.animate().alpha(1f) 368 .setStartDelay(delay) 369 .setDuration(duration) 370 .setInterpolator(PhoneStatusBar.ALPHA_IN) 371 .start(); 372 } 373 374 /** Animates this task view as it leaves recents by pressing home. */ 375 void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 376 animate() 377 .translationY(ctx.offscreenTranslationY) 378 .setStartDelay(0) 379 .setUpdateListener(null) 380 .setInterpolator(mConfig.fastOutLinearInInterpolator) 381 .setDuration(mConfig.taskViewExitToHomeDuration) 382 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable()) 383 .start(); 384 ctx.postAnimationTrigger.increment(); 385 } 386 387 /** Animates this task view away when dismissing all tasks. */ 388 void startDismissAllAnimation() { 389 dismissTask(); 390 } 391 392 /** Animates this task view as it exits recents */ 393 void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask, 394 boolean occludesLaunchTarget, boolean lockToTask) { 395 if (isLaunchingTask) { 396 // Animate the thumbnail alpha back into full opacity for the window animation out 397 mThumbnailView.startLaunchTaskAnimation(postAnimRunnable); 398 399 // Animate the dim 400 if (mDimAlpha > 0) { 401 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0); 402 anim.setDuration(mConfig.taskViewExitToAppDuration); 403 anim.setInterpolator(mConfig.fastOutLinearInInterpolator); 404 anim.start(); 405 } 406 407 // Animate the action button away 408 if (!lockToTask) { 409 float toScale = 0.9f; 410 mActionButtonView.animate() 411 .scaleX(toScale) 412 .scaleY(toScale); 413 } 414 mActionButtonView.animate() 415 .alpha(0f) 416 .setStartDelay(0) 417 .setDuration(mConfig.taskViewExitToAppDuration) 418 .setInterpolator(mConfig.fastOutLinearInInterpolator) 419 .start(); 420 } else { 421 // Hide the dismiss button 422 mHeaderView.startLaunchTaskDismissAnimation(); 423 // If this is another view in the task grouping and is in front of the launch task, 424 // animate it away first 425 if (occludesLaunchTarget) { 426 animate().alpha(0f) 427 .translationY(getTranslationY() + mConfig.taskViewAffiliateGroupEnterOffsetPx) 428 .setStartDelay(0) 429 .setUpdateListener(null) 430 .setInterpolator(mConfig.fastOutLinearInInterpolator) 431 .setDuration(mConfig.taskViewExitToAppDuration) 432 .start(); 433 } 434 } 435 } 436 437 /** Animates the deletion of this task view */ 438 void startDeleteTaskAnimation(final Runnable r, int delay) { 439 // Disabling clipping with the stack while the view is animating away 440 setClipViewInStack(false); 441 442 animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx) 443 .alpha(0f) 444 .setStartDelay(delay) 445 .setUpdateListener(null) 446 .setInterpolator(mConfig.fastOutSlowInInterpolator) 447 .setDuration(mConfig.taskViewRemoveAnimDuration) 448 .withEndAction(new Runnable() { 449 @Override 450 public void run() { 451 if (r != null) { 452 r.run(); 453 } 454 455 // Re-enable clipping with the stack (we will reuse this view) 456 setClipViewInStack(true); 457 } 458 }) 459 .start(); 460 } 461 462 /** Enables/disables handling touch on this task view. */ 463 void setTouchEnabled(boolean enabled) { 464 setOnClickListener(enabled ? this : null); 465 } 466 467 /** Animates this task view if the user does not interact with the stack after a certain time. */ 468 void startNoUserInteractionAnimation() { 469 mHeaderView.startNoUserInteractionAnimation(); 470 } 471 472 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 473 void setNoUserInteractionState() { 474 mHeaderView.setNoUserInteractionState(); 475 } 476 477 /** Resets the state tracking that the user has not interacted with the stack after a certain time. */ 478 void resetNoUserInteractionState() { 479 mHeaderView.resetNoUserInteractionState(); 480 } 481 482 /** Dismisses this task. */ 483 void dismissTask() { 484 // Animate out the view and call the callback 485 final TaskView tv = this; 486 startDeleteTaskAnimation(new Runnable() { 487 @Override 488 public void run() { 489 if (mCb != null) { 490 mCb.onTaskViewDismissed(tv); 491 } 492 } 493 }, 0); 494 } 495 496 /** 497 * Returns whether this view should be clipped, or any views below should clip against this 498 * view. 499 */ 500 boolean shouldClipViewInStack() { 501 return mClipViewInStack && (getVisibility() == View.VISIBLE); 502 } 503 504 /** Sets whether this view should be clipped, or clipped against. */ 505 void setClipViewInStack(boolean clip) { 506 if (clip != mClipViewInStack) { 507 mClipViewInStack = clip; 508 if (mCb != null) { 509 mCb.onTaskViewClipStateChanged(this); 510 } 511 } 512 } 513 514 /** Sets the current task progress. */ 515 public void setTaskProgress(float p) { 516 mTaskProgress = p; 517 mViewBounds.setAlpha(p); 518 updateDimFromTaskProgress(); 519 } 520 521 /** Returns the current task progress. */ 522 public float getTaskProgress() { 523 return mTaskProgress; 524 } 525 526 /** Returns the current dim. */ 527 public void setDim(int dim) { 528 mDimAlpha = dim; 529 if (mConfig.useHardwareLayers) { 530 // Defer setting hardware layers if we have not yet measured, or there is no dim to draw 531 if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) { 532 mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0)); 533 mDimLayerPaint.setColorFilter(mDimColorFilter); 534 mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); 535 } 536 } else { 537 float dimAlpha = mDimAlpha / 255.0f; 538 if (mThumbnailView != null) { 539 mThumbnailView.setDimAlpha(dimAlpha); 540 } 541 if (mHeaderView != null) { 542 mHeaderView.setDimAlpha(dim); 543 } 544 } 545 } 546 547 /** Returns the current dim. */ 548 public int getDim() { 549 return mDimAlpha; 550 } 551 552 /** Animates the dim to the task progress. */ 553 void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) { 554 // Animate the dim into view as well 555 int toDim = getDimFromTaskProgress(); 556 if (toDim != getDim()) { 557 ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim); 558 anim.setStartDelay(delay); 559 anim.setDuration(duration); 560 if (postAnimRunnable != null) { 561 anim.addListener(postAnimRunnable); 562 } 563 anim.start(); 564 } 565 } 566 567 /** Compute the dim as a function of the scale of this view. */ 568 int getDimFromTaskProgress() { 569 float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress); 570 return (int) (dim * 255); 571 } 572 573 /** Update the dim as a function of the scale of this view. */ 574 void updateDimFromTaskProgress() { 575 setDim(getDimFromTaskProgress()); 576 } 577 578 /**** View focus state ****/ 579 580 /** 581 * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen 582 * if the view is not currently visible, or we are in touch state (where we still want to keep 583 * track of focus). 584 */ 585 public void setFocusedTask(boolean animateFocusedState) { 586 mIsFocused = true; 587 if (mFocusAnimationsEnabled) { 588 // Focus the header bar 589 mHeaderView.onTaskViewFocusChanged(true, animateFocusedState); 590 } 591 // Update the thumbnail alpha with the focus 592 mThumbnailView.onFocusChanged(true); 593 // Call the callback 594 if (mCb != null) { 595 mCb.onTaskViewFocusChanged(this, true); 596 } 597 // Workaround, we don't always want it focusable in touch mode, but we want the first task 598 // to be focused after the enter-recents animation, which can be triggered from either touch 599 // or keyboard 600 setFocusableInTouchMode(true); 601 requestFocus(); 602 setFocusableInTouchMode(false); 603 invalidate(); 604 } 605 606 /** 607 * Unsets the focused task explicitly. 608 */ 609 void unsetFocusedTask() { 610 mIsFocused = false; 611 if (mFocusAnimationsEnabled) { 612 // Un-focus the header bar 613 mHeaderView.onTaskViewFocusChanged(false, true); 614 } 615 616 // Update the thumbnail alpha with the focus 617 mThumbnailView.onFocusChanged(false); 618 // Call the callback 619 if (mCb != null) { 620 mCb.onTaskViewFocusChanged(this, false); 621 } 622 invalidate(); 623 } 624 625 /** 626 * Updates the explicitly focused state when the view focus changes. 627 */ 628 @Override 629 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 630 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 631 if (!gainFocus) { 632 unsetFocusedTask(); 633 } 634 } 635 636 /** 637 * Returns whether we have explicitly been focused. 638 */ 639 public boolean isFocusedTask() { 640 return mIsFocused || isFocused(); 641 } 642 643 /** Enables all focus animations. */ 644 void enableFocusAnimations() { 645 boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled; 646 mFocusAnimationsEnabled = true; 647 if (mIsFocused && !wasFocusAnimationsEnabled) { 648 // Re-notify the header if we were focused and animations were not previously enabled 649 mHeaderView.onTaskViewFocusChanged(true, true); 650 } 651 } 652 653 public void disableLayersForOneFrame() { 654 mHeaderView.disableLayersForOneFrame(); 655 } 656 657 /**** TaskCallbacks Implementation ****/ 658 659 /** Binds this task view to the task */ 660 public void onTaskBound(Task t) { 661 mTask = t; 662 mTask.setCallbacks(this); 663 664 // Hide the action button if lock to app is disabled for this view 665 int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE; 666 if (mActionButtonView.getVisibility() != lockButtonVisibility) { 667 mActionButtonView.setVisibility(lockButtonVisibility); 668 requestLayout(); 669 } 670 } 671 672 @Override 673 public void onTaskDataLoaded() { 674 if (mThumbnailView != null && mHeaderView != null) { 675 // Bind each of the views to the new task data 676 mThumbnailView.rebindToTask(mTask); 677 mHeaderView.rebindToTask(mTask); 678 // Rebind any listeners 679 AccessibilityManager am = (AccessibilityManager) getContext(). 680 getSystemService(Context.ACCESSIBILITY_SERVICE); 681 if (Constants.DebugFlags.App.EnableTaskFiltering || (am != null && am.isEnabled())) { 682 mHeaderView.mApplicationIcon.setOnClickListener(this); 683 } 684 mHeaderView.mDismissButton.setOnClickListener(this); 685 if (mConfig.multiStackEnabled) { 686 mHeaderView.mMoveTaskButton.setOnClickListener(this); 687 } 688 mActionButtonView.setOnClickListener(this); 689 mHeaderView.mApplicationIcon.setOnLongClickListener(this); 690 } 691 mTaskDataLoaded = true; 692 } 693 694 @Override 695 public void onTaskDataUnloaded() { 696 if (mThumbnailView != null && mHeaderView != null) { 697 // Unbind each of the views from the task data and remove the task callback 698 mTask.setCallbacks(null); 699 mThumbnailView.unbindFromTask(); 700 mHeaderView.unbindFromTask(); 701 // Unbind any listeners 702 mHeaderView.mApplicationIcon.setOnClickListener(null); 703 mHeaderView.mDismissButton.setOnClickListener(null); 704 if (mConfig.multiStackEnabled) { 705 mHeaderView.mMoveTaskButton.setOnClickListener(null); 706 } 707 mActionButtonView.setOnClickListener(null); 708 mHeaderView.mApplicationIcon.setOnLongClickListener(null); 709 } 710 mTaskDataLoaded = false; 711 } 712 713 @Override 714 public void onMultiStackDebugTaskStackIdChanged() { 715 mHeaderView.rebindToTask(mTask); 716 } 717 718 /**** View.OnClickListener Implementation ****/ 719 720 @Override 721 public void onClick(final View v) { 722 final TaskView tv = this; 723 final boolean delayViewClick = (v != this) && (v != mActionButtonView); 724 if (delayViewClick) { 725 // We purposely post the handler delayed to allow for the touch feedback to draw 726 postDelayed(new Runnable() { 727 @Override 728 public void run() { 729 if (v == mHeaderView.mApplicationIcon) { 730 if (Constants.DebugFlags.App.EnableTaskFiltering) { 731 if (mCb != null) { 732 mCb.onTaskViewAppIconClicked(tv); 733 } 734 } else { 735 AccessibilityManager am = (AccessibilityManager) getContext(). 736 getSystemService(Context.ACCESSIBILITY_SERVICE); 737 if (am != null && am.isEnabled()) { 738 if (mCb != null) { 739 mCb.onTaskViewAppInfoClicked(tv); 740 } 741 } 742 } 743 } else if (v == mHeaderView.mDismissButton) { 744 dismissTask(); 745 // Keep track of deletions by the dismiss button 746 MetricsLogger.histogram(getContext(), "overview_task_dismissed_source", 747 Constants.Metrics.DismissSourceHeaderButton); 748 } else if (v == mHeaderView.mMoveTaskButton) { 749 if (mCb != null) { 750 mCb.onTaskResize(tv); 751 } 752 } 753 } 754 }, 125); 755 } else { 756 if (v == mActionButtonView) { 757 // Reset the translation of the action button before we animate it out 758 mActionButtonView.setTranslationZ(0f); 759 } 760 if (mCb != null) { 761 mCb.onTaskViewClicked(tv, tv.getTask(), (v == mActionButtonView)); 762 } 763 } 764 } 765 766 /**** View.OnLongClickListener Implementation ****/ 767 768 @Override 769 public boolean onLongClick(View v) { 770 if (v == mHeaderView.mApplicationIcon) { 771 if (mCb != null) { 772 mCb.onTaskViewAppInfoClicked(this); 773 return true; 774 } 775 } 776 return false; 777 } 778 } 779