TaskView.java revision 3fb67562c548662910160d5672b1b9558bd6571c
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.util.Log; 35import android.view.MotionEvent; 36import android.view.View; 37import android.view.ViewOutlineProvider; 38import android.view.animation.AccelerateInterpolator; 39import android.view.animation.AnimationUtils; 40import android.view.animation.Interpolator; 41import android.widget.FrameLayout; 42import com.android.systemui.R; 43import com.android.systemui.recents.Constants; 44import com.android.systemui.recents.Recents; 45import com.android.systemui.recents.RecentsActivity; 46import com.android.systemui.recents.RecentsActivityLaunchState; 47import com.android.systemui.recents.RecentsConfiguration; 48import com.android.systemui.recents.events.EventBus; 49import com.android.systemui.recents.events.ui.DismissTaskViewEvent; 50import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; 51import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; 52import com.android.systemui.recents.misc.SystemServicesProxy; 53import com.android.systemui.recents.misc.Utilities; 54import com.android.systemui.recents.model.Task; 55import com.android.systemui.recents.model.TaskStack; 56import com.android.systemui.statusbar.phone.PhoneStatusBar; 57 58/* A task view */ 59public class TaskView extends FrameLayout implements Task.TaskCallbacks, 60 View.OnClickListener, View.OnLongClickListener { 61 62 private final static String TAG = "TaskView"; 63 private final static boolean DEBUG = false; 64 65 /** The TaskView callbacks */ 66 interface TaskViewCallbacks { 67 public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask); 68 public void onTaskViewClipStateChanged(TaskView tv); 69 } 70 71 float mTaskProgress; 72 ObjectAnimator mTaskProgressAnimator; 73 float mMaxDimScale; 74 int mDimAlpha; 75 AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1f); 76 PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP); 77 Paint mDimLayerPaint = new Paint(); 78 float mActionButtonTranslationZ; 79 80 Task mTask; 81 boolean mTaskDataLoaded; 82 boolean mIsFocused; 83 boolean mIsFocusAnimated; 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 taskViewEnterFromAppDuration = res.getInteger( 317 R.integer.recents_task_enter_from_app_duration); 318 final int taskViewEnterFromHomeDuration = res.getInteger( 319 R.integer.recents_task_enter_from_home_duration); 320 final int taskViewEnterFromHomeStaggerDelay = res.getInteger( 321 R.integer.recents_task_enter_from_home_stagger_delay); 322 final int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 323 R.dimen.recents_task_view_affiliate_group_enter_offset); 324 325 if (launchState.launchedFromAppWithThumbnail) { 326 if (mTask.isLaunchTarget) { 327 // Animate the dim/overlay 328 if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) { 329 // Animate the thumbnail alpha before the dim animation (to prevent updating the 330 // hardware layer) 331 mThumbnailView.startEnterRecentsAnimation(new Runnable() { 332 @Override 333 public void run() { 334 animateDimToProgress(taskViewEnterFromAppDuration, 335 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 336 } 337 }); 338 } else { 339 // Immediately start the dim animation 340 animateDimToProgress(taskViewEnterFromAppDuration, 341 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 342 } 343 ctx.postAnimationTrigger.increment(); 344 345 // Animate the action button in 346 fadeInActionButton(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 .setUpdateListener(null) 355 .setInterpolator(mFastOutSlowInInterpolator) 356 .setDuration(taskViewEnterFromHomeDuration) 357 .withEndAction(new Runnable() { 358 @Override 359 public void run() { 360 // Decrement the post animation trigger 361 ctx.postAnimationTrigger.decrement(); 362 } 363 }) 364 .start(); 365 ctx.postAnimationTrigger.increment(); 366 } 367 } 368 369 } else if (launchState.launchedFromHome) { 370 // Animate the tasks up 371 int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1); 372 int delay = frontIndex * taskViewEnterFromHomeStaggerDelay; 373 374 setScaleX(transform.scale); 375 setScaleY(transform.scale); 376 if (!config.fakeShadows) { 377 animate().translationZ(transform.translationZ); 378 } 379 animate() 380 .translationY(transform.translationY) 381 .setStartDelay(delay) 382 .setUpdateListener(ctx.updateListener) 383 .setInterpolator(mQuintOutInterpolator) 384 .setDuration(taskViewEnterFromHomeDuration + 385 frontIndex * taskViewEnterFromHomeStaggerDelay) 386 .withEndAction(new Runnable() { 387 @Override 388 public void run() { 389 // Decrement the post animation trigger 390 ctx.postAnimationTrigger.decrement(); 391 } 392 }) 393 .start(); 394 ctx.postAnimationTrigger.increment(); 395 } 396 } 397 398 public void fadeInActionButton(int duration) { 399 // Hide the action button 400 mActionButtonView.setAlpha(0f); 401 402 // Animate the action button in 403 mActionButtonView.animate().alpha(1f) 404 .setDuration(duration) 405 .setInterpolator(PhoneStatusBar.ALPHA_IN) 406 .start(); 407 } 408 409 /** Animates this task view as it leaves recents by pressing home. */ 410 void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 411 int taskViewExitToHomeDuration = getResources().getInteger( 412 R.integer.recents_task_exit_to_home_duration); 413 animate() 414 .translationY(ctx.offscreenTranslationY) 415 .setStartDelay(0) 416 .setUpdateListener(null) 417 .setInterpolator(mFastOutLinearInInterpolator) 418 .setDuration(taskViewExitToHomeDuration) 419 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable()) 420 .start(); 421 ctx.postAnimationTrigger.increment(); 422 } 423 424 /** Animates this task view away when dismissing all tasks. */ 425 void startDismissAllAnimation() { 426 dismissTask(); 427 } 428 429 /** Animates this task view as it exits recents */ 430 void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask, 431 boolean occludesLaunchTarget, boolean lockToTask) { 432 final int taskViewExitToAppDuration = mContext.getResources().getInteger( 433 R.integer.recents_task_exit_to_app_duration); 434 final int taskViewAffiliateGroupEnterOffset = mContext.getResources().getDimensionPixelSize( 435 R.dimen.recents_task_view_affiliate_group_enter_offset); 436 437 if (isLaunchingTask) { 438 // Animate the thumbnail alpha back into full opacity for the window animation out 439 mThumbnailView.startLaunchTaskAnimation(postAnimRunnable); 440 441 // Animate the dim 442 if (mDimAlpha > 0) { 443 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0); 444 anim.setDuration(taskViewExitToAppDuration); 445 anim.setInterpolator(mFastOutLinearInInterpolator); 446 anim.start(); 447 } 448 449 // Animate the action button away 450 if (!lockToTask) { 451 float toScale = 0.9f; 452 mActionButtonView.animate() 453 .scaleX(toScale) 454 .scaleY(toScale); 455 } 456 mActionButtonView.animate() 457 .alpha(0f) 458 .setStartDelay(0) 459 .setDuration(taskViewExitToAppDuration) 460 .setInterpolator(mFastOutLinearInInterpolator) 461 .start(); 462 } else { 463 // Hide the dismiss button 464 mHeaderView.startLaunchTaskDismissAnimation(); 465 // If this is another view in the task grouping and is in front of the launch task, 466 // animate it away first 467 if (occludesLaunchTarget) { 468 animate().alpha(0f) 469 .translationY(getTranslationY() + taskViewAffiliateGroupEnterOffset) 470 .setStartDelay(0) 471 .setUpdateListener(null) 472 .setInterpolator(mFastOutLinearInInterpolator) 473 .setDuration(taskViewExitToAppDuration) 474 .start(); 475 } 476 } 477 } 478 479 /** Animates the deletion of this task view */ 480 void startDeleteTaskAnimation(final Runnable r, int delay) { 481 int taskViewRemoveAnimDuration = getResources().getInteger( 482 R.integer.recents_animate_task_view_remove_duration); 483 int taskViewRemoveAnimTranslationXPx = getResources().getDimensionPixelSize( 484 R.dimen.recents_task_view_remove_anim_translation_x); 485 486 // Disabling clipping with the stack while the view is animating away 487 setClipViewInStack(false); 488 489 animate().translationX(taskViewRemoveAnimTranslationXPx) 490 .alpha(0f) 491 .setStartDelay(delay) 492 .setUpdateListener(null) 493 .setInterpolator(mFastOutSlowInInterpolator) 494 .setDuration(taskViewRemoveAnimDuration) 495 .withEndAction(new Runnable() { 496 @Override 497 public void run() { 498 if (r != null) { 499 r.run(); 500 } 501 502 // Re-enable clipping with the stack (we will reuse this view) 503 setClipViewInStack(true); 504 } 505 }) 506 .start(); 507 } 508 509 /** Enables/disables handling touch on this task view. */ 510 void setTouchEnabled(boolean enabled) { 511 setOnClickListener(enabled ? this : null); 512 } 513 514 /** Animates this task view if the user does not interact with the stack after a certain time. */ 515 void startNoUserInteractionAnimation() { 516 mHeaderView.startNoUserInteractionAnimation(); 517 } 518 519 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 520 void setNoUserInteractionState() { 521 mHeaderView.setNoUserInteractionState(); 522 } 523 524 /** Resets the state tracking that the user has not interacted with the stack after a certain time. */ 525 void resetNoUserInteractionState() { 526 mHeaderView.resetNoUserInteractionState(); 527 } 528 529 /** Dismisses this task. */ 530 void dismissTask() { 531 // Animate out the view and call the callback 532 final TaskView tv = this; 533 startDeleteTaskAnimation(new Runnable() { 534 @Override 535 public void run() { 536 EventBus.getDefault().send(new DismissTaskViewEvent(mTask, tv)); 537 } 538 }, 0); 539 } 540 541 /** 542 * Returns whether this view should be clipped, or any views below should clip against this 543 * view. 544 */ 545 boolean shouldClipViewInStack() { 546 // Never clip for freeform tasks or if invisible 547 if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE) { 548 return false; 549 } 550 return mClipViewInStack; 551 } 552 553 /** Sets whether this view should be clipped, or clipped against. */ 554 void setClipViewInStack(boolean clip) { 555 if (clip != mClipViewInStack) { 556 mClipViewInStack = clip; 557 if (mCb != null) { 558 mCb.onTaskViewClipStateChanged(this); 559 } 560 } 561 } 562 563 /** Sets the current task progress. */ 564 public void setTaskProgress(float p) { 565 mTaskProgress = p; 566 mViewBounds.setAlpha(p); 567 updateDimFromTaskProgress(); 568 } 569 570 /** Returns the current task progress. */ 571 public float getTaskProgress() { 572 return mTaskProgress; 573 } 574 575 /** Returns the current dim. */ 576 public void setDim(int dim) { 577 RecentsConfiguration config = Recents.getConfiguration(); 578 579 mDimAlpha = dim; 580 if (config.useHardwareLayers) { 581 // Defer setting hardware layers if we have not yet measured, or there is no dim to draw 582 if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) { 583 mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0)); 584 mDimLayerPaint.setColorFilter(mDimColorFilter); 585 mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); 586 } 587 } else { 588 float dimAlpha = mDimAlpha / 255.0f; 589 if (mThumbnailView != null) { 590 mThumbnailView.setDimAlpha(dimAlpha); 591 } 592 if (mHeaderView != null) { 593 mHeaderView.setDimAlpha(dim); 594 } 595 } 596 } 597 598 /** Returns the current dim. */ 599 public int getDim() { 600 return mDimAlpha; 601 } 602 603 /** Animates the dim to the task progress. */ 604 void animateDimToProgress(int duration, Animator.AnimatorListener postAnimRunnable) { 605 // Animate the dim into view as well 606 int toDim = getDimFromTaskProgress(); 607 if (toDim != getDim()) { 608 ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim); 609 anim.setDuration(duration); 610 if (postAnimRunnable != null) { 611 anim.addListener(postAnimRunnable); 612 } 613 anim.start(); 614 } else { 615 postAnimRunnable.onAnimationEnd(null); 616 } 617 } 618 619 /** Compute the dim as a function of the scale of this view. */ 620 int getDimFromTaskProgress() { 621 // TODO: Temporarily disable the dim on the stack 622 /* 623 float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress); 624 return (int) (dim * 255); 625 */ 626 return 0; 627 } 628 629 /** Update the dim as a function of the scale of this view. */ 630 void updateDimFromTaskProgress() { 631 setDim(getDimFromTaskProgress()); 632 } 633 634 635 @Override 636 protected void dispatchDraw(Canvas canvas) { 637 super.dispatchDraw(canvas); 638 if (Constants.DebugFlags.App.EnableFastToggleRecents && mIsFocused) { 639 Paint tmpPaint = new Paint(); 640 Rect tmpRect = new Rect(); 641 tmpRect.set(0, 0, getWidth(), getHeight()); 642 tmpPaint.setColor(0xFFFF0000); 643 tmpPaint.setStrokeWidth(35); 644 tmpPaint.setStyle(Paint.Style.STROKE); 645 canvas.drawRect(tmpRect, tmpPaint); 646 } 647 } 648 649 /**** View focus state ****/ 650 651 /** 652 * Explicitly sets the focused state of this task. 653 */ 654 public void setFocusedState(boolean isFocused, boolean animated, boolean requestViewFocus) { 655 if (DEBUG) { 656 Log.d(TAG, "setFocusedState: " + mTask.activityLabel + " focused: " + isFocused + 657 " mIsFocused: " + mIsFocused + " animated: " + animated + 658 " requestViewFocus: " + requestViewFocus + " isFocused(): " + isFocused() + 659 " isAccessibilityFocused(): " + isAccessibilityFocused()); 660 } 661 662 SystemServicesProxy ssp = Recents.getSystemServices(); 663 mIsFocused = isFocused; 664 mIsFocusAnimated = animated; 665 mHeaderView.onTaskViewFocusChanged(isFocused, animated); 666 mThumbnailView.onFocusChanged(isFocused); 667 if (isFocused) { 668 if (requestViewFocus && !isFocused()) { 669 requestFocus(); 670 } 671 if (requestViewFocus && !isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) { 672 requestAccessibilityFocus(); 673 } 674 } else { 675 if (isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) { 676 clearAccessibilityFocus(); 677 } 678 } 679 if (Constants.DebugFlags.App.EnableFastToggleRecents) { 680 invalidate(); 681 } 682 } 683 684 /** 685 * Returns whether we have explicitly been focused. 686 */ 687 public boolean isFocusedTask() { 688 return mIsFocused; 689 } 690 691 /** 692 * Returns whether this focused task is animated. 693 */ 694 public boolean isFocusAnimated() { 695 return mIsFocusAnimated; 696 } 697 698 public void disableLayersForOneFrame() { 699 mHeaderView.disableLayersForOneFrame(); 700 } 701 702 /**** TaskCallbacks Implementation ****/ 703 704 /** Binds this task view to the task */ 705 public void onTaskBound(Task t) { 706 mTask = t; 707 mTask.setCallbacks(this); 708 709 // Hide the action button if lock to app is disabled for this view 710 int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE; 711 if (mActionButtonView.getVisibility() != lockButtonVisibility) { 712 mActionButtonView.setVisibility(lockButtonVisibility); 713 requestLayout(); 714 } 715 } 716 717 @Override 718 public void onTaskDataLoaded() { 719 SystemServicesProxy ssp = Recents.getSystemServices(); 720 RecentsConfiguration config = Recents.getConfiguration(); 721 if (mThumbnailView != null && mHeaderView != null) { 722 // Bind each of the views to the new task data 723 mThumbnailView.rebindToTask(mTask); 724 mHeaderView.rebindToTask(mTask); 725 // Rebind any listeners 726 mActionButtonView.setOnClickListener(this); 727 728 // Only enable long-click if we have a freeform workspace to drag to/from, or if we 729 // aren't already docked 730 if (ssp.hasFreeformWorkspaceSupport() || !config.hasDockedTasks) { 731 setOnLongClickListener(this); 732 } else { 733 setOnLongClickListener(null); 734 } 735 } 736 mTaskDataLoaded = true; 737 } 738 739 @Override 740 public void onTaskDataUnloaded() { 741 if (mThumbnailView != null && mHeaderView != null) { 742 // Unbind each of the views from the task data and remove the task callback 743 mTask.setCallbacks(null); 744 mThumbnailView.unbindFromTask(); 745 mHeaderView.unbindFromTask(); 746 // Unbind any listeners 747 mActionButtonView.setOnClickListener(null); 748 } 749 mTaskDataLoaded = false; 750 } 751 752 @Override 753 public void onTaskStackIdChanged() { 754 mHeaderView.rebindToTask(mTask); 755 } 756 757 /**** View.OnClickListener Implementation ****/ 758 759 @Override 760 public void onClick(final View v) { 761 if (v == mActionButtonView) { 762 // Reset the translation of the action button before we animate it out 763 mActionButtonView.setTranslationZ(0f); 764 } 765 if (mCb != null) { 766 mCb.onTaskViewClicked(this, mTask, (v == mActionButtonView)); 767 } 768 } 769 770 /**** View.OnLongClickListener Implementation ****/ 771 772 @Override 773 public boolean onLongClick(View v) { 774 if (v == this) { 775 // Start listening for drag events 776 setClipViewInStack(false); 777 778 final float finalScale = getScaleX() * 1.05f; 779 final int width = getWidth(); 780 final int height = getHeight(); 781 Bitmap dragBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 782 Canvas c = new Canvas(dragBitmap); 783 mThumbnailView.draw(c); 784 mHeaderView.draw(c); 785 c.setBitmap(null); 786 787 // The downTouchPos is relative to the currently transformed TaskView, but we will be 788 // dragging a copy of the full task view, which makes it easier for us to animate them 789 // when the user drops 790 mDownTouchPos.x += ((1f - getScaleX()) * width) / 2; 791 mDownTouchPos.y += ((1f - getScaleY()) * height) / 2; 792 793 // Initiate the drag 794 final DragView dragView = new DragView(getContext(), dragBitmap, mDownTouchPos); 795 dragView.setOutlineProvider(new ViewOutlineProvider() { 796 @Override 797 public void getOutline(View view, Outline outline) { 798 outline.setRect(0, 0, width, height); 799 } 800 }); 801 dragView.setScaleX(getScaleX()); 802 dragView.setScaleY(getScaleY()); 803 dragView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 804 @Override 805 public void onViewAttachedToWindow(View v) { 806 // Hide this task view after the drag view is attached 807 setVisibility(View.INVISIBLE); 808 // Animate the alpha slightly to indicate dragging 809 dragView.setElevation(getElevation()); 810 dragView.setTranslationZ(getTranslationZ()); 811 dragView.animate() 812 .scaleX(finalScale) 813 .scaleY(finalScale) 814 .setDuration(175) 815 .setInterpolator(mFastOutSlowInInterpolator) 816 .start(); 817 } 818 819 @Override 820 public void onViewDetachedFromWindow(View v) { 821 // Do nothing 822 } 823 }); 824 EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); 825 EventBus.getDefault().send(new DragStartEvent(mTask, this, dragView)); 826 return true; 827 } 828 return false; 829 } 830 831 /**** Events ****/ 832 833 public final void onBusEvent(DragEndEvent event) { 834 if (!(event.dropTarget instanceof TaskStack.DockState)) { 835 event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 836 @Override 837 public void run() { 838 // Show this task view 839 setVisibility(View.VISIBLE); 840 841 // Animate the drag view back from where it is, to the view location, then after 842 // it returns, update the clip state 843 setClipViewInStack(true); 844 } 845 }); 846 } 847 EventBus.getDefault().unregister(this); 848 } 849} 850