TaskView.java revision a0e88b5013d708ac6ed6518817d83c64c87ae4b1
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.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.animation.ValueAnimator; 23import android.content.Context; 24import android.graphics.Color; 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.animation.AccelerateInterpolator; 32import android.widget.FrameLayout; 33import com.android.systemui.R; 34import com.android.systemui.recents.AlternateRecentsComponent; 35import com.android.systemui.recents.Constants; 36import com.android.systemui.recents.RecentsConfiguration; 37import com.android.systemui.recents.model.RecentsTaskLoader; 38import com.android.systemui.recents.model.Task; 39 40/* A task view */ 41public class TaskView extends FrameLayout implements Task.TaskCallbacks, 42 TaskViewFooter.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener { 43 /** The TaskView callbacks */ 44 interface TaskViewCallbacks { 45 public void onTaskViewAppIconClicked(TaskView tv); 46 public void onTaskViewAppInfoClicked(TaskView tv); 47 public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask); 48 public void onTaskViewDismissed(TaskView tv); 49 public void onTaskViewClipStateChanged(TaskView tv); 50 public void onTaskViewFullScreenTransitionCompleted(); 51 public void onTaskViewFocusChanged(TaskView tv, boolean focused); 52 } 53 54 RecentsConfiguration mConfig; 55 56 float mTaskProgress; 57 ObjectAnimator mTaskProgressAnimator; 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 if (mConfig.launchedFromAppWithScreenshot) { 226 if (isTaskViewLaunchTargetTask) { 227 mHeaderView.prepareEnterRecentsAnimation(); 228 // Hide the footer during the transition in, and animate it out afterwards? 229 if (mFooterView != null) { 230 mFooterView.animateFooterVisibility(false, 0); 231 } 232 } else { 233 // Don't do anything for the side views when animating in 234 } 235 236 } else if (mConfig.launchedFromAppWithThumbnail) { 237 if (isTaskViewLaunchTargetTask) { 238 // Hide the front most task bar view so we can animate it in 239 mHeaderView.prepareEnterRecentsAnimation(); 240 // Hide the action button if it exists 241 mActionButtonView.setAlpha(0f); 242 // Set the dim to 0 so we can animate it in 243 setDim(0); 244 } else if (occludesLaunchTarget) { 245 // Move the task view off screen (below) so we can animate it in 246 setTranslationY(offscreenY); 247 } 248 249 } else if (mConfig.launchedFromHome) { 250 // Move the task view off screen (below) so we can animate it in 251 setTranslationY(offscreenY); 252 if (Constants.DebugFlags.App.EnableShadows) { 253 setTranslationZ(0); 254 } 255 setScaleX(1f); 256 setScaleY(1f); 257 } 258 } 259 260 /** Animates this task view as it enters recents */ 261 void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) { 262 final TaskViewTransform transform = ctx.currentTaskTransform; 263 int startDelay = 0; 264 265 if (mConfig.launchedFromAppWithScreenshot) { 266 if (mTask.isLaunchTarget) { 267 Rect taskRect = ctx.currentTaskRect; 268 int duration = mConfig.taskViewEnterFromHomeDuration * 10; 269 int windowInsetTop = mConfig.systemInsets.top; // XXX: Should be for the window 270 float taskScale = ((float) taskRect.width() / getMeasuredWidth()) * transform.scale; 271 float scaledYOffset = ((1f - taskScale) * getMeasuredHeight()) / 2; 272 float scaledWindowInsetTop = (int) (taskScale * windowInsetTop); 273 float scaledTranslationY = taskRect.top + transform.translationY - 274 (scaledWindowInsetTop + scaledYOffset); 275 startDelay = mConfig.taskViewEnterFromHomeDelay; 276 277 // Animate the top clip 278 mViewBounds.animateClipTop(windowInsetTop, duration, 279 new ValueAnimator.AnimatorUpdateListener() { 280 @Override 281 public void onAnimationUpdate(ValueAnimator animation) { 282 int y = (Integer) animation.getAnimatedValue(); 283 mHeaderView.setTranslationY(y); 284 } 285 }); 286 // Animate the bottom or right clip 287 int size = Math.round((taskRect.width() / taskScale)); 288 if (mConfig.hasHorizontalLayout()) { 289 mViewBounds.animateClipRight(getMeasuredWidth() - size, duration); 290 } else { 291 mViewBounds.animateClipBottom(getMeasuredHeight() - (windowInsetTop + size), duration); 292 } 293 // Animate the task bar of the first task view 294 mHeaderView.startEnterRecentsAnimation(0, null); 295 animate() 296 .scaleX(taskScale) 297 .scaleY(taskScale) 298 .translationY(scaledTranslationY) 299 .setDuration(duration) 300 .withEndAction(new Runnable() { 301 @Override 302 public void run() { 303 setIsFullScreen(false); 304 requestLayout(); 305 306 // Reset the clip 307 mViewBounds.setClipTop(0); 308 mViewBounds.setClipBottom(0); 309 mViewBounds.setClipRight(0); 310 // Reset the bar translation 311 mHeaderView.setTranslationY(0); 312 // Enable the thumbnail clip 313 mThumbnailView.enableTaskBarClip(mHeaderView); 314 // Animate the footer into view (if it is the front most task) 315 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration); 316 317 // Unbind the thumbnail from the screenshot 318 RecentsTaskLoader.getInstance().loadTaskData(mTask); 319 // Recycle the full screen screenshot 320 AlternateRecentsComponent.consumeLastScreenshot(); 321 322 mCb.onTaskViewFullScreenTransitionCompleted(); 323 324 // Decrement the post animation trigger 325 ctx.postAnimationTrigger.decrement(); 326 } 327 }) 328 .start(); 329 } else { 330 // Otherwise, just enable the thumbnail clip 331 mThumbnailView.enableTaskBarClip(mHeaderView); 332 333 // Animate the footer into view 334 animateFooterVisibility(true, 0); 335 } 336 ctx.postAnimationTrigger.increment(); 337 338 } else if (mConfig.launchedFromAppWithThumbnail) { 339 if (mTask.isLaunchTarget) { 340 // Animate the task bar of the first task view 341 mHeaderView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, 342 mThumbnailView.enableTaskBarClipAsRunnable(mHeaderView)); 343 344 // Animate the dim into view as well 345 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", getDimFromTaskProgress()); 346 anim.setStartDelay(mConfig.taskBarEnterAnimDelay); 347 anim.setDuration(mConfig.taskBarEnterAnimDuration); 348 anim.setInterpolator(mConfig.fastOutLinearInInterpolator); 349 anim.addListener(new AnimatorListenerAdapter() { 350 @Override 351 public void onAnimationEnd(Animator animation) { 352 // Decrement the post animation trigger 353 ctx.postAnimationTrigger.decrement(); 354 } 355 }); 356 anim.start(); 357 ctx.postAnimationTrigger.increment(); 358 359 // Animate the footer into view 360 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration); 361 362 // Animate the action button in 363 mActionButtonView.animate().alpha(1f) 364 .setStartDelay(mConfig.taskBarEnterAnimDelay) 365 .setDuration(mConfig.taskBarEnterAnimDuration) 366 .setInterpolator(mConfig.fastOutLinearInInterpolator) 367 .withLayer() 368 .start(); 369 } else { 370 // Enable the task bar clip 371 mThumbnailView.enableTaskBarClip(mHeaderView); 372 // Animate the task up if it was occluding the launch target 373 if (ctx.currentTaskOccludesLaunchTarget) { 374 setTranslationY(transform.translationY + mConfig.taskViewAffiliateGroupEnterOffsetPx); 375 setAlpha(0f); 376 animate().alpha(1f) 377 .translationY(transform.translationY) 378 .setStartDelay(mConfig.taskBarEnterAnimDelay) 379 .setUpdateListener(null) 380 .setInterpolator(mConfig.fastOutSlowInInterpolator) 381 .setDuration(mConfig.taskViewEnterFromHomeDuration) 382 .withEndAction(new Runnable() { 383 @Override 384 public void run() { 385 mThumbnailView.enableTaskBarClip(mHeaderView); 386 // Decrement the post animation trigger 387 ctx.postAnimationTrigger.decrement(); 388 } 389 }) 390 .start(); 391 ctx.postAnimationTrigger.increment(); 392 } 393 } 394 startDelay = mConfig.taskBarEnterAnimDelay; 395 396 } else if (mConfig.launchedFromHome) { 397 // Animate the tasks up 398 int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1); 399 int delay = mConfig.taskBarEnterAnimDelay + 400 frontIndex * mConfig.taskViewEnterFromHomeDelay; 401 if (Constants.DebugFlags.App.EnableShadows) { 402 animate().translationZ(transform.translationZ); 403 } 404 animate() 405 .scaleX(transform.scale) 406 .scaleY(transform.scale) 407 .translationY(transform.translationY) 408 .setStartDelay(delay) 409 .setUpdateListener(null) 410 .setInterpolator(mConfig.quintOutInterpolator) 411 .setDuration(mConfig.taskViewEnterFromHomeDuration) 412 .withEndAction(new Runnable() { 413 @Override 414 public void run() { 415 mThumbnailView.enableTaskBarClip(mHeaderView); 416 // Decrement the post animation trigger 417 ctx.postAnimationTrigger.decrement(); 418 } 419 }) 420 .start(); 421 ctx.postAnimationTrigger.increment(); 422 423 // Animate the footer into view 424 animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration); 425 startDelay = delay; 426 427 } else { 428 // Otherwise, just enable the thumbnail clip 429 mThumbnailView.enableTaskBarClip(mHeaderView); 430 431 // Animate the footer into view 432 animateFooterVisibility(true, 0); 433 } 434 435 // Enable the focus animations from this point onwards so that they aren't affected by the 436 // window transitions 437 postDelayed(new Runnable() { 438 @Override 439 public void run() { 440 enableFocusAnimations(); 441 } 442 }, (startDelay / 2)); 443 } 444 445 /** Animates this task view as it leaves recents by pressing home. */ 446 void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 447 animate() 448 .translationY(ctx.offscreenTranslationY) 449 .setStartDelay(0) 450 .setUpdateListener(null) 451 .setInterpolator(mConfig.fastOutLinearInInterpolator) 452 .setDuration(mConfig.taskViewExitToHomeDuration) 453 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable()) 454 .start(); 455 ctx.postAnimationTrigger.increment(); 456 } 457 458 /** Animates this task view as it exits recents */ 459 void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask, 460 boolean occludesLaunchTarget) { 461 if (isLaunchingTask) { 462 // Disable the thumbnail clip and animate the bar out 463 mHeaderView.startLaunchTaskAnimation(mThumbnailView.disableTaskBarClipAsRunnable(), r); 464 465 // Animate the dim 466 if (mDim > 0) { 467 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0); 468 anim.setDuration(mConfig.taskBarExitAnimDuration); 469 anim.setInterpolator(mConfig.fastOutLinearInInterpolator); 470 anim.start(); 471 } 472 473 // Animate the action button away 474 mActionButtonView.animate().alpha(0f) 475 .setStartDelay(0) 476 .setDuration(mConfig.taskBarExitAnimDuration) 477 .setInterpolator(mConfig.fastOutLinearInInterpolator) 478 .withLayer() 479 .start(); 480 } else { 481 // Hide the dismiss button 482 mHeaderView.startLaunchTaskDismissAnimation(); 483 // If this is another view in the task grouping and is in front of the launch task, 484 // animate it away first 485 if (occludesLaunchTarget) { 486 animate().alpha(0f) 487 .translationY(getTranslationY() + mConfig.taskViewAffiliateGroupEnterOffsetPx) 488 .setStartDelay(0) 489 .setUpdateListener(null) 490 .setInterpolator(mConfig.fastOutLinearInInterpolator) 491 .setDuration(mConfig.taskBarExitAnimDuration) 492 .start(); 493 } 494 } 495 } 496 497 /** Animates the deletion of this task view */ 498 void startDeleteTaskAnimation(final Runnable r) { 499 // Disabling clipping with the stack while the view is animating away 500 setClipViewInStack(false); 501 502 animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx) 503 .alpha(0f) 504 .setStartDelay(0) 505 .setUpdateListener(null) 506 .setInterpolator(mConfig.fastOutSlowInInterpolator) 507 .setDuration(mConfig.taskViewRemoveAnimDuration) 508 .withEndAction(new Runnable() { 509 @Override 510 public void run() { 511 // We just throw this into a runnable because starting a view property 512 // animation using layers can cause inconsisten results if we try and 513 // update the layers while the animation is running. In some cases, 514 // the runnabled passed in may start an animation which also uses layers 515 // so we defer all this by posting this. 516 r.run(); 517 518 // Re-enable clipping with the stack (we will reuse this view) 519 setClipViewInStack(true); 520 } 521 }) 522 .start(); 523 } 524 525 /** Animates this task view if the user does not interact with the stack after a certain time. */ 526 void startNoUserInteractionAnimation() { 527 mHeaderView.startNoUserInteractionAnimation(); 528 } 529 530 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 531 void setNoUserInteractionState() { 532 mHeaderView.setNoUserInteractionState(); 533 } 534 535 /** Dismisses this task. */ 536 void dismissTask() { 537 // Animate out the view and call the callback 538 final TaskView tv = this; 539 startDeleteTaskAnimation(new Runnable() { 540 @Override 541 public void run() { 542 mCb.onTaskViewDismissed(tv); 543 } 544 }); 545 // Hide the footer 546 animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration); 547 } 548 549 /** Sets whether this task view is full screen or not. */ 550 void setIsFullScreen(boolean isFullscreen) { 551 mIsFullScreenView = isFullscreen; 552 mHeaderView.setIsFullscreen(isFullscreen); 553 if (isFullscreen) { 554 // If we are full screen, then disable the bottom outline clip for the footer 555 mViewBounds.setOutlineClipBottom(0); 556 } 557 } 558 559 /** Returns whether this task view should currently be drawn as a full screen view. */ 560 boolean isFullScreenView() { 561 return mIsFullScreenView; 562 } 563 564 /** 565 * Returns whether this view should be clipped, or any views below should clip against this 566 * view. 567 */ 568 boolean shouldClipViewInStack() { 569 return mClipViewInStack && !mIsFullScreenView && (getVisibility() == View.VISIBLE); 570 } 571 572 /** Sets whether this view should be clipped, or clipped against. */ 573 void setClipViewInStack(boolean clip) { 574 if (clip != mClipViewInStack) { 575 mClipViewInStack = clip; 576 mCb.onTaskViewClipStateChanged(this); 577 } 578 } 579 580 /** Gets the max footer height. */ 581 public int getMaxFooterHeight() { 582 if (mFooterView != null) { 583 return mFooterView.mMaxFooterHeight; 584 } else { 585 return 0; 586 } 587 } 588 589 /** Animates the footer into and out of view. */ 590 void animateFooterVisibility(boolean visible, int duration) { 591 // Hide the footer if we are a full screen view 592 if (mIsFullScreenView) return; 593 // Hide the footer if the current task can not be locked to 594 if (!mTask.lockToTaskEnabled || !mTask.lockToThisTask) return; 595 // Otherwise, animate the visibility 596 if (mFooterView != null) { 597 mFooterView.animateFooterVisibility(visible, duration); 598 } 599 } 600 601 /** Sets the current task progress. */ 602 public void setTaskProgress(float p) { 603 mTaskProgress = p; 604 updateDimFromTaskProgress(); 605 } 606 607 /** Returns the current task progress. */ 608 public float getTaskProgress() { 609 return mTaskProgress; 610 } 611 612 /** Returns the current dim. */ 613 public void setDim(int dim) { 614 mDim = dim; 615 int inverse = 255 - mDim; 616 mDimColorFilter.setColor(Color.argb(0xFF, inverse, inverse, inverse)); 617 mLayerPaint.setColorFilter(mDimColorFilter); 618 setLayerType(LAYER_TYPE_HARDWARE, mLayerPaint); 619 } 620 621 /** Returns the current dim. */ 622 public int getDim() { 623 return mDim; 624 } 625 626 /** Compute the dim as a function of the scale of this view. */ 627 int getDimFromTaskProgress() { 628 float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress); 629 return (int) (dim * 255); 630 } 631 632 /** Update the dim as a function of the scale of this view. */ 633 void updateDimFromTaskProgress() { 634 setDim(getDimFromTaskProgress()); 635 } 636 637 /**** View focus state ****/ 638 639 /** 640 * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen 641 * if the view is not currently visible, or we are in touch state (where we still want to keep 642 * track of focus). 643 */ 644 public void setFocusedTask() { 645 mIsFocused = true; 646 if (mFocusAnimationsEnabled) { 647 // Focus the header bar 648 mHeaderView.onTaskViewFocusChanged(true); 649 } 650 // Call the callback 651 mCb.onTaskViewFocusChanged(this, true); 652 // Workaround, we don't always want it focusable in touch mode, but we want the first task 653 // to be focused after the enter-recents animation, which can be triggered from either touch 654 // or keyboard 655 setFocusableInTouchMode(true); 656 requestFocus(); 657 setFocusableInTouchMode(false); 658 invalidate(); 659 } 660 661 /** 662 * Updates the explicitly focused state when the view focus changes. 663 */ 664 @Override 665 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 666 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 667 if (!gainFocus) { 668 mIsFocused = false; 669 if (mFocusAnimationsEnabled) { 670 // Un-focus the header bar 671 mHeaderView.onTaskViewFocusChanged(false); 672 } 673 // Call the callback 674 mCb.onTaskViewFocusChanged(this, false); 675 invalidate(); 676 } 677 } 678 679 /** 680 * Returns whether we have explicitly been focused. 681 */ 682 public boolean isFocusedTask() { 683 return mIsFocused || isFocused(); 684 } 685 686 /** Enables all focus animations. */ 687 void enableFocusAnimations() { 688 boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled; 689 mFocusAnimationsEnabled = true; 690 if (mIsFocused && !wasFocusAnimationsEnabled) { 691 // Re-notify the header if we were focused and animations were not previously enabled 692 mHeaderView.onTaskViewFocusChanged(true); 693 } 694 } 695 696 /**** TaskCallbacks Implementation ****/ 697 698 /** Binds this task view to the task */ 699 public void onTaskBound(Task t) { 700 mTask = t; 701 mTask.setCallbacks(this); 702 if (getMeasuredWidth() == 0) { 703 // If we haven't yet measured, we should just set the footer height with any animation 704 animateFooterVisibility(t.lockToThisTask, 0); 705 } else { 706 animateFooterVisibility(t.lockToThisTask, mConfig.taskViewLockToAppLongAnimDuration); 707 } 708 // Hide the action button if lock to app is disabled 709 if (!t.lockToTaskEnabled && mActionButtonView.getVisibility() != View.GONE) { 710 mActionButtonView.setVisibility(View.GONE); 711 } 712 } 713 714 @Override 715 public void onTaskDataLoaded() { 716 if (mThumbnailView != null && mHeaderView != null) { 717 // Bind each of the views to the new task data 718 if (mIsFullScreenView) { 719 mThumbnailView.bindToScreenshot(AlternateRecentsComponent.getLastScreenshot()); 720 } else { 721 mThumbnailView.rebindToTask(mTask); 722 } 723 mHeaderView.rebindToTask(mTask); 724 // Rebind any listeners 725 mHeaderView.mApplicationIcon.setOnClickListener(this); 726 mHeaderView.mDismissButton.setOnClickListener(this); 727 if (mFooterView != null) { 728 mFooterView.setOnClickListener(this); 729 } 730 mActionButtonView.setOnClickListener(this); 731 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 732 if (mConfig.developerOptionsEnabled) { 733 mHeaderView.mApplicationIcon.setOnLongClickListener(this); 734 } 735 } 736 } 737 mTaskDataLoaded = true; 738 } 739 740 @Override 741 public void onTaskDataUnloaded() { 742 if (mThumbnailView != null && mHeaderView != null) { 743 // Unbind each of the views from the task data and remove the task callback 744 mTask.setCallbacks(null); 745 mThumbnailView.unbindFromTask(); 746 mHeaderView.unbindFromTask(); 747 // Unbind any listeners 748 mHeaderView.mApplicationIcon.setOnClickListener(null); 749 mHeaderView.mDismissButton.setOnClickListener(null); 750 if (mFooterView != null) { 751 mFooterView.setOnClickListener(null); 752 } 753 mActionButtonView.setOnClickListener(null); 754 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 755 mHeaderView.mApplicationIcon.setOnLongClickListener(null); 756 } 757 } 758 mTaskDataLoaded = false; 759 } 760 761 /** Enables/disables handling touch on this task view. */ 762 void setTouchEnabled(boolean enabled) { 763 setOnClickListener(enabled ? this : null); 764 } 765 766 /**** TaskViewFooter.TaskFooterViewCallbacks ****/ 767 768 @Override 769 public void onTaskFooterHeightChanged(int height, int maxHeight) { 770 if (mIsFullScreenView) { 771 // Disable the bottom outline clip when fullscreen 772 mViewBounds.setOutlineClipBottom(0); 773 } else { 774 // Update the bottom clip in our outline provider 775 mViewBounds.setOutlineClipBottom(maxHeight - height); 776 } 777 } 778 779 /**** View.OnClickListener Implementation ****/ 780 781 @Override 782 public void onClick(final View v) { 783 // We purposely post the handler delayed to allow for the touch feedback to draw 784 final TaskView tv = this; 785 postDelayed(new Runnable() { 786 @Override 787 public void run() { 788 if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) { 789 mCb.onTaskViewAppIconClicked(tv); 790 } else if (v == mHeaderView.mDismissButton) { 791 dismissTask(); 792 } else { 793 mCb.onTaskViewClicked(tv, tv.getTask(), 794 (v == mFooterView || v == mActionButtonView)); 795 } 796 } 797 }, 125); 798 } 799 800 /**** View.OnLongClickListener Implementation ****/ 801 802 @Override 803 public boolean onLongClick(View v) { 804 if (v == mHeaderView.mApplicationIcon) { 805 mCb.onTaskViewAppInfoClicked(this); 806 return true; 807 } 808 return false; 809 } 810} 811