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