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