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