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