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