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