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