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