TaskView.java revision de0591a0788c96757cce1eed93a7e8bc8bd0ef01
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 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 183 super.onSizeChanged(w, h, oldw, oldh); 184 mHeaderView.onTaskViewSizeChanged(w, h); 185 mThumbnailView.onTaskViewSizeChanged(w, h); 186 } 187 188 @Override 189 public boolean onInterceptTouchEvent(MotionEvent ev) { 190 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 191 mDownTouchPos.set((int) (ev.getX() * getScaleX()), (int) (ev.getY() * getScaleY())); 192 } 193 return super.onInterceptTouchEvent(ev); 194 } 195 196 @Override 197 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 198 int width = MeasureSpec.getSize(widthMeasureSpec); 199 int height = MeasureSpec.getSize(heightMeasureSpec); 200 201 int widthWithoutPadding = width - mPaddingLeft - mPaddingRight; 202 int heightWithoutPadding = height - mPaddingTop - mPaddingBottom; 203 int taskBarHeight = getResources().getDimensionPixelSize(R.dimen.recents_task_bar_height); 204 205 // Measure the content 206 mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 207 MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY)); 208 209 // Measure the bar view, and action button 210 mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 211 MeasureSpec.makeMeasureSpec(taskBarHeight, MeasureSpec.EXACTLY)); 212 mActionButtonView.measure( 213 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST), 214 MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST)); 215 // Measure the thumbnail to be square 216 mThumbnailView.measure( 217 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 218 MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY)); 219 mThumbnailView.updateClipToTaskBar(mHeaderView); 220 221 setMeasuredDimension(width, height); 222 invalidateOutline(); 223 } 224 225 /** Synchronizes this view's properties with the task's transform */ 226 void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int clipBottom, 227 int duration, Interpolator interpolator, 228 ValueAnimator.AnimatorUpdateListener updateCallback) { 229 RecentsConfiguration config = Recents.getConfiguration(); 230 Utilities.cancelAnimationWithoutCallbacks(mClipAnimation); 231 232 // Apply the transform 233 toTransform.applyToTaskView(this, duration, interpolator, false, 234 !config.fakeShadows, updateCallback); 235 236 // Update the clipping 237 if (duration > 0) { 238 mClipAnimation = new AnimatorSet(); 239 mClipAnimation.playTogether( 240 ObjectAnimator.ofInt(mViewBounds, AnimateableViewBounds.CLIP_BOTTOM, 241 mViewBounds.getClipBottom(), clipBottom), 242 ObjectAnimator.ofInt(this, TaskViewTransform.LEFT, getLeft(), 243 (int) toTransform.rect.left), 244 ObjectAnimator.ofInt(this, TaskViewTransform.TOP, getTop(), 245 (int) toTransform.rect.top), 246 ObjectAnimator.ofInt(this, TaskViewTransform.RIGHT, getRight(), 247 (int) toTransform.rect.right), 248 ObjectAnimator.ofInt(this, TaskViewTransform.BOTTOM, getBottom(), 249 (int) toTransform.rect.bottom), 250 ObjectAnimator.ofFloat(mThumbnailView, TaskViewThumbnail.BITMAP_SCALE, 251 mThumbnailView.getBitmapScale(), toTransform.thumbnailScale)); 252 mClipAnimation.setStartDelay(toTransform.startDelay); 253 mClipAnimation.setDuration(duration); 254 mClipAnimation.setInterpolator(interpolator); 255 mClipAnimation.start(); 256 } else { 257 mViewBounds.setClipBottom(clipBottom, false /* forceUpdate */); 258 mThumbnailView.setBitmapScale(toTransform.thumbnailScale); 259 setLeftTopRightBottom((int) toTransform.rect.left, (int) toTransform.rect.top, 260 (int) toTransform.rect.right, (int) toTransform.rect.bottom); 261 } 262 if (!config.useHardwareLayers) { 263 mThumbnailView.updateThumbnailVisibility(clipBottom - getPaddingBottom()); 264 } 265 266 // Update the task progress 267 Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator); 268 if (duration <= 0) { 269 setTaskProgress(toTransform.p); 270 } else { 271 mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p); 272 mTaskProgressAnimator.setDuration(duration); 273 mTaskProgressAnimator.addUpdateListener(mUpdateDimListener); 274 mTaskProgressAnimator.start(); 275 } 276 } 277 278 /** Resets this view's properties */ 279 void resetViewProperties() { 280 setDim(0); 281 setLayerType(View.LAYER_TYPE_NONE, null); 282 setVisibility(View.VISIBLE); 283 getViewBounds().reset(); 284 TaskViewTransform.reset(this); 285 if (mActionButtonView != null) { 286 mActionButtonView.setScaleX(1f); 287 mActionButtonView.setScaleY(1f); 288 mActionButtonView.setAlpha(1f); 289 mActionButtonView.setTranslationZ(mActionButtonTranslationZ); 290 } 291 } 292 293 /** Prepares this task view for the enter-recents animations. This is called earlier in the 294 * first layout because the actual animation into recents may take a long time. */ 295 void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, boolean hideTask, 296 boolean occludesLaunchTarget, int offscreenY) { 297 RecentsConfiguration config = Recents.getConfiguration(); 298 RecentsActivityLaunchState launchState = config.getLaunchState(); 299 int initialDim = getDim(); 300 if (hideTask) { 301 setVisibility(View.INVISIBLE); 302 } else if (launchState.launchedHasConfigurationChanged) { 303 // Just load the views as-is 304 } else if (launchState.launchedFromAppWithThumbnail) { 305 if (isTaskViewLaunchTargetTask) { 306 // Set the dim to 0 so we can animate it in 307 initialDim = 0; 308 // Hide the action button 309 mActionButtonView.setAlpha(0f); 310 } else if (occludesLaunchTarget) { 311 // Move the task view off screen (below) so we can animate it in 312 setTranslationY(offscreenY); 313 } 314 315 } else if (launchState.launchedFromHome) { 316 // Move the task view off screen (below) so we can animate it in 317 setTranslationY(offscreenY); 318 setTranslationZ(0); 319 setScaleX(1f); 320 setScaleY(1f); 321 } 322 // Apply the current dim 323 setDim(initialDim); 324 } 325 326 /** Animates this task view as it enters recents */ 327 void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) { 328 RecentsConfiguration config = Recents.getConfiguration(); 329 RecentsActivityLaunchState launchState = config.getLaunchState(); 330 Resources res = mContext.getResources(); 331 final TaskViewTransform transform = ctx.currentTaskTransform; 332 final int taskViewEnterFromAppDuration = res.getInteger( 333 R.integer.recents_task_enter_from_app_duration); 334 final int taskViewEnterFromHomeDuration = res.getInteger( 335 R.integer.recents_task_enter_from_home_duration); 336 final int taskViewEnterFromHomeStaggerDelay = res.getInteger( 337 R.integer.recents_task_enter_from_home_stagger_delay); 338 final int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 339 R.dimen.recents_task_view_affiliate_group_enter_offset); 340 341 if (launchState.launchedFromAppWithThumbnail) { 342 if (mTask.isLaunchTarget) { 343 // Immediately start the dim animation 344 animateDimToProgress(taskViewEnterFromAppDuration, 345 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 346 ctx.postAnimationTrigger.increment(); 347 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 mHeaderView.onTaskViewFocusChanged(isFocused, animated); 662 if (isFocused) { 663 if (requestViewFocus && !isFocused()) { 664 requestFocus(); 665 } 666 if (requestViewFocus && !isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) { 667 requestAccessibilityFocus(); 668 } 669 } else { 670 if (isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) { 671 clearAccessibilityFocus(); 672 } 673 } 674 } 675 676 /**** TaskCallbacks Implementation ****/ 677 678 /** Binds this task view to the task */ 679 public void onTaskBound(Task t) { 680 mTask = t; 681 mTask.setCallbacks(this); 682 683 // Hide the action button if lock to app is disabled for this view 684 int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE; 685 if (mActionButtonView.getVisibility() != lockButtonVisibility) { 686 mActionButtonView.setVisibility(lockButtonVisibility); 687 requestLayout(); 688 } 689 } 690 691 @Override 692 public void onTaskDataLoaded() { 693 if (mThumbnailView != null && mHeaderView != null) { 694 // Bind each of the views to the new task data 695 mThumbnailView.rebindToTask(mTask); 696 mHeaderView.rebindToTask(mTask); 697 698 // Rebind any listeners 699 mActionButtonView.setOnClickListener(this); 700 setOnLongClickListener(this); 701 } 702 mTaskDataLoaded = true; 703 } 704 705 @Override 706 public void onTaskDataUnloaded() { 707 if (mThumbnailView != null && mHeaderView != null) { 708 // Unbind each of the views from the task data and remove the task callback 709 mTask.setCallbacks(null); 710 mThumbnailView.unbindFromTask(); 711 mHeaderView.unbindFromTask(); 712 // Unbind any listeners 713 mActionButtonView.setOnClickListener(null); 714 } 715 mTaskDataLoaded = false; 716 } 717 718 @Override 719 public void onTaskStackIdChanged() { 720 mHeaderView.rebindToTask(mTask); 721 } 722 723 /**** View.OnClickListener Implementation ****/ 724 725 @Override 726 public void onClick(final View v) { 727 if (v == mActionButtonView) { 728 // Reset the translation of the action button before we animate it out 729 mActionButtonView.setTranslationZ(0f); 730 } 731 if (mCb != null) { 732 mCb.onTaskViewClicked(this, mTask, (v == mActionButtonView)); 733 } 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.postAnimationTrigger.addLastDecrementRunnable(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