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