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