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