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