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