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