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