TaskView.java revision 2536c7ed446203ea12b38cf05a88e603f8d1b768
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.ObjectAnimator; 21import android.animation.ValueAnimator; 22import android.content.Context; 23import android.content.res.Resources; 24import android.graphics.Color; 25import android.graphics.Outline; 26import android.graphics.Paint; 27import android.graphics.PorterDuff; 28import android.graphics.PorterDuffColorFilter; 29import android.graphics.Rect; 30import android.util.AttributeSet; 31import android.view.View; 32import android.view.ViewOutlineProvider; 33import android.view.animation.AccelerateInterpolator; 34import android.view.animation.AnimationUtils; 35import android.view.animation.Interpolator; 36import android.widget.FrameLayout; 37import com.android.systemui.R; 38import com.android.systemui.recents.Constants; 39import com.android.systemui.recents.RecentsActivityLaunchState; 40import com.android.systemui.recents.RecentsConfiguration; 41import com.android.systemui.recents.events.EventBus; 42import com.android.systemui.recents.events.ui.DismissTaskEvent; 43import com.android.systemui.recents.misc.Utilities; 44import com.android.systemui.recents.model.Task; 45import com.android.systemui.statusbar.phone.PhoneStatusBar; 46 47/* A task view */ 48public class TaskView extends FrameLayout implements Task.TaskCallbacks, 49 View.OnClickListener { 50 51 /** The TaskView callbacks */ 52 interface TaskViewCallbacks { 53 public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask); 54 public void onTaskViewClipStateChanged(TaskView tv); 55 public void onTaskViewFocusChanged(TaskView tv, boolean focused); 56 } 57 58 RecentsConfiguration mConfig; 59 60 float mTaskProgress; 61 ObjectAnimator mTaskProgressAnimator; 62 float mMaxDimScale; 63 int mDimAlpha; 64 AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1f); 65 PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP); 66 Paint mDimLayerPaint = new Paint(); 67 float mActionButtonTranslationZ; 68 69 Task mTask; 70 boolean mTaskDataLoaded; 71 boolean mIsFocused; 72 boolean mFocusAnimationsEnabled; 73 boolean mClipViewInStack; 74 AnimateableViewBounds mViewBounds; 75 76 View mContent; 77 TaskViewThumbnail mThumbnailView; 78 TaskViewHeader mHeaderView; 79 View mActionButtonView; 80 TaskViewCallbacks mCb; 81 82 Interpolator mFastOutSlowInInterpolator; 83 Interpolator mFastOutLinearInInterpolator; 84 Interpolator mQuintOutInterpolator; 85 86 // Optimizations 87 ValueAnimator.AnimatorUpdateListener mUpdateDimListener = 88 new ValueAnimator.AnimatorUpdateListener() { 89 @Override 90 public void onAnimationUpdate(ValueAnimator animation) { 91 setTaskProgress((Float) animation.getAnimatedValue()); 92 } 93 }; 94 95 96 public TaskView(Context context) { 97 this(context, null); 98 } 99 100 public TaskView(Context context, AttributeSet attrs) { 101 this(context, attrs, 0); 102 } 103 104 public TaskView(Context context, AttributeSet attrs, int defStyleAttr) { 105 this(context, attrs, defStyleAttr, 0); 106 } 107 108 public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 109 super(context, attrs, defStyleAttr, defStyleRes); 110 Resources res = context.getResources(); 111 mConfig = RecentsConfiguration.getInstance(); 112 mMaxDimScale = res.getInteger(R.integer.recents_max_task_stack_view_dim) / 255f; 113 mClipViewInStack = true; 114 mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize( 115 R.dimen.recents_task_view_rounded_corners_radius)); 116 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, 117 com.android.internal.R.interpolator.fast_out_slow_in); 118 mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, 119 com.android.internal.R.interpolator.fast_out_linear_in); 120 mQuintOutInterpolator = AnimationUtils.loadInterpolator(context, 121 com.android.internal.R.interpolator.decelerate_quint); 122 setTaskProgress(getTaskProgress()); 123 setDim(getDim()); 124 if (mConfig.fakeShadows) { 125 setBackground(new FakeShadowDrawable(res, mConfig)); 126 } 127 setOutlineProvider(mViewBounds); 128 } 129 130 /** Set callback */ 131 void setCallbacks(TaskViewCallbacks cb) { 132 mCb = cb; 133 } 134 135 /** Resets this TaskView for reuse. */ 136 void reset() { 137 resetViewProperties(); 138 resetNoUserInteractionState(); 139 setClipViewInStack(false); 140 setCallbacks(null); 141 } 142 143 /** Gets the task */ 144 Task getTask() { 145 return mTask; 146 } 147 148 /** Returns the view bounds. */ 149 AnimateableViewBounds getViewBounds() { 150 return mViewBounds; 151 } 152 153 @Override 154 protected void onFinishInflate() { 155 // Bind the views 156 mContent = findViewById(R.id.task_view_content); 157 mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar); 158 mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail); 159 mThumbnailView.updateClipToTaskBar(mHeaderView); 160 mActionButtonView = findViewById(R.id.lock_to_app_fab); 161 mActionButtonView.setOutlineProvider(new ViewOutlineProvider() { 162 @Override 163 public void getOutline(View view, Outline outline) { 164 // Set the outline to match the FAB background 165 outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight()); 166 } 167 }); 168 mActionButtonTranslationZ = mActionButtonView.getTranslationZ(); 169 } 170 171 @Override 172 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 173 int width = MeasureSpec.getSize(widthMeasureSpec); 174 int height = MeasureSpec.getSize(heightMeasureSpec); 175 176 int widthWithoutPadding = width - mPaddingLeft - mPaddingRight; 177 int heightWithoutPadding = height - mPaddingTop - mPaddingBottom; 178 int taskBarHeight = getResources().getDimensionPixelSize(R.dimen.recents_task_bar_height); 179 180 // Measure the content 181 mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 182 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY)); 183 184 // Measure the bar view, and action button 185 mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 186 MeasureSpec.makeMeasureSpec(taskBarHeight, MeasureSpec.EXACTLY)); 187 mActionButtonView.measure( 188 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST), 189 MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST)); 190 // Measure the thumbnail to be square 191 mThumbnailView.measure( 192 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 193 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY)); 194 setMeasuredDimension(width, height); 195 invalidateOutline(); 196 } 197 198 /** Synchronizes this view's properties with the task's transform */ 199 void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) { 200 updateViewPropertiesToTaskTransform(toTransform, duration, null); 201 } 202 203 void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration, 204 ValueAnimator.AnimatorUpdateListener updateCallback) { 205 // Apply the transform 206 toTransform.applyToTaskView(this, duration, mFastOutSlowInInterpolator, false, 207 !mConfig.fakeShadows, updateCallback); 208 209 // Update the task progress 210 Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator); 211 if (duration <= 0) { 212 setTaskProgress(toTransform.p); 213 } else { 214 mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p); 215 mTaskProgressAnimator.setDuration(duration); 216 mTaskProgressAnimator.addUpdateListener(mUpdateDimListener); 217 mTaskProgressAnimator.start(); 218 } 219 } 220 221 /** Resets this view's properties */ 222 void resetViewProperties() { 223 setDim(0); 224 setLayerType(View.LAYER_TYPE_NONE, null); 225 TaskViewTransform.reset(this); 226 if (mActionButtonView != null) { 227 mActionButtonView.setScaleX(1f); 228 mActionButtonView.setScaleY(1f); 229 mActionButtonView.setAlpha(1f); 230 mActionButtonView.setTranslationZ(mActionButtonTranslationZ); 231 } 232 } 233 234 /** 235 * When we are un/filtering, this method will set up the transform that we are animating to, 236 * in order to hide the task. 237 */ 238 void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) { 239 // Fade the view out and slide it away 240 toTransform.alpha = 0f; 241 toTransform.translationY += 200; 242 toTransform.translationZ = 0; 243 } 244 245 /** 246 * When we are un/filtering, this method will setup the transform that we are animating from, 247 * in order to show the task. 248 */ 249 void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) { 250 // Fade the view in 251 fromTransform.alpha = 0f; 252 } 253 254 /** Prepares this task view for the enter-recents animations. This is called earlier in the 255 * first layout because the actual animation into recents may take a long time. */ 256 void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, 257 boolean occludesLaunchTarget, int offscreenY) { 258 RecentsActivityLaunchState launchState = mConfig.getLaunchState(); 259 int initialDim = getDim(); 260 if (launchState.launchedHasConfigurationChanged) { 261 // Just load the views as-is 262 } else if (launchState.launchedFromAppWithThumbnail) { 263 if (isTaskViewLaunchTargetTask) { 264 // Set the dim to 0 so we can animate it in 265 initialDim = 0; 266 // Hide the action button 267 mActionButtonView.setAlpha(0f); 268 } else if (occludesLaunchTarget) { 269 // Move the task view off screen (below) so we can animate it in 270 setTranslationY(offscreenY); 271 } 272 273 } else if (launchState.launchedFromHome) { 274 // Move the task view off screen (below) so we can animate it in 275 setTranslationY(offscreenY); 276 setTranslationZ(0); 277 setScaleX(1f); 278 setScaleY(1f); 279 } 280 // Apply the current dim 281 setDim(initialDim); 282 // Prepare the thumbnail view alpha 283 mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask); 284 } 285 286 /** Animates this task view as it enters recents */ 287 void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) { 288 RecentsActivityLaunchState launchState = mConfig.getLaunchState(); 289 Resources res = mContext.getResources(); 290 final TaskViewTransform transform = ctx.currentTaskTransform; 291 final int transitionEnterFromAppDelay = res.getInteger( 292 R.integer.recents_enter_from_app_transition_duration); 293 final int transitionEnterFromHomeDelay = res.getInteger( 294 R.integer.recents_enter_from_home_transition_duration); 295 final int taskViewEnterFromAppDuration = res.getInteger( 296 R.integer.recents_task_enter_from_app_duration); 297 final int taskViewEnterFromHomeDuration = res.getInteger( 298 R.integer.recents_task_enter_from_home_duration); 299 final int taskViewEnterFromHomeStaggerDelay = res.getInteger( 300 R.integer.recents_task_enter_from_home_stagger_delay); 301 final int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 302 R.dimen.recents_task_view_affiliate_group_enter_offset); 303 int startDelay = 0; 304 305 if (launchState.launchedFromAppWithThumbnail) { 306 if (mTask.isLaunchTarget) { 307 // Animate the dim/overlay 308 if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) { 309 // Animate the thumbnail alpha before the dim animation (to prevent updating the 310 // hardware layer) 311 mThumbnailView.startEnterRecentsAnimation(transitionEnterFromAppDelay, 312 new Runnable() { 313 @Override 314 public void run() { 315 animateDimToProgress(0, taskViewEnterFromAppDuration, 316 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 317 } 318 }); 319 } else { 320 // Immediately start the dim animation 321 animateDimToProgress(transitionEnterFromAppDelay, 322 taskViewEnterFromAppDuration, 323 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 324 } 325 ctx.postAnimationTrigger.increment(); 326 327 // Animate the action button in 328 fadeInActionButton(transitionEnterFromAppDelay, 329 taskViewEnterFromAppDuration); 330 } else { 331 // Animate the task up if it was occluding the launch target 332 if (ctx.currentTaskOccludesLaunchTarget) { 333 setTranslationY(transform.translationY + taskViewAffiliateGroupEnterOffset); 334 setAlpha(0f); 335 animate().alpha(1f) 336 .translationY(transform.translationY) 337 .setStartDelay(transitionEnterFromAppDelay) 338 .setUpdateListener(null) 339 .setInterpolator(mFastOutSlowInInterpolator) 340 .setDuration(taskViewEnterFromHomeDuration) 341 .withEndAction(new Runnable() { 342 @Override 343 public void run() { 344 // Decrement the post animation trigger 345 ctx.postAnimationTrigger.decrement(); 346 } 347 }) 348 .start(); 349 ctx.postAnimationTrigger.increment(); 350 } 351 } 352 startDelay = transitionEnterFromAppDelay; 353 354 } else if (launchState.launchedFromHome) { 355 // Animate the tasks up 356 int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1); 357 int delay = transitionEnterFromHomeDelay + 358 frontIndex * taskViewEnterFromHomeStaggerDelay; 359 360 setScaleX(transform.scale); 361 setScaleY(transform.scale); 362 if (!mConfig.fakeShadows) { 363 animate().translationZ(transform.translationZ); 364 } 365 animate() 366 .translationY(transform.translationY) 367 .setStartDelay(delay) 368 .setUpdateListener(ctx.updateListener) 369 .setInterpolator(mQuintOutInterpolator) 370 .setDuration(taskViewEnterFromHomeDuration + 371 frontIndex * taskViewEnterFromHomeStaggerDelay) 372 .withEndAction(new Runnable() { 373 @Override 374 public void run() { 375 // Decrement the post animation trigger 376 ctx.postAnimationTrigger.decrement(); 377 } 378 }) 379 .start(); 380 ctx.postAnimationTrigger.increment(); 381 startDelay = delay; 382 } 383 384 // Enable the focus animations from this point onwards so that they aren't affected by the 385 // window transitions 386 postDelayed(new Runnable() { 387 @Override 388 public void run() { 389 enableFocusAnimations(); 390 } 391 }, startDelay); 392 } 393 394 public void fadeInActionButton(int delay, int duration) { 395 // Hide the action button 396 mActionButtonView.setAlpha(0f); 397 398 // Animate the action button in 399 mActionButtonView.animate().alpha(1f) 400 .setStartDelay(delay) 401 .setDuration(duration) 402 .setInterpolator(PhoneStatusBar.ALPHA_IN) 403 .start(); 404 } 405 406 /** Animates this task view as it leaves recents by pressing home. */ 407 void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 408 int taskViewExitToHomeDuration = getResources().getInteger( 409 R.integer.recents_task_exit_to_home_duration); 410 animate() 411 .translationY(ctx.offscreenTranslationY) 412 .setStartDelay(0) 413 .setUpdateListener(null) 414 .setInterpolator(mFastOutLinearInInterpolator) 415 .setDuration(taskViewExitToHomeDuration) 416 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable()) 417 .start(); 418 ctx.postAnimationTrigger.increment(); 419 } 420 421 /** Animates this task view away when dismissing all tasks. */ 422 void startDismissAllAnimation() { 423 dismissTask(); 424 } 425 426 /** Animates this task view as it exits recents */ 427 void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask, 428 boolean occludesLaunchTarget, boolean lockToTask) { 429 final int taskViewExitToAppDuration = mContext.getResources().getInteger( 430 R.integer.recents_task_exit_to_app_duration); 431 final int taskViewAffiliateGroupEnterOffset = mContext.getResources().getDimensionPixelSize( 432 R.dimen.recents_task_view_affiliate_group_enter_offset); 433 434 if (isLaunchingTask) { 435 // Animate the thumbnail alpha back into full opacity for the window animation out 436 mThumbnailView.startLaunchTaskAnimation(postAnimRunnable); 437 438 // Animate the dim 439 if (mDimAlpha > 0) { 440 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0); 441 anim.setDuration(taskViewExitToAppDuration); 442 anim.setInterpolator(mFastOutLinearInInterpolator); 443 anim.start(); 444 } 445 446 // Animate the action button away 447 if (!lockToTask) { 448 float toScale = 0.9f; 449 mActionButtonView.animate() 450 .scaleX(toScale) 451 .scaleY(toScale); 452 } 453 mActionButtonView.animate() 454 .alpha(0f) 455 .setStartDelay(0) 456 .setDuration(taskViewExitToAppDuration) 457 .setInterpolator(mFastOutLinearInInterpolator) 458 .start(); 459 } else { 460 // Hide the dismiss button 461 mHeaderView.startLaunchTaskDismissAnimation(); 462 // If this is another view in the task grouping and is in front of the launch task, 463 // animate it away first 464 if (occludesLaunchTarget) { 465 animate().alpha(0f) 466 .translationY(getTranslationY() + taskViewAffiliateGroupEnterOffset) 467 .setStartDelay(0) 468 .setUpdateListener(null) 469 .setInterpolator(mFastOutLinearInInterpolator) 470 .setDuration(taskViewExitToAppDuration) 471 .start(); 472 } 473 } 474 } 475 476 /** Animates the deletion of this task view */ 477 void startDeleteTaskAnimation(final Runnable r, int delay) { 478 int taskViewRemoveAnimDuration = getResources().getInteger( 479 R.integer.recents_animate_task_view_remove_duration); 480 int taskViewRemoveAnimTranslationXPx = getResources().getDimensionPixelSize( 481 R.dimen.recents_task_view_remove_anim_translation_x); 482 483 // Disabling clipping with the stack while the view is animating away 484 setClipViewInStack(false); 485 486 animate().translationX(taskViewRemoveAnimTranslationXPx) 487 .alpha(0f) 488 .setStartDelay(delay) 489 .setUpdateListener(null) 490 .setInterpolator(mFastOutSlowInInterpolator) 491 .setDuration(taskViewRemoveAnimDuration) 492 .withEndAction(new Runnable() { 493 @Override 494 public void run() { 495 if (r != null) { 496 r.run(); 497 } 498 499 // Re-enable clipping with the stack (we will reuse this view) 500 setClipViewInStack(true); 501 } 502 }) 503 .start(); 504 } 505 506 /** Enables/disables handling touch on this task view. */ 507 void setTouchEnabled(boolean enabled) { 508 setOnClickListener(enabled ? this : null); 509 } 510 511 /** Animates this task view if the user does not interact with the stack after a certain time. */ 512 void startNoUserInteractionAnimation() { 513 mHeaderView.startNoUserInteractionAnimation(); 514 } 515 516 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 517 void setNoUserInteractionState() { 518 mHeaderView.setNoUserInteractionState(); 519 } 520 521 /** Resets the state tracking that the user has not interacted with the stack after a certain time. */ 522 void resetNoUserInteractionState() { 523 mHeaderView.resetNoUserInteractionState(); 524 } 525 526 /** Dismisses this task. */ 527 void dismissTask() { 528 // Animate out the view and call the callback 529 final TaskView tv = this; 530 startDeleteTaskAnimation(new Runnable() { 531 @Override 532 public void run() { 533 EventBus.getDefault().send(new DismissTaskEvent(mTask, tv)); 534 } 535 }, 0); 536 } 537 538 /** 539 * Returns whether this view should be clipped, or any views below should clip against this 540 * view. 541 */ 542 boolean shouldClipViewInStack() { 543 return mClipViewInStack && (getVisibility() == View.VISIBLE); 544 } 545 546 /** Sets whether this view should be clipped, or clipped against. */ 547 void setClipViewInStack(boolean clip) { 548 if (clip != mClipViewInStack) { 549 mClipViewInStack = clip; 550 if (mCb != null) { 551 mCb.onTaskViewClipStateChanged(this); 552 } 553 } 554 } 555 556 /** Sets the current task progress. */ 557 public void setTaskProgress(float p) { 558 mTaskProgress = p; 559 mViewBounds.setAlpha(p); 560 updateDimFromTaskProgress(); 561 } 562 563 /** Returns the current task progress. */ 564 public float getTaskProgress() { 565 return mTaskProgress; 566 } 567 568 /** Returns the current dim. */ 569 public void setDim(int dim) { 570 mDimAlpha = dim; 571 if (mConfig.useHardwareLayers) { 572 // Defer setting hardware layers if we have not yet measured, or there is no dim to draw 573 if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) { 574 mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0)); 575 mDimLayerPaint.setColorFilter(mDimColorFilter); 576 mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); 577 } 578 } else { 579 float dimAlpha = mDimAlpha / 255.0f; 580 if (mThumbnailView != null) { 581 mThumbnailView.setDimAlpha(dimAlpha); 582 } 583 if (mHeaderView != null) { 584 mHeaderView.setDimAlpha(dim); 585 } 586 } 587 } 588 589 /** Returns the current dim. */ 590 public int getDim() { 591 return mDimAlpha; 592 } 593 594 /** Animates the dim to the task progress. */ 595 void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) { 596 // Animate the dim into view as well 597 int toDim = getDimFromTaskProgress(); 598 if (toDim != getDim()) { 599 ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim); 600 anim.setStartDelay(delay); 601 anim.setDuration(duration); 602 if (postAnimRunnable != null) { 603 anim.addListener(postAnimRunnable); 604 } 605 anim.start(); 606 } 607 } 608 609 /** Compute the dim as a function of the scale of this view. */ 610 int getDimFromTaskProgress() { 611 float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress); 612 return (int) (dim * 255); 613 } 614 615 /** Update the dim as a function of the scale of this view. */ 616 void updateDimFromTaskProgress() { 617 setDim(getDimFromTaskProgress()); 618 } 619 620 /**** View focus state ****/ 621 622 /** 623 * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen 624 * if the view is not currently visible, or we are in touch state (where we still want to keep 625 * track of focus). 626 */ 627 public void setFocusedTask(boolean animateFocusedState) { 628 mIsFocused = true; 629 if (mFocusAnimationsEnabled) { 630 // Focus the header bar 631 mHeaderView.onTaskViewFocusChanged(true, animateFocusedState); 632 } 633 // Update the thumbnail alpha with the focus 634 mThumbnailView.onFocusChanged(true); 635 // Call the callback 636 if (mCb != null) { 637 mCb.onTaskViewFocusChanged(this, true); 638 } 639 // Workaround, we don't always want it focusable in touch mode, but we want the first task 640 // to be focused after the enter-recents animation, which can be triggered from either touch 641 // or keyboard 642 setFocusableInTouchMode(true); 643 requestFocus(); 644 setFocusableInTouchMode(false); 645 invalidate(); 646 } 647 648 /** 649 * Unsets the focused task explicitly. 650 */ 651 void unsetFocusedTask() { 652 mIsFocused = false; 653 if (mFocusAnimationsEnabled) { 654 // Un-focus the header bar 655 mHeaderView.onTaskViewFocusChanged(false, true); 656 } 657 658 // Update the thumbnail alpha with the focus 659 mThumbnailView.onFocusChanged(false); 660 // Call the callback 661 if (mCb != null) { 662 mCb.onTaskViewFocusChanged(this, false); 663 } 664 invalidate(); 665 } 666 667 /** 668 * Updates the explicitly focused state when the view focus changes. 669 */ 670 @Override 671 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 672 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 673 if (!gainFocus) { 674 unsetFocusedTask(); 675 } 676 } 677 678 /** 679 * Returns whether we have explicitly been focused. 680 */ 681 public boolean isFocusedTask() { 682 return mIsFocused || isFocused(); 683 } 684 685 /** Enables all focus animations. */ 686 void enableFocusAnimations() { 687 boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled; 688 mFocusAnimationsEnabled = true; 689 if (mIsFocused && !wasFocusAnimationsEnabled) { 690 // Re-notify the header if we were focused and animations were not previously enabled 691 mHeaderView.onTaskViewFocusChanged(true, true); 692 } 693 } 694 695 public void disableLayersForOneFrame() { 696 mHeaderView.disableLayersForOneFrame(); 697 } 698 699 /**** TaskCallbacks Implementation ****/ 700 701 /** Binds this task view to the task */ 702 public void onTaskBound(Task t) { 703 mTask = t; 704 mTask.setCallbacks(this); 705 706 // Hide the action button if lock to app is disabled for this view 707 int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE; 708 if (mActionButtonView.getVisibility() != lockButtonVisibility) { 709 mActionButtonView.setVisibility(lockButtonVisibility); 710 requestLayout(); 711 } 712 } 713 714 @Override 715 public void onTaskDataLoaded() { 716 if (mThumbnailView != null && mHeaderView != null) { 717 // Bind each of the views to the new task data 718 mThumbnailView.rebindToTask(mTask); 719 mHeaderView.rebindToTask(mTask); 720 // Rebind any listeners 721 mActionButtonView.setOnClickListener(this); 722 } 723 mTaskDataLoaded = true; 724 } 725 726 @Override 727 public void onTaskDataUnloaded() { 728 if (mThumbnailView != null && mHeaderView != null) { 729 // Unbind each of the views from the task data and remove the task callback 730 mTask.setCallbacks(null); 731 mThumbnailView.unbindFromTask(); 732 mHeaderView.unbindFromTask(); 733 // Unbind any listeners 734 mActionButtonView.setOnClickListener(null); 735 } 736 mTaskDataLoaded = false; 737 } 738 739 @Override 740 public void onMultiStackDebugTaskStackIdChanged() { 741 mHeaderView.rebindToTask(mTask); 742 } 743 744 /**** View.OnClickListener Implementation ****/ 745 746 @Override 747 public void onClick(final View v) { 748 if (v == mActionButtonView) { 749 // Reset the translation of the action button before we animate it out 750 mActionButtonView.setTranslationZ(0f); 751 } 752 if (mCb != null) { 753 mCb.onTaskViewClicked(this, mTask, (v == mActionButtonView)); 754 } 755 } 756} 757