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