TaskView.java revision ca8686af744f30992904ead71db7bb39866d4f99
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 // Hide the action button 232 mActionButtonView.setAlpha(0f); 233 } else if (occludesLaunchTarget) { 234 // Move the task view off screen (below) so we can animate it in 235 setTranslationY(offscreenY); 236 } 237 238 } else if (mConfig.launchedFromHome) { 239 // Move the task view off screen (below) so we can animate it in 240 setTranslationY(offscreenY); 241 setTranslationZ(0); 242 setScaleX(1f); 243 setScaleY(1f); 244 } 245 // Apply the current dim 246 setDim(initialDim); 247 // Prepare the thumbnail view alpha 248 mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask); 249 } 250 251 /** Animates this task view as it enters recents */ 252 void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) { 253 final TaskViewTransform transform = ctx.currentTaskTransform; 254 int startDelay = 0; 255 256 if (mConfig.launchedFromAppWithThumbnail) { 257 if (mTask.isLaunchTarget) { 258 // Animate the dim/overlay 259 if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) { 260 // Animate the thumbnail alpha before the dim animation (to prevent updating the 261 // hardware layer) 262 mThumbnailView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, 263 new Runnable() { 264 @Override 265 public void run() { 266 animateDimToProgress(0, mConfig.taskBarEnterAnimDuration, 267 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 268 } 269 }); 270 } else { 271 // Immediately start the dim animation 272 animateDimToProgress(mConfig.taskBarEnterAnimDelay, 273 mConfig.taskBarEnterAnimDuration, 274 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 275 } 276 ctx.postAnimationTrigger.increment(); 277 278 // Animate the action button in 279 fadeInActionButton(true); 280 } else { 281 // Animate the task up if it was occluding the launch target 282 if (ctx.currentTaskOccludesLaunchTarget) { 283 setTranslationY(transform.translationY + mConfig.taskViewAffiliateGroupEnterOffsetPx); 284 setAlpha(0f); 285 animate().alpha(1f) 286 .translationY(transform.translationY) 287 .setStartDelay(mConfig.taskBarEnterAnimDelay) 288 .setUpdateListener(null) 289 .setInterpolator(mConfig.fastOutSlowInInterpolator) 290 .setDuration(mConfig.taskViewEnterFromHomeDuration) 291 .withEndAction(new Runnable() { 292 @Override 293 public void run() { 294 // Decrement the post animation trigger 295 ctx.postAnimationTrigger.decrement(); 296 } 297 }) 298 .start(); 299 ctx.postAnimationTrigger.increment(); 300 } 301 } 302 startDelay = mConfig.taskBarEnterAnimDelay; 303 304 } else if (mConfig.launchedFromHome) { 305 // Animate the tasks up 306 int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1); 307 int delay = mConfig.taskViewEnterFromHomeDelay + 308 frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay; 309 310 setScaleX(transform.scale); 311 setScaleY(transform.scale); 312 if (!mConfig.fakeShadows) { 313 animate().translationZ(transform.translationZ); 314 } 315 animate() 316 .translationY(transform.translationY) 317 .setStartDelay(delay) 318 .setUpdateListener(ctx.updateListener) 319 .setInterpolator(mConfig.quintOutInterpolator) 320 .setDuration(mConfig.taskViewEnterFromHomeDuration + 321 frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay) 322 .withEndAction(new Runnable() { 323 @Override 324 public void run() { 325 // Decrement the post animation trigger 326 ctx.postAnimationTrigger.decrement(); 327 } 328 }) 329 .start(); 330 ctx.postAnimationTrigger.increment(); 331 startDelay = delay; 332 } 333 334 // Enable the focus animations from this point onwards so that they aren't affected by the 335 // window transitions 336 postDelayed(new Runnable() { 337 @Override 338 public void run() { 339 enableFocusAnimations(); 340 } 341 }, (startDelay / 2)); 342 } 343 344 public void fadeInActionButton(boolean withDelay) { 345 // Hide the action button 346 mActionButtonView.setAlpha(0f); 347 348 // Animate the action button in 349 ViewPropertyAnimator animator = mActionButtonView.animate().alpha(1f) 350 .setDuration(mConfig.taskBarEnterAnimDuration) 351 .setInterpolator(PhoneStatusBar.ALPHA_IN) 352 .withLayer(); 353 if (withDelay) { 354 animator.setStartDelay(mConfig.taskBarEnterAnimDelay); 355 } 356 animator.start(); 357 } 358 359 /** Animates this task view as it leaves recents by pressing home. */ 360 void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 361 animate() 362 .translationY(ctx.offscreenTranslationY) 363 .setStartDelay(0) 364 .setUpdateListener(null) 365 .setInterpolator(mConfig.fastOutLinearInInterpolator) 366 .setDuration(mConfig.taskViewExitToHomeDuration) 367 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable()) 368 .start(); 369 ctx.postAnimationTrigger.increment(); 370 } 371 372 /** Animates this task view as it exits recents */ 373 void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask, 374 boolean occludesLaunchTarget, boolean lockToTask) { 375 if (isLaunchingTask) { 376 // Animate the thumbnail alpha back into full opacity for the window animation out 377 mThumbnailView.startLaunchTaskAnimation(postAnimRunnable); 378 379 // Animate the dim 380 if (mDim > 0) { 381 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0); 382 anim.setDuration(mConfig.taskBarExitAnimDuration); 383 anim.setInterpolator(mConfig.fastOutLinearInInterpolator); 384 anim.start(); 385 } 386 387 // Animate the action button away 388 if (!lockToTask) { 389 float toScale = 0.9f; 390 mActionButtonView.animate() 391 .scaleX(toScale) 392 .scaleY(toScale); 393 } 394 mActionButtonView.animate() 395 .alpha(0f) 396 .setStartDelay(0) 397 .setDuration(mConfig.taskBarExitAnimDuration) 398 .setInterpolator(mConfig.fastOutLinearInInterpolator) 399 .withLayer() 400 .start(); 401 } else { 402 // Hide the dismiss button 403 mHeaderView.startLaunchTaskDismissAnimation(); 404 // If this is another view in the task grouping and is in front of the launch task, 405 // animate it away first 406 if (occludesLaunchTarget) { 407 animate().alpha(0f) 408 .translationY(getTranslationY() + mConfig.taskViewAffiliateGroupEnterOffsetPx) 409 .setStartDelay(0) 410 .setUpdateListener(null) 411 .setInterpolator(mConfig.fastOutLinearInInterpolator) 412 .setDuration(mConfig.taskBarExitAnimDuration) 413 .start(); 414 } 415 } 416 } 417 418 /** Animates the deletion of this task view */ 419 void startDeleteTaskAnimation(final Runnable r) { 420 // Disabling clipping with the stack while the view is animating away 421 setClipViewInStack(false); 422 423 animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx) 424 .alpha(0f) 425 .setStartDelay(0) 426 .setUpdateListener(null) 427 .setInterpolator(mConfig.fastOutSlowInInterpolator) 428 .setDuration(mConfig.taskViewRemoveAnimDuration) 429 .withEndAction(new Runnable() { 430 @Override 431 public void run() { 432 // We just throw this into a runnable because starting a view property 433 // animation using layers can cause inconsisten results if we try and 434 // update the layers while the animation is running. In some cases, 435 // the runnabled passed in may start an animation which also uses layers 436 // so we defer all this by posting this. 437 r.run(); 438 439 // Re-enable clipping with the stack (we will reuse this view) 440 setClipViewInStack(true); 441 } 442 }) 443 .start(); 444 } 445 446 /** Animates this task view if the user does not interact with the stack after a certain time. */ 447 void startNoUserInteractionAnimation() { 448 mHeaderView.startNoUserInteractionAnimation(); 449 } 450 451 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 452 void setNoUserInteractionState() { 453 mHeaderView.setNoUserInteractionState(); 454 } 455 456 /** Dismisses this task. */ 457 void dismissTask() { 458 // Animate out the view and call the callback 459 final TaskView tv = this; 460 startDeleteTaskAnimation(new Runnable() { 461 @Override 462 public void run() { 463 mCb.onTaskViewDismissed(tv); 464 } 465 }); 466 } 467 468 /** 469 * Returns whether this view should be clipped, or any views below should clip against this 470 * view. 471 */ 472 boolean shouldClipViewInStack() { 473 return mClipViewInStack && (getVisibility() == View.VISIBLE); 474 } 475 476 /** Sets whether this view should be clipped, or clipped against. */ 477 void setClipViewInStack(boolean clip) { 478 if (clip != mClipViewInStack) { 479 mClipViewInStack = clip; 480 mCb.onTaskViewClipStateChanged(this); 481 } 482 } 483 484 /** Sets the current task progress. */ 485 public void setTaskProgress(float p) { 486 mTaskProgress = p; 487 mViewBounds.setAlpha(p); 488 updateDimFromTaskProgress(); 489 } 490 491 /** Returns the current task progress. */ 492 public float getTaskProgress() { 493 return mTaskProgress; 494 } 495 496 /** Returns the current dim. */ 497 public void setDim(int dim) { 498 mDim = dim; 499 if (mDimAnimator != null) { 500 mDimAnimator.removeAllListeners(); 501 mDimAnimator.cancel(); 502 } 503 if (mConfig.useHardwareLayers) { 504 // Defer setting hardware layers if we have not yet measured, or there is no dim to draw 505 if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) { 506 if (mDimAnimator != null) { 507 mDimAnimator.removeAllListeners(); 508 mDimAnimator.cancel(); 509 } 510 511 int inverse = 255 - mDim; 512 mDimColorFilter.setColor(Color.argb(0xFF, inverse, inverse, inverse)); 513 mLayerPaint.setColorFilter(mDimColorFilter); 514 mContent.setLayerType(LAYER_TYPE_HARDWARE, mLayerPaint); 515 } 516 } else { 517 float dimAlpha = mDim / 255.0f; 518 if (mThumbnailView != null) { 519 mThumbnailView.setDimAlpha(dimAlpha); 520 } 521 if (mHeaderView != null) { 522 mHeaderView.setDimAlpha(dim); 523 } 524 } 525 } 526 527 /** Returns the current dim. */ 528 public int getDim() { 529 return mDim; 530 } 531 532 /** Animates the dim to the task progress. */ 533 void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) { 534 // Animate the dim into view as well 535 int toDim = getDimFromTaskProgress(); 536 if (toDim != getDim()) { 537 ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim); 538 anim.setStartDelay(delay); 539 anim.setDuration(duration); 540 if (postAnimRunnable != null) { 541 anim.addListener(postAnimRunnable); 542 } 543 anim.start(); 544 } 545 } 546 547 /** Compute the dim as a function of the scale of this view. */ 548 int getDimFromTaskProgress() { 549 float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress); 550 return (int) (dim * 255); 551 } 552 553 /** Update the dim as a function of the scale of this view. */ 554 void updateDimFromTaskProgress() { 555 setDim(getDimFromTaskProgress()); 556 } 557 558 /**** View focus state ****/ 559 560 /** 561 * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen 562 * if the view is not currently visible, or we are in touch state (where we still want to keep 563 * track of focus). 564 */ 565 public void setFocusedTask(boolean animateFocusedState) { 566 mIsFocused = true; 567 if (mFocusAnimationsEnabled) { 568 // Focus the header bar 569 mHeaderView.onTaskViewFocusChanged(true, animateFocusedState); 570 } 571 // Update the thumbnail alpha with the focus 572 mThumbnailView.onFocusChanged(true); 573 // Call the callback 574 mCb.onTaskViewFocusChanged(this, true); 575 // Workaround, we don't always want it focusable in touch mode, but we want the first task 576 // to be focused after the enter-recents animation, which can be triggered from either touch 577 // or keyboard 578 setFocusableInTouchMode(true); 579 requestFocus(); 580 setFocusableInTouchMode(false); 581 invalidate(); 582 } 583 584 /** 585 * Unsets the focused task explicitly. 586 */ 587 void unsetFocusedTask() { 588 mIsFocused = false; 589 if (mFocusAnimationsEnabled) { 590 // Un-focus the header bar 591 mHeaderView.onTaskViewFocusChanged(false, true); 592 } 593 594 // Update the thumbnail alpha with the focus 595 mThumbnailView.onFocusChanged(false); 596 // Call the callback 597 mCb.onTaskViewFocusChanged(this, false); 598 invalidate(); 599 } 600 601 /** 602 * Updates the explicitly focused state when the view focus changes. 603 */ 604 @Override 605 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 606 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 607 if (!gainFocus) { 608 unsetFocusedTask(); 609 } 610 } 611 612 /** 613 * Returns whether we have explicitly been focused. 614 */ 615 public boolean isFocusedTask() { 616 return mIsFocused || isFocused(); 617 } 618 619 /** Enables all focus animations. */ 620 void enableFocusAnimations() { 621 boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled; 622 mFocusAnimationsEnabled = true; 623 if (mIsFocused && !wasFocusAnimationsEnabled) { 624 // Re-notify the header if we were focused and animations were not previously enabled 625 mHeaderView.onTaskViewFocusChanged(true, true); 626 } 627 } 628 629 /**** TaskCallbacks Implementation ****/ 630 631 /** Binds this task view to the task */ 632 public void onTaskBound(Task t) { 633 mTask = t; 634 mTask.setCallbacks(this); 635 636 // Hide the action button if lock to app is disabled for this view 637 int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE; 638 if (mActionButtonView.getVisibility() != lockButtonVisibility) { 639 mActionButtonView.setVisibility(lockButtonVisibility); 640 requestLayout(); 641 } 642 } 643 644 @Override 645 public void onTaskDataLoaded() { 646 if (mThumbnailView != null && mHeaderView != null) { 647 // Bind each of the views to the new task data 648 mThumbnailView.rebindToTask(mTask); 649 mHeaderView.rebindToTask(mTask); 650 // Rebind any listeners 651 mHeaderView.mApplicationIcon.setOnClickListener(this); 652 mHeaderView.mDismissButton.setOnClickListener(this); 653 mActionButtonView.setOnClickListener(this); 654 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 655 if (mConfig.developerOptionsEnabled) { 656 mHeaderView.mApplicationIcon.setOnLongClickListener(this); 657 } 658 } 659 } 660 mTaskDataLoaded = true; 661 } 662 663 @Override 664 public void onTaskDataUnloaded() { 665 if (mThumbnailView != null && mHeaderView != null) { 666 // Unbind each of the views from the task data and remove the task callback 667 mTask.setCallbacks(null); 668 mThumbnailView.unbindFromTask(); 669 mHeaderView.unbindFromTask(); 670 // Unbind any listeners 671 mHeaderView.mApplicationIcon.setOnClickListener(null); 672 mHeaderView.mDismissButton.setOnClickListener(null); 673 mActionButtonView.setOnClickListener(null); 674 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 675 mHeaderView.mApplicationIcon.setOnLongClickListener(null); 676 } 677 } 678 mTaskDataLoaded = false; 679 } 680 681 /** Enables/disables handling touch on this task view. */ 682 void setTouchEnabled(boolean enabled) { 683 setOnClickListener(enabled ? this : null); 684 } 685 686 /**** View.OnClickListener Implementation ****/ 687 688 @Override 689 public void onClick(final View v) { 690 final TaskView tv = this; 691 final boolean delayViewClick = (v != this) && (v != mActionButtonView); 692 if (delayViewClick) { 693 // We purposely post the handler delayed to allow for the touch feedback to draw 694 postDelayed(new Runnable() { 695 @Override 696 public void run() { 697 if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) { 698 mCb.onTaskViewAppIconClicked(tv); 699 } else if (v == mHeaderView.mDismissButton) { 700 dismissTask(); 701 } 702 } 703 }, 125); 704 } else { 705 if (v == mActionButtonView) { 706 // Reset the translation of the action button before we animate it out 707 mActionButtonView.setTranslationZ(0f); 708 } 709 mCb.onTaskViewClicked(tv, tv.getTask(), (v == mActionButtonView)); 710 } 711 } 712 713 /**** View.OnLongClickListener Implementation ****/ 714 715 @Override 716 public boolean onLongClick(View v) { 717 if (v == mHeaderView.mApplicationIcon) { 718 mCb.onTaskViewAppInfoClicked(this); 719 return true; 720 } 721 return false; 722 } 723} 724