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