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