TaskView.java revision e8199c582d826a39e6e47b0d8418834c15242fec
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.Color; 24import android.graphics.Outline; 25import android.graphics.Paint; 26import android.graphics.PorterDuff; 27import android.graphics.PorterDuffColorFilter; 28import android.graphics.Rect; 29import android.util.AttributeSet; 30import android.view.View; 31import android.view.ViewOutlineProvider; 32import android.view.animation.AccelerateInterpolator; 33import android.widget.FrameLayout; 34import com.android.systemui.R; 35import com.android.systemui.recents.AlternateRecentsComponent; 36import com.android.systemui.recents.Constants; 37import com.android.systemui.recents.RecentsConfiguration; 38import com.android.systemui.recents.model.RecentsTaskLoader; 39import com.android.systemui.recents.model.Task; 40 41/* A task view */ 42public class TaskView extends FrameLayout implements Task.TaskCallbacks, 43 TaskViewFooter.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener { 44 45 /** The TaskView callbacks */ 46 interface TaskViewCallbacks { 47 public void onTaskViewAppIconClicked(TaskView tv); 48 public void onTaskViewAppInfoClicked(TaskView tv); 49 public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask); 50 public void onTaskViewDismissed(TaskView tv); 51 public void onTaskViewClipStateChanged(TaskView tv); 52 public void onTaskViewFullScreenTransitionCompleted(); 53 public void onTaskViewFocusChanged(TaskView tv, boolean focused); 54 } 55 56 RecentsConfiguration mConfig; 57 58 float mTaskProgress; 59 ObjectAnimator mTaskProgressAnimator; 60 ObjectAnimator mDimAnimator; 61 float mMaxDimScale; 62 int mDim; 63 AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1f); 64 PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.MULTIPLY); 65 66 Task mTask; 67 boolean mTaskDataLoaded; 68 boolean mIsFocused; 69 boolean mFocusAnimationsEnabled; 70 boolean mIsFullScreenView; 71 boolean mClipViewInStack; 72 AnimateableViewBounds mViewBounds; 73 Paint mLayerPaint = new Paint(); 74 75 TaskViewThumbnail mThumbnailView; 76 TaskViewHeader mHeaderView; 77 TaskViewFooter mFooterView; 78 View mActionButtonView; 79 TaskViewCallbacks mCb; 80 81 // Optimizations 82 ValueAnimator.AnimatorUpdateListener mUpdateDimListener = 83 new ValueAnimator.AnimatorUpdateListener() { 84 @Override 85 public void onAnimationUpdate(ValueAnimator animation) { 86 setTaskProgress((Float) animation.getAnimatedValue()); 87 } 88 }; 89 90 91 public TaskView(Context context) { 92 this(context, null); 93 } 94 95 public TaskView(Context context, AttributeSet attrs) { 96 this(context, attrs, 0); 97 } 98 99 public TaskView(Context context, AttributeSet attrs, int defStyleAttr) { 100 this(context, attrs, defStyleAttr, 0); 101 } 102 103 public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 104 super(context, attrs, defStyleAttr, defStyleRes); 105 mConfig = RecentsConfiguration.getInstance(); 106 mMaxDimScale = mConfig.taskStackMaxDim / 255f; 107 mClipViewInStack = true; 108 mViewBounds = new AnimateableViewBounds(this, mConfig.taskViewRoundedCornerRadiusPx); 109 setOutlineProvider(mViewBounds); 110 setTaskProgress(getTaskProgress()); 111 setDim(getDim()); 112 } 113 114 /** Set callback */ 115 void setCallbacks(TaskViewCallbacks cb) { 116 mCb = cb; 117 } 118 119 /** Gets the task */ 120 Task getTask() { 121 return mTask; 122 } 123 124 /** Returns the view bounds. */ 125 AnimateableViewBounds getViewBounds() { 126 return mViewBounds; 127 } 128 129 @Override 130 protected void onFinishInflate() { 131 // Bind the views 132 mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar); 133 mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail); 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(), 140 mActionButtonView.getHeight()); 141 } 142 }); 143 if (mFooterView != null) { 144 mFooterView.setCallbacks(this); 145 } 146 } 147 148 @Override 149 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 150 int width = MeasureSpec.getSize(widthMeasureSpec); 151 int height = MeasureSpec.getSize(heightMeasureSpec); 152 153 // Measure the bar view, thumbnail, and footer 154 mHeaderView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 155 MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY)); 156 if (mFooterView != null) { 157 mFooterView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 158 MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight, 159 MeasureSpec.EXACTLY)); 160 } 161 mActionButtonView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), 162 MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); 163 if (mIsFullScreenView) { 164 // Measure the thumbnail height to be the full dimensions 165 mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 166 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 167 } else { 168 // Measure the thumbnail to be square 169 mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 170 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)); 171 } 172 setMeasuredDimension(width, height); 173 invalidateOutline(); 174 } 175 176 /** Synchronizes this view's properties with the task's transform */ 177 void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) { 178 // If we are a full screen view, then only update the Z to keep it in order 179 // XXX: Also update/animate the dim as well 180 if (mIsFullScreenView) { 181 if (Constants.DebugFlags.App.EnableShadows && 182 toTransform.hasTranslationZChangedFrom(getTranslationZ())) { 183 setTranslationZ(toTransform.translationZ); 184 } 185 return; 186 } 187 188 // Apply the transform 189 toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator, false); 190 191 // Update the task progress 192 if (mTaskProgressAnimator != null) { 193 mTaskProgressAnimator.removeAllListeners(); 194 mTaskProgressAnimator.cancel(); 195 } 196 if (duration <= 0) { 197 setTaskProgress(toTransform.p); 198 } else { 199 mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p); 200 mTaskProgressAnimator.setDuration(duration); 201 mTaskProgressAnimator.addUpdateListener(mUpdateDimListener); 202 mTaskProgressAnimator.start(); 203 } 204 } 205 206 /** Resets this view's properties */ 207 void resetViewProperties() { 208 setDim(0); 209 TaskViewTransform.reset(this); 210 } 211 212 /** 213 * When we are un/filtering, this method will set up the transform that we are animating to, 214 * in order to hide the task. 215 */ 216 void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) { 217 // Fade the view out and slide it away 218 toTransform.alpha = 0f; 219 toTransform.translationY += 200; 220 toTransform.translationZ = 0; 221 } 222 223 /** 224 * When we are un/filtering, this method will setup the transform that we are animating from, 225 * in order to show the task. 226 */ 227 void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) { 228 // Fade the view in 229 fromTransform.alpha = 0f; 230 } 231 232 /** Prepares this task view for the enter-recents animations. This is called earlier in the 233 * first layout because the actual animation into recents may take a long time. */ 234 void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, 235 boolean occludesLaunchTarget, int offscreenY) { 236 int initialDim = getDim(); 237 if (mConfig.launchedFromAppWithScreenshot) { 238 if (isTaskViewLaunchTargetTask) { 239 // Hide the footer during the transition in, and animate it out afterwards? 240 if (mFooterView != null) { 241 mFooterView.animateFooterVisibility(false, 0); 242 } 243 } else { 244 // Don't do anything for the side views when animating in 245 } 246 247 } else if (mConfig.launchedFromAppWithThumbnail) { 248 if (isTaskViewLaunchTargetTask) { 249 // Hide the action button if it exists 250 mActionButtonView.setAlpha(0f); 251 // Set the dim to 0 so we can animate it in 252 initialDim = 0; 253 } else if (occludesLaunchTarget) { 254 // Move the task view off screen (below) so we can animate it in 255 setTranslationY(offscreenY); 256 } 257 258 } else if (mConfig.launchedFromHome) { 259 // Move the task view off screen (below) so we can animate it in 260 setTranslationY(offscreenY); 261 if (Constants.DebugFlags.App.EnableShadows) { 262 setTranslationZ(0); 263 } 264 setScaleX(1f); 265 setScaleY(1f); 266 } 267 // Apply the current dim 268 setDim(initialDim); 269 // Prepare the thumbnail view alpha 270 mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask); 271 } 272 273 /** Animates this task view as it enters recents */ 274 void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) { 275 final TaskViewTransform transform = ctx.currentTaskTransform; 276 int startDelay = 0; 277 278 if (mConfig.launchedFromAppWithScreenshot) { 279 if (mTask.isLaunchTarget) { 280 Rect taskRect = ctx.currentTaskRect; 281 int duration = mConfig.taskViewEnterFromHomeDuration * 10; 282 int windowInsetTop = mConfig.systemInsets.top; // XXX: Should be for the window 283 float taskScale = ((float) taskRect.width() / getMeasuredWidth()) * transform.scale; 284 float scaledYOffset = ((1f - taskScale) * getMeasuredHeight()) / 2; 285 float scaledWindowInsetTop = (int) (taskScale * windowInsetTop); 286 float scaledTranslationY = taskRect.top + transform.translationY - 287 (scaledWindowInsetTop + scaledYOffset); 288 startDelay = mConfig.taskViewEnterFromHomeStaggerDelay; 289 290 // Animate the top clip 291 mViewBounds.animateClipTop(windowInsetTop, duration, 292 new ValueAnimator.AnimatorUpdateListener() { 293 @Override 294 public void onAnimationUpdate(ValueAnimator animation) { 295 int y = (Integer) animation.getAnimatedValue(); 296 mHeaderView.setTranslationY(y); 297 } 298 }); 299 // Animate the bottom or right clip 300 int size = Math.round((taskRect.width() / taskScale)); 301 if (mConfig.hasHorizontalLayout()) { 302 mViewBounds.animateClipRight(getMeasuredWidth() - size, duration); 303 } else { 304 mViewBounds.animateClipBottom(getMeasuredHeight() - (windowInsetTop + size), duration); 305 } 306 // Animate the task bar of the first task view 307 animate() 308 .scaleX(taskScale) 309 .scaleY(taskScale) 310 .translationY(scaledTranslationY) 311 .setDuration(duration) 312 .withEndAction(new Runnable() { 313 @Override 314 public void run() { 315 setIsFullScreen(false); 316 requestLayout(); 317 318 // Reset the clip 319 mViewBounds.setClipTop(0); 320 mViewBounds.setClipBottom(0); 321 mViewBounds.setClipRight(0); 322 // Reset the bar translation 323 mHeaderView.setTranslationY(0); 324 // Enable the thumbnail clip 325 mThumbnailView.enableTaskBarClip(mHeaderView); 326 // Animate the footer into view (if it is the front most task) 327 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration); 328 329 // Unbind the thumbnail from the screenshot 330 RecentsTaskLoader.getInstance().loadTaskData(mTask); 331 // Recycle the full screen screenshot 332 AlternateRecentsComponent.consumeLastScreenshot(); 333 334 mCb.onTaskViewFullScreenTransitionCompleted(); 335 336 // Decrement the post animation trigger 337 ctx.postAnimationTrigger.decrement(); 338 } 339 }) 340 .start(); 341 } else { 342 // Otherwise, just enable the thumbnail clip 343 mThumbnailView.enableTaskBarClip(mHeaderView); 344 345 // Animate the footer into view 346 animateFooterVisibility(true, 0); 347 } 348 ctx.postAnimationTrigger.increment(); 349 350 } else if (mConfig.launchedFromAppWithThumbnail) { 351 if (mTask.isLaunchTarget) { 352 // Enable the task bar clip 353 mThumbnailView.enableTaskBarClip(mHeaderView); 354 // Animate the dim/overlay 355 if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) { 356 // Animate the thumbnail alpha before the dim animation (to prevent updating the 357 // hardware layer) 358 mThumbnailView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, 359 new Runnable() { 360 @Override 361 public void run() { 362 animateDimToProgress(0, mConfig.taskBarEnterAnimDuration, 363 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 364 } 365 }); 366 } else { 367 // Immediately start the dim animation 368 animateDimToProgress(mConfig.taskBarEnterAnimDelay, 369 mConfig.taskBarEnterAnimDuration, 370 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 371 } 372 ctx.postAnimationTrigger.increment(); 373 374 // Animate the footer into view 375 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration); 376 377 // Animate the action button in 378 mActionButtonView.animate().alpha(1f) 379 .setStartDelay(mConfig.taskBarEnterAnimDelay) 380 .setDuration(mConfig.taskBarEnterAnimDuration) 381 .setInterpolator(mConfig.fastOutLinearInInterpolator) 382 .withLayer() 383 .start(); 384 } else { 385 // Enable the task bar clip 386 mThumbnailView.enableTaskBarClip(mHeaderView); 387 // Animate the task up if it was occluding the launch target 388 if (ctx.currentTaskOccludesLaunchTarget) { 389 setTranslationY(transform.translationY + mConfig.taskViewAffiliateGroupEnterOffsetPx); 390 setAlpha(0f); 391 animate().alpha(1f) 392 .translationY(transform.translationY) 393 .setStartDelay(mConfig.taskBarEnterAnimDelay) 394 .setUpdateListener(null) 395 .setInterpolator(mConfig.fastOutSlowInInterpolator) 396 .setDuration(mConfig.taskViewEnterFromHomeDuration) 397 .withEndAction(new Runnable() { 398 @Override 399 public void run() { 400 mThumbnailView.enableTaskBarClip(mHeaderView); 401 // Decrement the post animation trigger 402 ctx.postAnimationTrigger.decrement(); 403 } 404 }) 405 .start(); 406 ctx.postAnimationTrigger.increment(); 407 } 408 } 409 startDelay = mConfig.taskBarEnterAnimDelay; 410 411 } else if (mConfig.launchedFromHome) { 412 // Animate the tasks up 413 int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1); 414 int delay = mConfig.taskViewEnterFromHomeDelay + 415 frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay; 416 if (Constants.DebugFlags.App.EnableShadows) { 417 animate().translationZ(transform.translationZ); 418 } 419 animate() 420 .scaleX(transform.scale) 421 .scaleY(transform.scale) 422 .translationY(transform.translationY) 423 .setStartDelay(delay) 424 .setUpdateListener(null) 425 .setInterpolator(mConfig.quintOutInterpolator) 426 .setDuration(mConfig.taskViewEnterFromHomeDuration) 427 .withEndAction(new Runnable() { 428 @Override 429 public void run() { 430 mThumbnailView.enableTaskBarClip(mHeaderView); 431 // Decrement the post animation trigger 432 ctx.postAnimationTrigger.decrement(); 433 } 434 }) 435 .start(); 436 ctx.postAnimationTrigger.increment(); 437 438 // Animate the footer into view 439 animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration); 440 startDelay = delay; 441 442 } else { 443 // Otherwise, just enable the thumbnail clip 444 mThumbnailView.enableTaskBarClip(mHeaderView); 445 446 // Animate the footer into view 447 animateFooterVisibility(true, 0); 448 } 449 450 // Enable the focus animations from this point onwards so that they aren't affected by the 451 // window transitions 452 postDelayed(new Runnable() { 453 @Override 454 public void run() { 455 enableFocusAnimations(); 456 } 457 }, (startDelay / 2)); 458 } 459 460 /** Animates this task view as it leaves recents by pressing home. */ 461 void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 462 animate() 463 .translationY(ctx.offscreenTranslationY) 464 .setStartDelay(0) 465 .setUpdateListener(null) 466 .setInterpolator(mConfig.fastOutLinearInInterpolator) 467 .setDuration(mConfig.taskViewExitToHomeDuration) 468 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable()) 469 .start(); 470 ctx.postAnimationTrigger.increment(); 471 } 472 473 /** Animates this task view as it exits recents */ 474 void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask, 475 boolean occludesLaunchTarget, boolean lockToTask) { 476 if (isLaunchingTask) { 477 // Disable the thumbnail clip 478 mThumbnailView.disableTaskBarClip(); 479 // Animate the thumbnail alpha back into full opacity for the window animation out 480 mThumbnailView.startLaunchTaskAnimation(postAnimRunnable); 481 482 // Animate the dim 483 if (mDim > 0) { 484 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0); 485 anim.setDuration(mConfig.taskBarExitAnimDuration); 486 anim.setInterpolator(mConfig.fastOutLinearInInterpolator); 487 anim.start(); 488 } 489 490 // Animate the action button away 491 if (!lockToTask) { 492 float toScale = 0.9f; 493 mActionButtonView.animate() 494 .scaleX(toScale) 495 .scaleY(toScale); 496 } 497 mActionButtonView.animate() 498 .alpha(0f) 499 .setStartDelay(0) 500 .setDuration(mConfig.taskBarExitAnimDuration) 501 .setInterpolator(mConfig.fastOutLinearInInterpolator) 502 .withLayer() 503 .start(); 504 } else { 505 // Hide the dismiss button 506 mHeaderView.startLaunchTaskDismissAnimation(); 507 // If this is another view in the task grouping and is in front of the launch task, 508 // animate it away first 509 if (occludesLaunchTarget) { 510 animate().alpha(0f) 511 .translationY(getTranslationY() + mConfig.taskViewAffiliateGroupEnterOffsetPx) 512 .setStartDelay(0) 513 .setUpdateListener(null) 514 .setInterpolator(mConfig.fastOutLinearInInterpolator) 515 .setDuration(mConfig.taskBarExitAnimDuration) 516 .start(); 517 } 518 } 519 } 520 521 /** Animates the deletion of this task view */ 522 void startDeleteTaskAnimation(final Runnable r) { 523 // Disabling clipping with the stack while the view is animating away 524 setClipViewInStack(false); 525 526 animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx) 527 .alpha(0f) 528 .setStartDelay(0) 529 .setUpdateListener(null) 530 .setInterpolator(mConfig.fastOutSlowInInterpolator) 531 .setDuration(mConfig.taskViewRemoveAnimDuration) 532 .withEndAction(new Runnable() { 533 @Override 534 public void run() { 535 // We just throw this into a runnable because starting a view property 536 // animation using layers can cause inconsisten results if we try and 537 // update the layers while the animation is running. In some cases, 538 // the runnabled passed in may start an animation which also uses layers 539 // so we defer all this by posting this. 540 r.run(); 541 542 // Re-enable clipping with the stack (we will reuse this view) 543 setClipViewInStack(true); 544 } 545 }) 546 .start(); 547 } 548 549 /** Animates this task view if the user does not interact with the stack after a certain time. */ 550 void startNoUserInteractionAnimation() { 551 mHeaderView.startNoUserInteractionAnimation(); 552 } 553 554 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 555 void setNoUserInteractionState() { 556 mHeaderView.setNoUserInteractionState(); 557 } 558 559 /** Dismisses this task. */ 560 void dismissTask() { 561 // Animate out the view and call the callback 562 final TaskView tv = this; 563 startDeleteTaskAnimation(new Runnable() { 564 @Override 565 public void run() { 566 mCb.onTaskViewDismissed(tv); 567 } 568 }); 569 // Hide the footer 570 animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration); 571 } 572 573 /** Sets whether this task view is full screen or not. */ 574 void setIsFullScreen(boolean isFullscreen) { 575 mIsFullScreenView = isFullscreen; 576 mHeaderView.setIsFullscreen(isFullscreen); 577 if (isFullscreen) { 578 // If we are full screen, then disable the bottom outline clip for the footer 579 mViewBounds.setOutlineClipBottom(0); 580 } 581 } 582 583 /** Returns whether this task view should currently be drawn as a full screen view. */ 584 boolean isFullScreenView() { 585 return mIsFullScreenView; 586 } 587 588 /** 589 * Returns whether this view should be clipped, or any views below should clip against this 590 * view. 591 */ 592 boolean shouldClipViewInStack() { 593 return mClipViewInStack && !mIsFullScreenView && (getVisibility() == View.VISIBLE); 594 } 595 596 /** Sets whether this view should be clipped, or clipped against. */ 597 void setClipViewInStack(boolean clip) { 598 if (clip != mClipViewInStack) { 599 mClipViewInStack = clip; 600 mCb.onTaskViewClipStateChanged(this); 601 } 602 } 603 604 /** Gets the max footer height. */ 605 public int getMaxFooterHeight() { 606 if (mFooterView != null) { 607 return mFooterView.mMaxFooterHeight; 608 } else { 609 return 0; 610 } 611 } 612 613 /** Animates the footer into and out of view. */ 614 void animateFooterVisibility(boolean visible, int duration) { 615 // Hide the footer if we are a full screen view 616 if (mIsFullScreenView) return; 617 // Hide the footer if the current task can not be locked to 618 if (!mTask.lockToTaskEnabled || !mTask.lockToThisTask) return; 619 // Otherwise, animate the visibility 620 if (mFooterView != null) { 621 mFooterView.animateFooterVisibility(visible, duration); 622 } 623 } 624 625 /** Sets the current task progress. */ 626 public void setTaskProgress(float p) { 627 mTaskProgress = p; 628 mViewBounds.setAlpha(p); 629 updateDimFromTaskProgress(); 630 } 631 632 /** Returns the current task progress. */ 633 public float getTaskProgress() { 634 return mTaskProgress; 635 } 636 637 /** Returns the current dim. */ 638 public void setDim(int dim) { 639 mDim = dim; 640 if (mDimAnimator != null) { 641 mDimAnimator.removeAllListeners(); 642 mDimAnimator.cancel(); 643 } 644 if (mConfig.useHardwareLayers) { 645 // Defer setting hardware layers if we have not yet measured, or there is no dim to draw 646 if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) { 647 if (mDimAnimator != null) { 648 mDimAnimator.removeAllListeners(); 649 mDimAnimator.cancel(); 650 } 651 652 int inverse = 255 - mDim; 653 mDimColorFilter.setColor(Color.argb(0xFF, inverse, inverse, inverse)); 654 mLayerPaint.setColorFilter(mDimColorFilter); 655 setLayerType(LAYER_TYPE_HARDWARE, mLayerPaint); 656 } 657 } else { 658 float dimAlpha = mDim / 255.0f; 659 if (mThumbnailView != null) { 660 mThumbnailView.setDimAlpha(dimAlpha); 661 } 662 if (mHeaderView != null) { 663 mHeaderView.setDimAlpha(dim); 664 } 665 } 666 } 667 668 /** Returns the current dim. */ 669 public int getDim() { 670 return mDim; 671 } 672 673 /** Animates the dim to the task progress. */ 674 void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) { 675 // Animate the dim into view as well 676 int toDim = getDimFromTaskProgress(); 677 if (toDim != getDim()) { 678 ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim); 679 anim.setStartDelay(delay); 680 anim.setDuration(duration); 681 if (postAnimRunnable != null) { 682 anim.addListener(postAnimRunnable); 683 } 684 anim.start(); 685 } 686 } 687 688 /** Compute the dim as a function of the scale of this view. */ 689 int getDimFromTaskProgress() { 690 float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress); 691 return (int) (dim * 255); 692 } 693 694 /** Update the dim as a function of the scale of this view. */ 695 void updateDimFromTaskProgress() { 696 setDim(getDimFromTaskProgress()); 697 } 698 699 /**** View focus state ****/ 700 701 /** 702 * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen 703 * if the view is not currently visible, or we are in touch state (where we still want to keep 704 * track of focus). 705 */ 706 public void setFocusedTask() { 707 mIsFocused = true; 708 if (mFocusAnimationsEnabled) { 709 // Focus the header bar 710 mHeaderView.onTaskViewFocusChanged(true); 711 } 712 // Update the thumbnail alpha with the focus 713 mThumbnailView.onFocusChanged(true); 714 // Call the callback 715 mCb.onTaskViewFocusChanged(this, true); 716 // Workaround, we don't always want it focusable in touch mode, but we want the first task 717 // to be focused after the enter-recents animation, which can be triggered from either touch 718 // or keyboard 719 setFocusableInTouchMode(true); 720 requestFocus(); 721 setFocusableInTouchMode(false); 722 invalidate(); 723 } 724 725 /** 726 * Unsets the focused task explicitly. 727 */ 728 void unsetFocusedTask() { 729 mIsFocused = false; 730 if (mFocusAnimationsEnabled) { 731 // Un-focus the header bar 732 mHeaderView.onTaskViewFocusChanged(false); 733 } 734 735 // Update the thumbnail alpha with the focus 736 mThumbnailView.onFocusChanged(false); 737 // Call the callback 738 mCb.onTaskViewFocusChanged(this, false); 739 invalidate(); 740 } 741 742 /** 743 * Updates the explicitly focused state when the view focus changes. 744 */ 745 @Override 746 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 747 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 748 if (!gainFocus) { 749 unsetFocusedTask(); 750 } 751 } 752 753 /** 754 * Returns whether we have explicitly been focused. 755 */ 756 public boolean isFocusedTask() { 757 return mIsFocused || isFocused(); 758 } 759 760 /** Enables all focus animations. */ 761 void enableFocusAnimations() { 762 boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled; 763 mFocusAnimationsEnabled = true; 764 if (mIsFocused && !wasFocusAnimationsEnabled) { 765 // Re-notify the header if we were focused and animations were not previously enabled 766 mHeaderView.onTaskViewFocusChanged(true); 767 } 768 } 769 770 /**** TaskCallbacks Implementation ****/ 771 772 /** Binds this task view to the task */ 773 public void onTaskBound(Task t) { 774 mTask = t; 775 mTask.setCallbacks(this); 776 if (getMeasuredWidth() == 0) { 777 // If we haven't yet measured, we should just set the footer height with any animation 778 animateFooterVisibility(t.lockToThisTask, 0); 779 } else { 780 animateFooterVisibility(t.lockToThisTask, mConfig.taskViewLockToAppLongAnimDuration); 781 } 782 // Hide the action button if lock to app is disabled 783 if (!t.lockToTaskEnabled && mActionButtonView.getVisibility() != View.GONE) { 784 mActionButtonView.setVisibility(View.GONE); 785 } 786 } 787 788 @Override 789 public void onTaskDataLoaded() { 790 if (mThumbnailView != null && mHeaderView != null) { 791 // Bind each of the views to the new task data 792 if (mIsFullScreenView) { 793 mThumbnailView.bindToScreenshot(AlternateRecentsComponent.getLastScreenshot()); 794 } else { 795 mThumbnailView.rebindToTask(mTask); 796 } 797 mHeaderView.rebindToTask(mTask); 798 // Rebind any listeners 799 mHeaderView.mApplicationIcon.setOnClickListener(this); 800 mHeaderView.mDismissButton.setOnClickListener(this); 801 if (mFooterView != null) { 802 mFooterView.setOnClickListener(this); 803 } 804 mActionButtonView.setOnClickListener(this); 805 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 806 if (mConfig.developerOptionsEnabled) { 807 mHeaderView.mApplicationIcon.setOnLongClickListener(this); 808 } 809 } 810 } 811 mTaskDataLoaded = true; 812 } 813 814 @Override 815 public void onTaskDataUnloaded() { 816 if (mThumbnailView != null && mHeaderView != null) { 817 // Unbind each of the views from the task data and remove the task callback 818 mTask.setCallbacks(null); 819 mThumbnailView.unbindFromTask(); 820 mHeaderView.unbindFromTask(); 821 // Unbind any listeners 822 mHeaderView.mApplicationIcon.setOnClickListener(null); 823 mHeaderView.mDismissButton.setOnClickListener(null); 824 if (mFooterView != null) { 825 mFooterView.setOnClickListener(null); 826 } 827 mActionButtonView.setOnClickListener(null); 828 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 829 mHeaderView.mApplicationIcon.setOnLongClickListener(null); 830 } 831 } 832 mTaskDataLoaded = false; 833 } 834 835 /** Enables/disables handling touch on this task view. */ 836 void setTouchEnabled(boolean enabled) { 837 setOnClickListener(enabled ? this : null); 838 } 839 840 /**** TaskViewFooter.TaskFooterViewCallbacks ****/ 841 842 @Override 843 public void onTaskFooterHeightChanged(int height, int maxHeight) { 844 if (mIsFullScreenView) { 845 // Disable the bottom outline clip when fullscreen 846 mViewBounds.setOutlineClipBottom(0); 847 } else { 848 // Update the bottom clip in our outline provider 849 mViewBounds.setOutlineClipBottom(maxHeight - height); 850 } 851 } 852 853 /**** View.OnClickListener Implementation ****/ 854 855 @Override 856 public void onClick(final View v) { 857 final TaskView tv = this; 858 final boolean delayViewClick = (v != this) && (v != mActionButtonView); 859 if (delayViewClick) { 860 // We purposely post the handler delayed to allow for the touch feedback to draw 861 postDelayed(new Runnable() { 862 @Override 863 public void run() { 864 if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) { 865 mCb.onTaskViewAppIconClicked(tv); 866 } else if (v == mHeaderView.mDismissButton) { 867 dismissTask(); 868 } 869 } 870 }, 125); 871 } else { 872 if (v == mActionButtonView) { 873 // Reset the translation of the action button before we animate it out 874 mActionButtonView.setTranslationZ(0f); 875 } 876 mCb.onTaskViewClicked(tv, tv.getTask(), 877 (v == mFooterView || v == mActionButtonView)); 878 } 879 } 880 881 /**** View.OnLongClickListener Implementation ****/ 882 883 @Override 884 public boolean onLongClick(View v) { 885 if (v == mHeaderView.mApplicationIcon) { 886 mCb.onTaskViewAppInfoClicked(this); 887 return true; 888 } 889 return false; 890 } 891} 892