TaskView.java revision bb410951b9ae373999c058fa141b3522ba169960
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.AnimatorSet; 22import android.animation.ObjectAnimator; 23import android.animation.ValueAnimator; 24import android.content.Context; 25import android.content.res.Resources; 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.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(3f); 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 mClipViewInStack; 82 AnimateableViewBounds mViewBounds; 83 private AnimatorSet mClipAnimation; 84 85 View mContent; 86 TaskViewThumbnail mThumbnailView; 87 TaskViewHeader mHeaderView; 88 View mActionButtonView; 89 TaskViewCallbacks mCb; 90 91 Point mDownTouchPos = new Point(); 92 93 Interpolator mFastOutSlowInInterpolator; 94 Interpolator mFastOutLinearInInterpolator; 95 Interpolator mQuintOutInterpolator; 96 97 // Optimizations 98 ValueAnimator.AnimatorUpdateListener mUpdateDimListener = 99 new ValueAnimator.AnimatorUpdateListener() { 100 @Override 101 public void onAnimationUpdate(ValueAnimator animation) { 102 setTaskProgress((Float) animation.getAnimatedValue()); 103 } 104 }; 105 106 107 public TaskView(Context context) { 108 this(context, null); 109 } 110 111 public TaskView(Context context, AttributeSet attrs) { 112 this(context, attrs, 0); 113 } 114 115 public TaskView(Context context, AttributeSet attrs, int defStyleAttr) { 116 this(context, attrs, defStyleAttr, 0); 117 } 118 119 public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 120 super(context, attrs, defStyleAttr, defStyleRes); 121 RecentsConfiguration config = Recents.getConfiguration(); 122 Resources res = context.getResources(); 123 mMaxDimScale = res.getInteger(R.integer.recents_max_task_stack_view_dim) / 255f; 124 mClipViewInStack = true; 125 mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize( 126 R.dimen.recents_task_view_rounded_corners_radius)); 127 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, 128 com.android.internal.R.interpolator.fast_out_slow_in); 129 mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, 130 com.android.internal.R.interpolator.fast_out_linear_in); 131 mQuintOutInterpolator = AnimationUtils.loadInterpolator(context, 132 com.android.internal.R.interpolator.decelerate_quint); 133 setTaskProgress(getTaskProgress()); 134 setDim(getDim()); 135 if (config.fakeShadows) { 136 setBackground(new FakeShadowDrawable(res, config)); 137 } 138 setOutlineProvider(mViewBounds); 139 } 140 141 /** Set callback */ 142 void setCallbacks(TaskViewCallbacks cb) { 143 mCb = cb; 144 } 145 146 /** Resets this TaskView for reuse. */ 147 void reset() { 148 resetViewProperties(); 149 resetNoUserInteractionState(); 150 setClipViewInStack(false); 151 setCallbacks(null); 152 } 153 154 /** Gets the task */ 155 public Task getTask() { 156 return mTask; 157 } 158 159 /** Returns the view bounds. */ 160 AnimateableViewBounds getViewBounds() { 161 return mViewBounds; 162 } 163 164 @Override 165 protected void onFinishInflate() { 166 // Bind the views 167 mContent = findViewById(R.id.task_view_content); 168 mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar); 169 mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail); 170 mActionButtonView = findViewById(R.id.lock_to_app_fab); 171 mActionButtonView.setOutlineProvider(new ViewOutlineProvider() { 172 @Override 173 public void getOutline(View view, Outline outline) { 174 // Set the outline to match the FAB background 175 outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight()); 176 } 177 }); 178 mActionButtonTranslationZ = mActionButtonView.getTranslationZ(); 179 } 180 181 @Override 182 public boolean onInterceptTouchEvent(MotionEvent ev) { 183 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 184 mDownTouchPos.set((int) (ev.getX() * getScaleX()), (int) (ev.getY() * getScaleY())); 185 } 186 return super.onInterceptTouchEvent(ev); 187 } 188 189 @Override 190 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 191 int width = MeasureSpec.getSize(widthMeasureSpec); 192 int height = MeasureSpec.getSize(heightMeasureSpec); 193 194 int widthWithoutPadding = width - mPaddingLeft - mPaddingRight; 195 int heightWithoutPadding = height - mPaddingTop - mPaddingBottom; 196 int taskBarHeight = getResources().getDimensionPixelSize(R.dimen.recents_task_bar_height); 197 198 // Measure the content 199 mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 200 MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY)); 201 202 // Measure the bar view, and action button 203 mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 204 MeasureSpec.makeMeasureSpec(taskBarHeight, MeasureSpec.EXACTLY)); 205 mActionButtonView.measure( 206 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST), 207 MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST)); 208 // Measure the thumbnail to be square 209 mThumbnailView.measure( 210 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 211 MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY)); 212 mThumbnailView.updateClipToTaskBar(mHeaderView); 213 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 clipBottom, 220 int duration, Interpolator interpolator, 221 ValueAnimator.AnimatorUpdateListener updateCallback) { 222 RecentsConfiguration config = Recents.getConfiguration(); 223 Utilities.cancelAnimationWithoutCallbacks(mClipAnimation); 224 225 // Apply the transform 226 toTransform.applyToTaskView(this, duration, interpolator, false, 227 !config.fakeShadows, updateCallback); 228 229 // Update the clipping 230 if (duration > 0) { 231 mClipAnimation = new AnimatorSet(); 232 mClipAnimation.playTogether( 233 ObjectAnimator.ofInt(mViewBounds, AnimateableViewBounds.CLIP_BOTTOM, 234 mViewBounds.getClipBottom(), clipBottom), 235 ObjectAnimator.ofInt(this, TaskViewTransform.LEFT, getLeft(), 236 (int) toTransform.rect.left), 237 ObjectAnimator.ofInt(this, TaskViewTransform.TOP, getTop(), 238 (int) toTransform.rect.top), 239 ObjectAnimator.ofInt(this, TaskViewTransform.RIGHT, getRight(), 240 (int) toTransform.rect.right), 241 ObjectAnimator.ofInt(this, TaskViewTransform.BOTTOM, getBottom(), 242 (int) toTransform.rect.bottom), 243 ObjectAnimator.ofFloat(mThumbnailView, TaskViewThumbnail.BITMAP_SCALE, 244 mThumbnailView.getBitmapScale(), toTransform.thumbnailScale)); 245 mClipAnimation.setStartDelay(toTransform.startDelay); 246 mClipAnimation.setDuration(duration); 247 mClipAnimation.setInterpolator(interpolator); 248 mClipAnimation.start(); 249 } else { 250 mViewBounds.setClipBottom(clipBottom, false /* forceUpdate */); 251 mThumbnailView.setBitmapScale(toTransform.thumbnailScale); 252 setLeftTopRightBottom((int) toTransform.rect.left, (int) toTransform.rect.top, 253 (int) toTransform.rect.right, (int) toTransform.rect.bottom); 254 } 255 if (!config.useHardwareLayers) { 256 mThumbnailView.updateThumbnailVisibility(clipBottom - getPaddingBottom()); 257 } 258 259 // Update the task progress 260 Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator); 261 if (duration <= 0) { 262 setTaskProgress(toTransform.p); 263 } else { 264 mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p); 265 mTaskProgressAnimator.setDuration(duration); 266 mTaskProgressAnimator.addUpdateListener(mUpdateDimListener); 267 mTaskProgressAnimator.start(); 268 } 269 } 270 271 /** Resets this view's properties */ 272 void resetViewProperties() { 273 setDim(0); 274 setLayerType(View.LAYER_TYPE_NONE, null); 275 setVisibility(View.VISIBLE); 276 getViewBounds().reset(); 277 TaskViewTransform.reset(this); 278 if (mActionButtonView != null) { 279 mActionButtonView.setScaleX(1f); 280 mActionButtonView.setScaleY(1f); 281 mActionButtonView.setAlpha(1f); 282 mActionButtonView.setTranslationZ(mActionButtonTranslationZ); 283 } 284 } 285 286 /** Prepares this task view for the enter-recents animations. This is called earlier in the 287 * first layout because the actual animation into recents may take a long time. */ 288 void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, boolean hideTask, 289 boolean occludesLaunchTarget, int offscreenY) { 290 RecentsConfiguration config = Recents.getConfiguration(); 291 RecentsActivityLaunchState launchState = config.getLaunchState(); 292 int initialDim = getDim(); 293 if (hideTask) { 294 setVisibility(View.INVISIBLE); 295 } else if (launchState.launchedHasConfigurationChanged) { 296 // Just load the views as-is 297 } else if (launchState.launchedFromAppWithThumbnail) { 298 if (isTaskViewLaunchTargetTask) { 299 // Set the dim to 0 so we can animate it in 300 initialDim = 0; 301 // Hide the action button 302 mActionButtonView.setAlpha(0f); 303 } else if (occludesLaunchTarget) { 304 // Move the task view off screen (below) so we can animate it in 305 setTranslationY(offscreenY); 306 } 307 308 } else if (launchState.launchedFromHome) { 309 // Move the task view off screen (below) so we can animate it in 310 setTranslationY(offscreenY); 311 setTranslationZ(0); 312 setScaleX(1f); 313 setScaleY(1f); 314 } 315 // Apply the current dim 316 setDim(initialDim); 317 } 318 319 /** Animates this task view as it enters recents */ 320 void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) { 321 RecentsConfiguration config = Recents.getConfiguration(); 322 RecentsActivityLaunchState launchState = config.getLaunchState(); 323 Resources res = mContext.getResources(); 324 final TaskViewTransform transform = ctx.currentTaskTransform; 325 final int taskViewEnterFromAppDuration = res.getInteger( 326 R.integer.recents_task_enter_from_app_duration); 327 final int taskViewEnterFromHomeDuration = res.getInteger( 328 R.integer.recents_task_enter_from_home_duration); 329 final int taskViewEnterFromHomeStaggerDelay = res.getInteger( 330 R.integer.recents_task_enter_from_home_stagger_delay); 331 final int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 332 R.dimen.recents_task_view_affiliate_group_enter_offset); 333 334 if (launchState.launchedFromAppWithThumbnail) { 335 if (mTask.isLaunchTarget) { 336 // Immediately start the dim animation 337 animateDimToProgress(taskViewEnterFromAppDuration, 338 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 339 ctx.postAnimationTrigger.increment(); 340 341 // Animate the action button in 342 fadeInActionButton(taskViewEnterFromAppDuration); 343 } else { 344 // Animate the task up if it was occluding the launch target 345 if (ctx.currentTaskOccludesLaunchTarget) { 346 setTranslationY(taskViewAffiliateGroupEnterOffset); 347 setAlpha(0f); 348 animate().alpha(1f) 349 .translationY(0) 350 .setUpdateListener(null) 351 .setListener(new AnimatorListenerAdapter() { 352 private boolean hasEnded; 353 354 // We use the animation listener instead of withEndAction() to 355 // ensure that onAnimationEnd() is called when the animator is 356 // cancelled 357 @Override 358 public void onAnimationEnd(Animator animation) { 359 if (hasEnded) return; 360 ctx.postAnimationTrigger.decrement(); 361 hasEnded = true; 362 } 363 }) 364 .setInterpolator(mFastOutSlowInInterpolator) 365 .setDuration(taskViewEnterFromHomeDuration) 366 .start(); 367 ctx.postAnimationTrigger.increment(); 368 } 369 } 370 371 } else if (launchState.launchedFromHome) { 372 // Animate the tasks up 373 int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1); 374 int delay = frontIndex * taskViewEnterFromHomeStaggerDelay; 375 376 setScaleX(transform.scale); 377 setScaleY(transform.scale); 378 if (!config.fakeShadows) { 379 animate().translationZ(transform.translationZ); 380 } 381 animate() 382 .translationY(0) 383 .setStartDelay(delay) 384 .setUpdateListener(ctx.updateListener) 385 .setListener(new AnimatorListenerAdapter() { 386 private boolean hasEnded; 387 388 // We use the animation listener instead of withEndAction() to ensure that 389 // onAnimationEnd() is called when the animator is cancelled 390 @Override 391 public void onAnimationEnd(Animator animation) { 392 if (hasEnded) return; 393 ctx.postAnimationTrigger.decrement(); 394 hasEnded = true; 395 } 396 }) 397 .setInterpolator(mQuintOutInterpolator) 398 .setDuration(taskViewEnterFromHomeDuration + 399 frontIndex * taskViewEnterFromHomeStaggerDelay) 400 .start(); 401 ctx.postAnimationTrigger.increment(); 402 } 403 } 404 405 public void cancelEnterRecentsAnimation() { 406 animate().cancel(); 407 } 408 409 public void fadeInActionButton(int duration) { 410 // Hide the action button 411 mActionButtonView.setAlpha(0f); 412 413 // Animate the action button in 414 mActionButtonView.animate().alpha(1f) 415 .setDuration(duration) 416 .setInterpolator(PhoneStatusBar.ALPHA_IN) 417 .start(); 418 } 419 420 /** Animates this task view as it leaves recents by pressing home. */ 421 void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 422 int taskViewExitToHomeDuration = getResources().getInteger( 423 R.integer.recents_task_exit_to_home_duration); 424 animate() 425 .translationY(ctx.offscreenTranslationY) 426 .setStartDelay(0) 427 .setUpdateListener(null) 428 .setListener(null) 429 .setInterpolator(mFastOutLinearInInterpolator) 430 .setDuration(taskViewExitToHomeDuration) 431 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable()) 432 .start(); 433 ctx.postAnimationTrigger.increment(); 434 } 435 436 /** Animates this task view as it exits recents */ 437 void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask, 438 boolean occludesLaunchTarget, boolean lockToTask) { 439 final int taskViewExitToAppDuration = mContext.getResources().getInteger( 440 R.integer.recents_task_exit_to_app_duration); 441 final int taskViewAffiliateGroupEnterOffset = mContext.getResources().getDimensionPixelSize( 442 R.dimen.recents_task_view_affiliate_group_enter_offset); 443 444 if (isLaunchingTask) { 445 // Animate the dim 446 if (mDimAlpha > 0) { 447 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0); 448 anim.setDuration(taskViewExitToAppDuration); 449 anim.setInterpolator(mFastOutLinearInInterpolator); 450 anim.start(); 451 } 452 453 // Animate the action button away 454 if (!lockToTask) { 455 float toScale = 0.9f; 456 mActionButtonView.animate() 457 .scaleX(toScale) 458 .scaleY(toScale); 459 } 460 mActionButtonView.animate() 461 .alpha(0f) 462 .setStartDelay(0) 463 .setDuration(taskViewExitToAppDuration) 464 .setInterpolator(mFastOutLinearInInterpolator) 465 .withEndAction(postAnimRunnable) 466 .start(); 467 } else { 468 // Hide the dismiss button 469 mHeaderView.startLaunchTaskDismissAnimation(postAnimRunnable); 470 // If this is another view in the task grouping and is in front of the launch task, 471 // animate it away first 472 if (occludesLaunchTarget) { 473 animate().alpha(0f) 474 .translationY(getTranslationY() + taskViewAffiliateGroupEnterOffset) 475 .setStartDelay(0) 476 .setUpdateListener(null) 477 .setListener(null) 478 .setInterpolator(mFastOutLinearInInterpolator) 479 .setDuration(taskViewExitToAppDuration) 480 .start(); 481 } 482 } 483 } 484 485 /** Animates the deletion of this task view */ 486 void startDeleteTaskAnimation(final Runnable r, int delay) { 487 int taskViewRemoveAnimDuration = getResources().getInteger( 488 R.integer.recents_animate_task_view_remove_duration); 489 int taskViewRemoveAnimTranslationXPx = getResources().getDimensionPixelSize( 490 R.dimen.recents_task_view_remove_anim_translation_x); 491 492 // Disabling clipping with the stack while the view is animating away 493 setClipViewInStack(false); 494 495 animate().translationX(taskViewRemoveAnimTranslationXPx) 496 .alpha(0f) 497 .setStartDelay(delay) 498 .setUpdateListener(null) 499 .setListener(null) 500 .setInterpolator(mFastOutSlowInInterpolator) 501 .setDuration(taskViewRemoveAnimDuration) 502 .withEndAction(new Runnable() { 503 @Override 504 public void run() { 505 if (r != null) { 506 r.run(); 507 } 508 509 // Re-enable clipping with the stack (we will reuse this view) 510 setClipViewInStack(true); 511 } 512 }) 513 .start(); 514 } 515 516 /** Enables/disables handling touch on this task view. */ 517 void setTouchEnabled(boolean enabled) { 518 setOnClickListener(enabled ? this : null); 519 } 520 521 /** Animates this task view if the user does not interact with the stack after a certain time. */ 522 void startNoUserInteractionAnimation() { 523 mHeaderView.startNoUserInteractionAnimation(); 524 } 525 526 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 527 void setNoUserInteractionState() { 528 mHeaderView.setNoUserInteractionState(); 529 } 530 531 /** Resets the state tracking that the user has not interacted with the stack after a certain time. */ 532 void resetNoUserInteractionState() { 533 mHeaderView.resetNoUserInteractionState(); 534 } 535 536 /** Dismisses this task. */ 537 void dismissTask() { 538 // Animate out the view and call the callback 539 final TaskView tv = this; 540 startDeleteTaskAnimation(new Runnable() { 541 @Override 542 public void run() { 543 EventBus.getDefault().send(new DismissTaskViewEvent(mTask, tv)); 544 } 545 }, 0); 546 } 547 548 /** 549 * Returns whether this view should be clipped, or any views below should clip against this 550 * view. 551 */ 552 boolean shouldClipViewInStack() { 553 // Never clip for freeform tasks or if invisible 554 if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE) { 555 return false; 556 } 557 return mClipViewInStack; 558 } 559 560 /** Sets whether this view should be clipped, or clipped against. */ 561 void setClipViewInStack(boolean clip) { 562 if (clip != mClipViewInStack) { 563 mClipViewInStack = clip; 564 if (mCb != null) { 565 mCb.onTaskViewClipStateChanged(this); 566 } 567 } 568 } 569 570 /** Sets the current task progress. */ 571 public void setTaskProgress(float p) { 572 mTaskProgress = p; 573 mViewBounds.setAlpha(p); 574 updateDimFromTaskProgress(); 575 } 576 577 /** Returns the current task progress. */ 578 public float getTaskProgress() { 579 return mTaskProgress; 580 } 581 582 /** Returns the current dim. */ 583 public void setDim(int dim) { 584 RecentsConfiguration config = Recents.getConfiguration(); 585 586 mDimAlpha = dim; 587 if (config.useHardwareLayers) { 588 // Defer setting hardware layers if we have not yet measured, or there is no dim to draw 589 if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) { 590 mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0)); 591 mDimLayerPaint.setColorFilter(mDimColorFilter); 592 mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); 593 } 594 } else { 595 float dimAlpha = mDimAlpha / 255.0f; 596 if (mThumbnailView != null) { 597 mThumbnailView.setDimAlpha(dimAlpha); 598 } 599 if (mHeaderView != null) { 600 mHeaderView.setDimAlpha(dim); 601 } 602 } 603 } 604 605 /** Returns the current dim. */ 606 public int getDim() { 607 return mDimAlpha; 608 } 609 610 /** Animates the dim to the task progress. */ 611 void animateDimToProgress(int duration, Animator.AnimatorListener postAnimRunnable) { 612 // Animate the dim into view as well 613 int toDim = getDimFromTaskProgress(); 614 if (toDim != getDim()) { 615 ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim); 616 anim.setDuration(duration); 617 if (postAnimRunnable != null) { 618 anim.addListener(postAnimRunnable); 619 } 620 anim.start(); 621 } else { 622 postAnimRunnable.onAnimationEnd(null); 623 } 624 } 625 626 /** Compute the dim as a function of the scale of this view. */ 627 int getDimFromTaskProgress() { 628 float x = mTaskProgress < 0 629 ? 1f 630 : mDimInterpolator.getInterpolation(1f - mTaskProgress); 631 float dim = mMaxDimScale * x; 632 return (int) (dim * 255); 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 " animated: " + animated + " requestViewFocus: " + requestViewFocus + 649 " isFocused(): " + isFocused() + 650 " isAccessibilityFocused(): " + isAccessibilityFocused()); 651 } 652 653 SystemServicesProxy ssp = Recents.getSystemServices(); 654 mHeaderView.onTaskViewFocusChanged(isFocused, animated); 655 if (isFocused) { 656 if (requestViewFocus && !isFocused()) { 657 requestFocus(); 658 } 659 if (requestViewFocus && !isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) { 660 requestAccessibilityFocus(); 661 } 662 } else { 663 if (isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) { 664 clearAccessibilityFocus(); 665 } 666 } 667 } 668 669 /**** TaskCallbacks Implementation ****/ 670 671 /** Binds this task view to the task */ 672 public void onTaskBound(Task t) { 673 mTask = t; 674 mTask.setCallbacks(this); 675 676 // Hide the action button if lock to app is disabled for this view 677 int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE; 678 if (mActionButtonView.getVisibility() != lockButtonVisibility) { 679 mActionButtonView.setVisibility(lockButtonVisibility); 680 requestLayout(); 681 } 682 } 683 684 @Override 685 public void onTaskDataLoaded() { 686 if (mThumbnailView != null && mHeaderView != null) { 687 // Bind each of the views to the new task data 688 mThumbnailView.rebindToTask(mTask); 689 mHeaderView.rebindToTask(mTask); 690 691 // Rebind any listeners 692 mActionButtonView.setOnClickListener(this); 693 setOnLongClickListener(this); 694 } 695 mTaskDataLoaded = true; 696 } 697 698 @Override 699 public void onTaskDataUnloaded() { 700 if (mThumbnailView != null && mHeaderView != null) { 701 // Unbind each of the views from the task data and remove the task callback 702 mTask.setCallbacks(null); 703 mThumbnailView.unbindFromTask(); 704 mHeaderView.unbindFromTask(); 705 // Unbind any listeners 706 mActionButtonView.setOnClickListener(null); 707 } 708 mTaskDataLoaded = false; 709 } 710 711 @Override 712 public void onTaskStackIdChanged() { 713 mHeaderView.rebindToTask(mTask); 714 } 715 716 /**** View.OnClickListener Implementation ****/ 717 718 @Override 719 public void onClick(final View v) { 720 if (v == mActionButtonView) { 721 // Reset the translation of the action button before we animate it out 722 mActionButtonView.setTranslationZ(0f); 723 } 724 if (mCb != null) { 725 mCb.onTaskViewClicked(this, mTask, (v == mActionButtonView)); 726 } 727 } 728 729 /**** View.OnLongClickListener Implementation ****/ 730 731 @Override 732 public boolean onLongClick(View v) { 733 SystemServicesProxy ssp = Recents.getSystemServices(); 734 // Since we are clipping the view to the bounds, manually do the hit test 735 Rect clipBounds = new Rect(mViewBounds.mClipBounds); 736 clipBounds.scale(getScaleX()); 737 boolean inBounds = clipBounds.contains(mDownTouchPos.x, mDownTouchPos.y); 738 if (v == this && inBounds && !ssp.hasDockedTask()) { 739 // Start listening for drag events 740 setClipViewInStack(false); 741 742 // Enlarge the view slightly 743 final float finalScale = getScaleX() * 1.05f; 744 animate() 745 .scaleX(finalScale) 746 .scaleY(finalScale) 747 .setDuration(175) 748 .setUpdateListener(null) 749 .setListener(null) 750 .setInterpolator(mFastOutSlowInInterpolator) 751 .start(); 752 753 mDownTouchPos.x += ((1f - getScaleX()) * getWidth()) / 2; 754 mDownTouchPos.y += ((1f - getScaleY()) * getHeight()) / 2; 755 756 EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); 757 EventBus.getDefault().send(new DragStartEvent(mTask, this, mDownTouchPos)); 758 return true; 759 } 760 return false; 761 } 762 763 /**** Events ****/ 764 765 public final void onBusEvent(DragEndEvent event) { 766 if (!(event.dropTarget instanceof TaskStack.DockState)) { 767 event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 768 @Override 769 public void run() { 770 // Animate the drag view back from where it is, to the view location, then after 771 // it returns, update the clip state 772 setClipViewInStack(true); 773 } 774 }); 775 } 776 EventBus.getDefault().unregister(this); 777 } 778} 779