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