TaskView.java revision 1f24c7e37bc794057a156a730c7e4b53b01212ed
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.Canvas; 25import android.graphics.Outline; 26import android.graphics.Paint; 27import android.graphics.Rect; 28import android.util.AttributeSet; 29import android.view.View; 30import android.view.ViewPropertyAnimator; 31import android.view.animation.AccelerateInterpolator; 32import android.widget.FrameLayout; 33import com.android.systemui.R; 34import com.android.systemui.recents.misc.Console; 35import com.android.systemui.recents.Constants; 36import com.android.systemui.recents.RecentsConfiguration; 37import com.android.systemui.recents.model.Task; 38import com.android.systemui.recents.model.TaskStack; 39 40 41/* A task view */ 42public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.OnClickListener, 43 View.OnLongClickListener { 44 /** The TaskView callbacks */ 45 interface TaskViewCallbacks { 46 public void onTaskViewAppIconClicked(TaskView tv); 47 public void onTaskViewAppInfoClicked(TaskView tv); 48 public void onTaskViewClicked(TaskView tv, Task t, boolean lockToTask); 49 public void onTaskViewDismissed(TaskView tv); 50 } 51 52 RecentsConfiguration mConfig; 53 54 int mFooterHeight; 55 int mMaxFooterHeight; 56 ObjectAnimator mFooterAnimator; 57 58 int mDim; 59 int mMaxDim; 60 AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(); 61 62 Task mTask; 63 boolean mTaskDataLoaded; 64 boolean mIsFocused; 65 boolean mIsStub; 66 boolean mClipViewInStack; 67 Rect mTmpRect = new Rect(); 68 Paint mLayerPaint = new Paint(); 69 Outline mOutline = new Outline(); 70 71 TaskThumbnailView mThumbnailView; 72 TaskBarView mBarView; 73 View mLockToAppButtonView; 74 TaskViewCallbacks mCb; 75 76 // Optimizations 77 ValueAnimator.AnimatorUpdateListener mUpdateDimListener = 78 new ValueAnimator.AnimatorUpdateListener() { 79 @Override 80 public void onAnimationUpdate(ValueAnimator animation) { 81 updateDimOverlayFromScale(); 82 } 83 }; 84 Runnable mEnableThumbnailClip = new Runnable() { 85 @Override 86 public void run() { 87 mThumbnailView.updateTaskBarClip(mBarView); 88 } 89 }; 90 Runnable mDisableThumbnailClip = new Runnable() { 91 @Override 92 public void run() { 93 mThumbnailView.disableClipTaskBarView(); 94 } 95 }; 96 97 98 public TaskView(Context context) { 99 this(context, null); 100 } 101 102 public TaskView(Context context, AttributeSet attrs) { 103 this(context, attrs, 0); 104 } 105 106 public TaskView(Context context, AttributeSet attrs, int defStyleAttr) { 107 this(context, attrs, defStyleAttr, 0); 108 } 109 110 public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 111 super(context, attrs, defStyleAttr, defStyleRes); 112 mConfig = RecentsConfiguration.getInstance(); 113 mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight; 114 setWillNotDraw(false); 115 setClipToOutline(true); 116 setDim(getDim()); 117 setFooterHeight(getFooterHeight()); 118 } 119 120 @Override 121 protected void onFinishInflate() { 122 mMaxDim = mConfig.taskStackMaxDim; 123 124 // By default, all views are clipped to other views in their stack 125 mClipViewInStack = true; 126 127 // Bind the views 128 mBarView = (TaskBarView) findViewById(R.id.task_view_bar); 129 mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail); 130 mLockToAppButtonView = findViewById(R.id.lock_to_app); 131 132 if (mTaskDataLoaded) { 133 onTaskDataLoaded(); 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 lock-to-app buttons 143 mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 144 MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY)); 145 mLockToAppButtonView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 146 MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight, 147 MeasureSpec.EXACTLY)); 148 // Measure the thumbnail height to be the same as the width 149 mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 150 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)); 151 setMeasuredDimension(width, height); 152 updateOutline(); 153 } 154 155 /** Updates the outline to match whether the lock-to-app button is visible or not. */ 156 void updateOutline() { 157 int height = getMeasuredHeight(); 158 if (height == 0) return; 159 160 // Account for the current footer height 161 height = height - mMaxFooterHeight + mFooterHeight; 162 163 mOutline.setRoundRect(0, 0, getMeasuredWidth(), height, 164 mConfig.taskViewRoundedCornerRadiusPx); 165 setOutline(mOutline); 166 } 167 168 /** Set callback */ 169 void setCallbacks(TaskViewCallbacks cb) { 170 mCb = cb; 171 } 172 173 /** Gets the task */ 174 Task getTask() { 175 return mTask; 176 } 177 178 /** Synchronizes this view's properties with the task's transform */ 179 void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) { 180 if (Console.Enabled) { 181 Console.log(Constants.Log.UI.Draw, "[TaskView|updateViewPropertiesToTaskTransform]", 182 "duration: " + duration, Console.AnsiPurple); 183 } 184 185 // Update the bar view 186 mBarView.updateViewPropertiesToTaskTransform(toTransform, duration); 187 188 // Check to see if any properties have changed, and update the task view 189 if (duration > 0) { 190 ViewPropertyAnimator anim = animate(); 191 boolean useLayers = false; 192 193 // Animate to the final state 194 if (toTransform.hasTranslationYChangedFrom(getTranslationY())) { 195 anim.translationY(toTransform.translationY); 196 } 197 if (Constants.DebugFlags.App.EnableShadows && 198 toTransform.hasTranslationZChangedFrom(getTranslationZ())) { 199 anim.translationZ(toTransform.translationZ); 200 } 201 if (toTransform.hasScaleChangedFrom(getScaleX())) { 202 anim.scaleX(toTransform.scale) 203 .scaleY(toTransform.scale) 204 .setUpdateListener(mUpdateDimListener); 205 useLayers = true; 206 } 207 if (toTransform.hasAlphaChangedFrom(getAlpha())) { 208 // Use layers if we animate alpha 209 anim.alpha(toTransform.alpha); 210 useLayers = true; 211 } 212 if (useLayers) { 213 anim.withLayer(); 214 } 215 anim.setStartDelay(toTransform.startDelay) 216 .setDuration(duration) 217 .setInterpolator(mConfig.fastOutSlowInInterpolator) 218 .start(); 219 } else { 220 // Set the changed properties 221 if (toTransform.hasTranslationYChangedFrom(getTranslationY())) { 222 setTranslationY(toTransform.translationY); 223 } 224 if (Constants.DebugFlags.App.EnableShadows && 225 toTransform.hasTranslationZChangedFrom(getTranslationZ())) { 226 setTranslationZ(toTransform.translationZ); 227 } 228 if (toTransform.hasScaleChangedFrom(getScaleX())) { 229 setScaleX(toTransform.scale); 230 setScaleY(toTransform.scale); 231 updateDimOverlayFromScale(); 232 } 233 if (toTransform.hasAlphaChangedFrom(getAlpha())) { 234 setAlpha(toTransform.alpha); 235 } 236 } 237 } 238 239 /** Resets this view's properties */ 240 void resetViewProperties() { 241 setTranslationX(0f); 242 setTranslationY(0f); 243 if (Constants.DebugFlags.App.EnableShadows) { 244 setTranslationZ(0f); 245 } 246 setScaleX(1f); 247 setScaleY(1f); 248 setAlpha(1f); 249 setDim(0); 250 invalidate(); 251 } 252 253 /** 254 * When we are un/filtering, this method will set up the transform that we are animating to, 255 * in order to hide the task. 256 */ 257 void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) { 258 // Fade the view out and slide it away 259 toTransform.alpha = 0f; 260 toTransform.translationY += 200; 261 toTransform.translationZ = 0; 262 } 263 264 /** 265 * When we are un/filtering, this method will setup the transform that we are animating from, 266 * in order to show the task. 267 */ 268 void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) { 269 // Fade the view in 270 fromTransform.alpha = 0f; 271 } 272 273 /** Prepares this task view for the enter-recents animations. This is called earlier in the 274 * first layout because the actual animation into recents may take a long time. */ 275 public void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, int offsetY, 276 int offscreenY) { 277 if (mConfig.launchedFromAppWithScreenshot) { 278 if (isTaskViewLaunchTargetTask) { 279 // Hide the task view as we are going to animate the full screenshot into view 280 // and then replace it with this view once we are done 281 setVisibility(View.INVISIBLE); 282 // Also hide the front most task bar view so we can animate it in 283 mBarView.prepareEnterRecentsAnimation(); 284 } else { 285 // Top align the task views 286 setTranslationY(offsetY); 287 setScaleX(1f); 288 setScaleY(1f); 289 } 290 291 } else if (mConfig.launchedFromAppWithThumbnail) { 292 if (isTaskViewLaunchTargetTask) { 293 // Hide the front most task bar view so we can animate it in 294 mBarView.prepareEnterRecentsAnimation(); 295 // Set the dim to 0 so we can animate it in 296 setDim(0); 297 } 298 299 } else if (mConfig.launchedFromHome) { 300 // Move the task view off screen (below) so we can animate it in 301 setTranslationY(offscreenY); 302 if (Constants.DebugFlags.App.EnableShadows) { 303 setTranslationZ(0); 304 } 305 setScaleX(1f); 306 setScaleY(1f); 307 } 308 } 309 310 /** Animates this task view as it enters recents */ 311 public void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) { 312 TaskViewTransform transform = ctx.currentTaskTransform; 313 314 if (mConfig.launchedFromAppWithScreenshot) { 315 if (ctx.isCurrentTaskLaunchTarget) { 316 // Animate the full screenshot down first, before swapping with this task view 317 ctx.fullScreenshotView.animateOnEnterRecents(ctx, new Runnable() { 318 @Override 319 public void run() { 320 // Animate the task bar of the first task view 321 mBarView.startEnterRecentsAnimation(0, mEnableThumbnailClip); 322 setVisibility(View.VISIBLE); 323 // Animate the footer into view 324 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration, 0); 325 // Decrement the post animation trigger 326 ctx.postAnimationTrigger.decrement(); 327 } 328 }); 329 } else { 330 // Animate the tasks down behind the full screenshot 331 animate() 332 .scaleX(transform.scale) 333 .scaleY(transform.scale) 334 .translationY(transform.translationY) 335 .setStartDelay(0) 336 .setUpdateListener(null) 337 .setInterpolator(mConfig.linearOutSlowInInterpolator) 338 .setDuration(475) 339 .withLayer() 340 .withEndAction(new Runnable() { 341 @Override 342 public void run() { 343 mEnableThumbnailClip.run(); 344 // Decrement the post animation trigger 345 ctx.postAnimationTrigger.decrement(); 346 } 347 }) 348 .start(); 349 } 350 ctx.postAnimationTrigger.increment(); 351 352 } else if (mConfig.launchedFromAppWithThumbnail) { 353 if (ctx.isCurrentTaskLaunchTarget) { 354 // Animate the task bar of the first task view 355 mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, mEnableThumbnailClip); 356 357 // Animate the dim into view as well 358 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", getDimOverlayFromScale()); 359 anim.setStartDelay(mConfig.taskBarEnterAnimDelay); 360 anim.setDuration(mConfig.taskBarEnterAnimDuration); 361 anim.setInterpolator(mConfig.fastOutLinearInInterpolator); 362 anim.addListener(new AnimatorListenerAdapter() { 363 @Override 364 public void onAnimationEnd(Animator animation) { 365 // Decrement the post animation trigger 366 ctx.postAnimationTrigger.decrement(); 367 } 368 }); 369 anim.start(); 370 ctx.postAnimationTrigger.increment(); 371 372 // Animate the footer into view 373 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration, 374 mConfig.taskBarEnterAnimDelay); 375 } else { 376 mEnableThumbnailClip.run(); 377 } 378 379 } else if (mConfig.launchedFromHome) { 380 // Animate the tasks up 381 int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1); 382 int delay = mConfig.taskBarEnterAnimDelay + 383 frontIndex * mConfig.taskViewEnterFromHomeDelay; 384 if (Constants.DebugFlags.App.EnableShadows) { 385 animate().translationZ(transform.translationZ); 386 } 387 animate() 388 .scaleX(transform.scale) 389 .scaleY(transform.scale) 390 .translationY(transform.translationY) 391 .setStartDelay(delay) 392 .setUpdateListener(null) 393 .setInterpolator(mConfig.quintOutInterpolator) 394 .setDuration(mConfig.taskViewEnterFromHomeDuration) 395 .withLayer() 396 .withEndAction(new Runnable() { 397 @Override 398 public void run() { 399 mEnableThumbnailClip.run(); 400 // Decrement the post animation trigger 401 ctx.postAnimationTrigger.decrement(); 402 } 403 }) 404 .start(); 405 ctx.postAnimationTrigger.increment(); 406 407 // Animate the footer into view 408 animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration, 409 mConfig.taskBarEnterAnimDelay); 410 } else { 411 // Otherwise, just enable the thumbnail clip 412 mEnableThumbnailClip.run(); 413 414 // Animate the footer into view 415 animateFooterVisibility(true, 0, 0); 416 } 417 } 418 419 /** Animates this task view as it leaves recents by pressing home. */ 420 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 421 animate() 422 .translationY(ctx.offscreenTranslationY) 423 .setStartDelay(0) 424 .setUpdateListener(null) 425 .setInterpolator(mConfig.fastOutLinearInInterpolator) 426 .setDuration(mConfig.taskViewExitToHomeDuration) 427 .withLayer() 428 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable()) 429 .start(); 430 ctx.postAnimationTrigger.increment(); 431 } 432 433 /** Animates this task view as it exits recents */ 434 public void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask) { 435 if (isLaunchingTask) { 436 // Disable the thumbnail clip and animate the bar out 437 mBarView.startLaunchTaskAnimation(mDisableThumbnailClip, r); 438 439 // Animate the dim 440 if (mDim > 0) { 441 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0); 442 anim.setDuration(mConfig.taskBarExitAnimDuration); 443 anim.setInterpolator(mConfig.fastOutLinearInInterpolator); 444 anim.start(); 445 } 446 } else { 447 // Hide the dismiss button 448 mBarView.startLaunchTaskDismissAnimation(); 449 } 450 } 451 452 /** Animates the deletion of this task view */ 453 public void startDeleteTaskAnimation(final Runnable r) { 454 // Disabling clipping with the stack while the view is animating away 455 setClipViewInStack(false); 456 457 animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx) 458 .alpha(0f) 459 .setStartDelay(0) 460 .setUpdateListener(null) 461 .setInterpolator(mConfig.fastOutSlowInInterpolator) 462 .setDuration(mConfig.taskViewRemoveAnimDuration) 463 .withLayer() 464 .withEndAction(new Runnable() { 465 @Override 466 public void run() { 467 // We just throw this into a runnable because starting a view property 468 // animation using layers can cause inconsisten results if we try and 469 // update the layers while the animation is running. In some cases, 470 // the runnabled passed in may start an animation which also uses layers 471 // so we defer all this by posting this. 472 r.run(); 473 474 // Re-enable clipping with the stack (we will reuse this view) 475 setClipViewInStack(true); 476 } 477 }) 478 .start(); 479 } 480 481 /** Animates this task view if the user does not interact with the stack after a certain time. */ 482 public void startNoUserInteractionAnimation() { 483 mBarView.startNoUserInteractionAnimation(); 484 } 485 486 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 487 public void setNoUserInteractionState() { 488 mBarView.setNoUserInteractionState(); 489 } 490 491 /** Returns the rect we want to clip (it may not be the full rect) */ 492 Rect getClippingRect(Rect outRect) { 493 getHitRect(outRect); 494 // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster 495 outRect.right = outRect.left + mThumbnailView.getRight(); 496 outRect.bottom = outRect.top + mThumbnailView.getBottom(); 497 return outRect; 498 } 499 500 /** Enable the hw layers on this task view */ 501 void enableHwLayers() { 502 mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint); 503 mBarView.enableHwLayers(); 504 mLockToAppButtonView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint); 505 } 506 507 /** Disable the hw layers on this task view */ 508 void disableHwLayers() { 509 mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint); 510 mBarView.disableHwLayers(); 511 mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint); 512 } 513 514 /** Sets the stubbed state of this task view. */ 515 void setStubState(boolean isStub) { 516 if (!mIsStub && isStub) { 517 // This is now a stub task view, so clip to the bar height, hide the thumbnail 518 setClipBounds(new Rect(0, 0, getMeasuredWidth(), mBarView.getMeasuredHeight())); 519 mThumbnailView.setVisibility(View.INVISIBLE); 520 // Temporary 521 mBarView.mActivityDescription.setText("Stub"); 522 } else if (mIsStub && !isStub) { 523 setClipBounds(null); 524 mThumbnailView.setVisibility(View.VISIBLE); 525 } 526 mIsStub = isStub; 527 } 528 529 /** 530 * Returns whether this view should be clipped, or any views below should clip against this 531 * view. 532 */ 533 boolean shouldClipViewInStack() { 534 return mClipViewInStack && (getVisibility() == View.VISIBLE); 535 } 536 537 /** Sets whether this view should be clipped, or clipped against. */ 538 void setClipViewInStack(boolean clip) { 539 if (clip != mClipViewInStack) { 540 mClipViewInStack = clip; 541 if (getParent() instanceof View) { 542 getHitRect(mTmpRect); 543 ((View) getParent()).invalidate(mTmpRect); 544 } 545 } 546 } 547 548 /** Sets the footer height. */ 549 public void setFooterHeight(int height) { 550 mFooterHeight = height; 551 updateOutline(); 552 invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(), 553 getMeasuredHeight()); 554 } 555 556 /** Gets the footer height. */ 557 public int getFooterHeight() { 558 return mFooterHeight; 559 } 560 561 /** Animates the footer into and out of view. */ 562 public void animateFooterVisibility(boolean visible, int duration, int delay) { 563 if (!mTask.canLockToTask) return; 564 if (mMaxFooterHeight <= 0) return; 565 566 if (mFooterAnimator != null) { 567 mFooterAnimator.removeAllListeners(); 568 mFooterAnimator.cancel(); 569 } 570 int height = visible ? mMaxFooterHeight : 0; 571 if (visible && mLockToAppButtonView.getVisibility() != View.VISIBLE) { 572 if (duration > 0) { 573 setFooterHeight(0); 574 } else { 575 setFooterHeight(mMaxFooterHeight); 576 } 577 mLockToAppButtonView.setVisibility(View.VISIBLE); 578 } 579 if (duration > 0) { 580 mFooterAnimator = ObjectAnimator.ofInt(this, "footerHeight", height); 581 mFooterAnimator.setDuration(duration); 582 mFooterAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator); 583 if (!visible) { 584 mFooterAnimator.addListener(new AnimatorListenerAdapter() { 585 @Override 586 public void onAnimationEnd(Animator animation) { 587 mLockToAppButtonView.setVisibility(View.INVISIBLE); 588 } 589 }); 590 } 591 mFooterAnimator.start(); 592 } else { 593 if (!visible) { 594 mLockToAppButtonView.setVisibility(View.INVISIBLE); 595 } 596 } 597 } 598 599 /** Returns the current dim. */ 600 public void setDim(int dim) { 601 mDim = dim; 602 postInvalidateOnAnimation(); 603 } 604 605 /** Returns the current dim. */ 606 public int getDim() { 607 return mDim; 608 } 609 610 /** Compute the dim as a function of the scale of this view. */ 611 int getDimOverlayFromScale() { 612 float minScale = TaskStackViewLayoutAlgorithm.StackPeekMinScale; 613 float scaleRange = 1f - minScale; 614 float dim = (1f - getScaleX()) / scaleRange; 615 dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f)); 616 return Math.max(0, Math.min(mMaxDim, (int) (dim * 255))); 617 } 618 619 /** Update the dim as a function of the scale of this view. */ 620 void updateDimOverlayFromScale() { 621 setDim(getDimOverlayFromScale()); 622 } 623 624 @Override 625 public void draw(Canvas canvas) { 626 super.draw(canvas); 627 628 // Apply the dim if necessary 629 if (mDim > 0) { 630 canvas.drawColor(mDim << 24); 631 } 632 } 633 634 @Override 635 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 636 if (mIsStub && (child == mThumbnailView)) { 637 // Skip the thumbnail view if we are in stub mode 638 return false; 639 } 640 return super.drawChild(canvas, child, drawingTime); 641 } 642 643 /** 644 * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen 645 * if the view is not currently visible, or we are in touch state (where we still want to keep 646 * track of focus). 647 */ 648 public void setFocusedTask() { 649 mIsFocused = true; 650 // Workaround, we don't always want it focusable in touch mode, but we want the first task 651 // to be focused after the enter-recents animation, which can be triggered from either touch 652 // or keyboard 653 setFocusableInTouchMode(true); 654 requestFocus(); 655 setFocusableInTouchMode(false); 656 invalidate(); 657 } 658 659 /** 660 * Updates the explicitly focused state when the view focus changes. 661 */ 662 @Override 663 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 664 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 665 if (!gainFocus) { 666 mIsFocused = false; 667 invalidate(); 668 } 669 } 670 671 /** 672 * Returns whether we have explicitly been focused. 673 */ 674 public boolean isFocusedTask() { 675 return mIsFocused || isFocused(); 676 } 677 678 /**** TaskCallbacks Implementation ****/ 679 680 /** Binds this task view to the task */ 681 public void onTaskBound(Task t) { 682 mTask = t; 683 mTask.setCallbacks(this); 684 if (getMeasuredWidth() == 0) { 685 animateFooterVisibility(t.canLockToTask, 0, 0); 686 } else { 687 animateFooterVisibility(t.canLockToTask, mConfig.taskViewLockToAppLongAnimDuration, 0); 688 } 689 } 690 691 @Override 692 public void onTaskDataLoaded() { 693 if (mThumbnailView != null && mBarView != null) { 694 // Bind each of the views to the new task data 695 mThumbnailView.rebindToTask(mTask); 696 mBarView.rebindToTask(mTask); 697 // Rebind any listeners 698 if (Constants.DebugFlags.App.EnableTaskFiltering) { 699 mBarView.mApplicationIcon.setOnClickListener(this); 700 } 701 mBarView.mDismissButton.setOnClickListener(this); 702 mLockToAppButtonView.setOnClickListener(this); 703 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 704 if (mConfig.developerOptionsEnabled) { 705 mBarView.mApplicationIcon.setOnLongClickListener(this); 706 } 707 } 708 } 709 mTaskDataLoaded = true; 710 } 711 712 @Override 713 public void onTaskDataUnloaded() { 714 if (mThumbnailView != null && mBarView != null) { 715 // Unbind each of the views from the task data and remove the task callback 716 mTask.setCallbacks(null); 717 mThumbnailView.unbindFromTask(); 718 mBarView.unbindFromTask(); 719 // Unbind any listeners 720 if (Constants.DebugFlags.App.EnableTaskFiltering) { 721 mBarView.mApplicationIcon.setOnClickListener(null); 722 } 723 mBarView.mDismissButton.setOnClickListener(null); 724 mLockToAppButtonView.setOnClickListener(null); 725 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 726 mBarView.mApplicationIcon.setOnLongClickListener(null); 727 } 728 } 729 mTaskDataLoaded = false; 730 } 731 732 /** Enables/disables handling touch on this task view. */ 733 void setTouchEnabled(boolean enabled) { 734 setOnClickListener(enabled ? this : null); 735 } 736 737 @Override 738 public void onClick(final View v) { 739 // We purposely post the handler delayed to allow for the touch feedback to draw 740 final TaskView tv = this; 741 postDelayed(new Runnable() { 742 @Override 743 public void run() { 744 if (v == mBarView.mApplicationIcon) { 745 mCb.onTaskViewAppIconClicked(tv); 746 } else if (v == mBarView.mDismissButton) { 747 // Animate out the view and call the callback 748 startDeleteTaskAnimation(new Runnable() { 749 @Override 750 public void run() { 751 mCb.onTaskViewDismissed(tv); 752 } 753 }); 754 // Hide the footer 755 tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration, 0); 756 } else if (v == tv || v == mLockToAppButtonView) { 757 mCb.onTaskViewClicked(tv, tv.getTask(), (v == mLockToAppButtonView)); 758 } 759 } 760 }, 125); 761 } 762 763 @Override 764 public boolean onLongClick(View v) { 765 if (v == mBarView.mApplicationIcon) { 766 mCb.onTaskViewAppInfoClicked(this); 767 return true; 768 } 769 return false; 770 } 771} 772