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