TaskView.java revision bf8e290376b8ee6d04c330b22f5275e82103def3
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 static android.app.ActivityManager.StackId.INVALID_STACK_ID; 20 21import android.animation.Animator; 22import android.animation.AnimatorSet; 23import android.animation.ObjectAnimator; 24import android.animation.ValueAnimator; 25import android.app.ActivityManager; 26import android.content.Context; 27import android.content.res.Resources; 28import android.graphics.Outline; 29import android.graphics.Point; 30import android.graphics.Rect; 31import android.util.AttributeSet; 32import android.util.FloatProperty; 33import android.util.Property; 34import android.view.MotionEvent; 35import android.view.View; 36import android.view.ViewDebug; 37import android.view.ViewOutlineProvider; 38import android.widget.TextView; 39import android.widget.Toast; 40 41import com.android.internal.logging.MetricsLogger; 42import com.android.internal.logging.MetricsProto.MetricsEvent; 43import com.android.systemui.Interpolators; 44import com.android.systemui.R; 45import com.android.systemui.recents.Recents; 46import com.android.systemui.recents.RecentsActivity; 47import com.android.systemui.recents.RecentsConfiguration; 48import com.android.systemui.recents.events.EventBus; 49import com.android.systemui.recents.events.activity.LaunchTaskEvent; 50import com.android.systemui.recents.events.ui.DismissTaskViewEvent; 51import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; 52import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent; 53import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; 54import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; 55import com.android.systemui.recents.misc.ReferenceCountedTrigger; 56import com.android.systemui.recents.misc.SystemServicesProxy; 57import com.android.systemui.recents.misc.Utilities; 58import com.android.systemui.recents.model.Task; 59import com.android.systemui.recents.model.TaskStack; 60 61import java.util.ArrayList; 62 63/** 64 * A {@link TaskView} represents a fixed view of a task. Because the TaskView's layout is directed 65 * solely by the {@link TaskStackView}, we make it a fixed size layout which allows relayouts down 66 * the view hierarchy, but not upwards from any of its children (the TaskView will relayout itself 67 * with the previous bounds if any child requests layout). 68 */ 69public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks, 70 TaskStackAnimationHelper.Callbacks, View.OnClickListener, View.OnLongClickListener { 71 72 /** The TaskView callbacks */ 73 interface TaskViewCallbacks { 74 void onTaskViewClipStateChanged(TaskView tv); 75 } 76 77 /** 78 * The dim overlay is generally calculated from the task progress, but occasionally (like when 79 * launching) needs to be animated independently of the task progress. This call is only used 80 * when animating the task into Recents, when the header dim is already applied 81 */ 82 public static final Property<TaskView, Float> DIM_ALPHA_WITHOUT_HEADER = 83 new FloatProperty<TaskView>("dimAlphaWithoutHeader") { 84 @Override 85 public void setValue(TaskView tv, float dimAlpha) { 86 tv.setDimAlphaWithoutHeader(dimAlpha); 87 } 88 89 @Override 90 public Float get(TaskView tv) { 91 return tv.getDimAlpha(); 92 } 93 }; 94 95 /** 96 * The dim overlay is generally calculated from the task progress, but occasionally (like when 97 * launching) needs to be animated independently of the task progress. 98 */ 99 public static final Property<TaskView, Float> DIM_ALPHA = 100 new FloatProperty<TaskView>("dimAlpha") { 101 @Override 102 public void setValue(TaskView tv, float dimAlpha) { 103 tv.setDimAlpha(dimAlpha); 104 } 105 106 @Override 107 public Float get(TaskView tv) { 108 return tv.getDimAlpha(); 109 } 110 }; 111 112 /** 113 * The dim overlay is generally calculated from the task progress, but occasionally (like when 114 * launching) needs to be animated independently of the task progress. 115 */ 116 public static final Property<TaskView, Float> VIEW_OUTLINE_ALPHA = 117 new FloatProperty<TaskView>("viewOutlineAlpha") { 118 @Override 119 public void setValue(TaskView tv, float alpha) { 120 tv.getViewBounds().setAlpha(alpha); 121 } 122 123 @Override 124 public Float get(TaskView tv) { 125 return tv.getViewBounds().getAlpha(); 126 } 127 }; 128 129 @ViewDebug.ExportedProperty(category="recents") 130 private float mDimAlpha; 131 private float mActionButtonTranslationZ; 132 133 @ViewDebug.ExportedProperty(deepExport=true, prefix="task_") 134 private Task mTask; 135 @ViewDebug.ExportedProperty(category="recents") 136 private boolean mClipViewInStack = true; 137 @ViewDebug.ExportedProperty(category="recents") 138 private boolean mTouchExplorationEnabled; 139 @ViewDebug.ExportedProperty(category="recents") 140 private boolean mIsDisabledInSafeMode; 141 @ViewDebug.ExportedProperty(deepExport=true, prefix="view_bounds_") 142 private AnimateableViewBounds mViewBounds; 143 144 private AnimatorSet mTransformAnimation; 145 private ObjectAnimator mDimAnimator; 146 private ObjectAnimator mOutlineAnimator; 147 private final TaskViewTransform mTargetAnimationTransform = new TaskViewTransform(); 148 private ArrayList<Animator> mTmpAnimators = new ArrayList<>(); 149 150 @ViewDebug.ExportedProperty(deepExport=true, prefix="thumbnail_") 151 protected TaskViewThumbnail mThumbnailView; 152 @ViewDebug.ExportedProperty(deepExport=true, prefix="header_") 153 TaskViewHeader mHeaderView; 154 private View mActionButtonView; 155 private View mIncompatibleAppToastView; 156 private TaskViewCallbacks mCb; 157 158 @ViewDebug.ExportedProperty(category="recents") 159 private Point mDownTouchPos = new Point(); 160 161 private Toast mDisabledAppToast; 162 163 public TaskView(Context context) { 164 this(context, null); 165 } 166 167 public TaskView(Context context, AttributeSet attrs) { 168 this(context, attrs, 0); 169 } 170 171 public TaskView(Context context, AttributeSet attrs, int defStyleAttr) { 172 this(context, attrs, defStyleAttr, 0); 173 } 174 175 public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 176 super(context, attrs, defStyleAttr, defStyleRes); 177 RecentsConfiguration config = Recents.getConfiguration(); 178 Resources res = context.getResources(); 179 mViewBounds = createOutlineProvider(); 180 if (config.fakeShadows) { 181 setBackground(new FakeShadowDrawable(res, config)); 182 } 183 setOutlineProvider(mViewBounds); 184 setOnLongClickListener(this); 185 } 186 187 /** Set callback */ 188 void setCallbacks(TaskViewCallbacks cb) { 189 mCb = cb; 190 } 191 192 /** 193 * Called from RecentsActivity when it is relaunched. 194 */ 195 void onReload(boolean isResumingFromVisible) { 196 if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) { 197 resetNoUserInteractionState(); 198 } 199 if (!isResumingFromVisible) { 200 resetViewProperties(); 201 } 202 } 203 204 /** Gets the task */ 205 public Task getTask() { 206 return mTask; 207 } 208 209 /* Create an outline provider to clip and outline the view */ 210 protected AnimateableViewBounds createOutlineProvider() { 211 return new AnimateableViewBounds(this, mContext.getResources().getDimensionPixelSize( 212 R.dimen.recents_task_view_shadow_rounded_corners_radius)); 213 } 214 215 /** Returns the view bounds. */ 216 AnimateableViewBounds getViewBounds() { 217 return mViewBounds; 218 } 219 220 @Override 221 protected void onFinishInflate() { 222 // Bind the views 223 mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar); 224 mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail); 225 mThumbnailView.updateClipToTaskBar(mHeaderView); 226 mActionButtonView = findViewById(R.id.lock_to_app_fab); 227 mActionButtonView.setOutlineProvider(new ViewOutlineProvider() { 228 @Override 229 public void getOutline(View view, Outline outline) { 230 // Set the outline to match the FAB background 231 outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight()); 232 outline.setAlpha(0.35f); 233 } 234 }); 235 mActionButtonView.setOnClickListener(this); 236 mActionButtonTranslationZ = mActionButtonView.getTranslationZ(); 237 } 238 239 /** 240 * Update the task view when the configuration changes. 241 */ 242 protected void onConfigurationChanged() { 243 mHeaderView.onConfigurationChanged(); 244 } 245 246 @Override 247 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 248 super.onSizeChanged(w, h, oldw, oldh); 249 if (w > 0 && h > 0) { 250 mHeaderView.onTaskViewSizeChanged(w, h); 251 mThumbnailView.onTaskViewSizeChanged(w, h); 252 253 mActionButtonView.setTranslationX(w - getMeasuredWidth()); 254 mActionButtonView.setTranslationY(h - getMeasuredHeight()); 255 } 256 } 257 258 @Override 259 public boolean hasOverlappingRendering() { 260 return false; 261 } 262 263 @Override 264 public boolean onInterceptTouchEvent(MotionEvent ev) { 265 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 266 mDownTouchPos.set((int) (ev.getX() * getScaleX()), (int) (ev.getY() * getScaleY())); 267 } 268 return super.onInterceptTouchEvent(ev); 269 } 270 271 272 @Override 273 protected void measureContents(int width, int height) { 274 int widthWithoutPadding = width - mPaddingLeft - mPaddingRight; 275 int heightWithoutPadding = height - mPaddingTop - mPaddingBottom; 276 int widthSpec = MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY); 277 int heightSpec = MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY); 278 279 // Measure the content 280 measureChildren(widthSpec, heightSpec); 281 282 setMeasuredDimension(width, height); 283 } 284 285 void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, 286 AnimationProps toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) { 287 RecentsConfiguration config = Recents.getConfiguration(); 288 cancelTransformAnimation(); 289 290 // Compose the animations for the transform 291 mTmpAnimators.clear(); 292 toTransform.applyToTaskView(this, mTmpAnimators, toAnimation, !config.fakeShadows); 293 if (toAnimation.isImmediate()) { 294 if (Float.compare(getDimAlpha(), toTransform.dimAlpha) != 0) { 295 setDimAlpha(toTransform.dimAlpha); 296 } 297 if (Float.compare(mViewBounds.getAlpha(), toTransform.viewOutlineAlpha) != 0) { 298 mViewBounds.setAlpha(toTransform.viewOutlineAlpha); 299 } 300 // Manually call back to the animator listener and update callback 301 if (toAnimation.getListener() != null) { 302 toAnimation.getListener().onAnimationEnd(null); 303 } 304 if (updateCallback != null) { 305 updateCallback.onAnimationUpdate(null); 306 } 307 } else { 308 // Both the progress and the update are a function of the bounds movement of the task 309 if (Float.compare(getDimAlpha(), toTransform.dimAlpha) != 0) { 310 mDimAnimator = ObjectAnimator.ofFloat(this, DIM_ALPHA, getDimAlpha(), 311 toTransform.dimAlpha); 312 mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, mDimAnimator)); 313 } 314 if (Float.compare(mViewBounds.getAlpha(), toTransform.viewOutlineAlpha) != 0) { 315 mOutlineAnimator = ObjectAnimator.ofFloat(this, VIEW_OUTLINE_ALPHA, 316 mViewBounds.getAlpha(), toTransform.viewOutlineAlpha); 317 mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, mOutlineAnimator)); 318 } 319 if (updateCallback != null) { 320 ValueAnimator updateCallbackAnim = ValueAnimator.ofInt(0, 1); 321 updateCallbackAnim.addUpdateListener(updateCallback); 322 mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, updateCallbackAnim)); 323 } 324 325 // Create the animator 326 mTransformAnimation = toAnimation.createAnimator(mTmpAnimators); 327 mTransformAnimation.start(); 328 mTargetAnimationTransform.copyFrom(toTransform); 329 } 330 } 331 332 /** Resets this view's properties */ 333 void resetViewProperties() { 334 cancelTransformAnimation(); 335 setDimAlpha(0); 336 setVisibility(View.VISIBLE); 337 getViewBounds().reset(); 338 getHeaderView().reset(); 339 TaskViewTransform.reset(this); 340 341 mActionButtonView.setScaleX(1f); 342 mActionButtonView.setScaleY(1f); 343 mActionButtonView.setAlpha(0f); 344 mActionButtonView.setTranslationX(0f); 345 mActionButtonView.setTranslationY(0f); 346 mActionButtonView.setTranslationZ(mActionButtonTranslationZ); 347 if (mIncompatibleAppToastView != null) { 348 mIncompatibleAppToastView.setVisibility(View.INVISIBLE); 349 } 350 } 351 352 /** 353 * @return whether we are animating towards {@param transform} 354 */ 355 boolean isAnimatingTo(TaskViewTransform transform) { 356 return mTransformAnimation != null && mTransformAnimation.isStarted() 357 && mTargetAnimationTransform.isSame(transform); 358 } 359 360 /** 361 * Cancels any current transform animations. 362 */ 363 public void cancelTransformAnimation() { 364 Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation); 365 Utilities.cancelAnimationWithoutCallbacks(mDimAnimator); 366 Utilities.cancelAnimationWithoutCallbacks(mOutlineAnimator); 367 } 368 369 /** Enables/disables handling touch on this task view. */ 370 public void setTouchEnabled(boolean enabled) { 371 setOnClickListener(enabled ? this : null); 372 } 373 374 /** Animates this task view if the user does not interact with the stack after a certain time. */ 375 public void startNoUserInteractionAnimation() { 376 mHeaderView.startNoUserInteractionAnimation(); 377 } 378 379 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 380 void setNoUserInteractionState() { 381 mHeaderView.setNoUserInteractionState(); 382 } 383 384 /** Resets the state tracking that the user has not interacted with the stack after a certain time. */ 385 void resetNoUserInteractionState() { 386 mHeaderView.resetNoUserInteractionState(); 387 } 388 389 /** Dismisses this task. */ 390 void dismissTask() { 391 // Animate out the view and call the callback 392 final TaskView tv = this; 393 DismissTaskViewEvent dismissEvent = new DismissTaskViewEvent(tv); 394 dismissEvent.addPostAnimationCallback(new Runnable() { 395 @Override 396 public void run() { 397 EventBus.getDefault().send(new TaskViewDismissedEvent(mTask, tv, 398 new AnimationProps(TaskStackView.DEFAULT_SYNC_STACK_DURATION, 399 Interpolators.FAST_OUT_SLOW_IN))); 400 } 401 }); 402 EventBus.getDefault().send(dismissEvent); 403 } 404 405 /** 406 * Returns whether this view should be clipped, or any views below should clip against this 407 * view. 408 */ 409 boolean shouldClipViewInStack() { 410 // Never clip for freeform tasks or if invisible 411 if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE) { 412 return false; 413 } 414 return mClipViewInStack; 415 } 416 417 /** Sets whether this view should be clipped, or clipped against. */ 418 void setClipViewInStack(boolean clip) { 419 if (clip != mClipViewInStack) { 420 mClipViewInStack = clip; 421 if (mCb != null) { 422 mCb.onTaskViewClipStateChanged(this); 423 } 424 } 425 } 426 427 public TaskViewHeader getHeaderView() { 428 return mHeaderView; 429 } 430 431 /** 432 * Sets the current dim. 433 */ 434 public void setDimAlpha(float dimAlpha) { 435 mDimAlpha = dimAlpha; 436 mThumbnailView.setDimAlpha(dimAlpha); 437 mHeaderView.setDimAlpha(dimAlpha); 438 } 439 440 /** 441 * Sets the current dim without updating the header's dim. 442 */ 443 public void setDimAlphaWithoutHeader(float dimAlpha) { 444 mDimAlpha = dimAlpha; 445 mThumbnailView.setDimAlpha(dimAlpha); 446 } 447 448 /** 449 * Returns the current dim. 450 */ 451 public float getDimAlpha() { 452 return mDimAlpha; 453 } 454 455 /** 456 * Explicitly sets the focused state of this task. 457 */ 458 public void setFocusedState(boolean isFocused, boolean requestViewFocus) { 459 if (isFocused) { 460 if (requestViewFocus && !isFocused()) { 461 requestFocus(); 462 } 463 } else { 464 if (isAccessibilityFocused() && mTouchExplorationEnabled) { 465 clearAccessibilityFocus(); 466 } 467 } 468 } 469 470 /** 471 * Shows the action button. 472 * @param fadeIn whether or not to animate the action button in. 473 * @param fadeInDuration the duration of the action button animation, only used if 474 * {@param fadeIn} is true. 475 */ 476 public void showActionButton(boolean fadeIn, int fadeInDuration) { 477 mActionButtonView.setVisibility(View.VISIBLE); 478 479 if (fadeIn && mActionButtonView.getAlpha() < 1f) { 480 mActionButtonView.animate() 481 .alpha(1f) 482 .scaleX(1f) 483 .scaleY(1f) 484 .setDuration(fadeInDuration) 485 .setInterpolator(Interpolators.ALPHA_IN) 486 .start(); 487 } else { 488 mActionButtonView.setScaleX(1f); 489 mActionButtonView.setScaleY(1f); 490 mActionButtonView.setAlpha(1f); 491 mActionButtonView.setTranslationZ(mActionButtonTranslationZ); 492 } 493 } 494 495 /** 496 * Immediately hides the action button. 497 * 498 * @param fadeOut whether or not to animate the action button out. 499 */ 500 public void hideActionButton(boolean fadeOut, int fadeOutDuration, boolean scaleDown, 501 final Animator.AnimatorListener animListener) { 502 if (fadeOut && mActionButtonView.getAlpha() > 0f) { 503 if (scaleDown) { 504 float toScale = 0.9f; 505 mActionButtonView.animate() 506 .scaleX(toScale) 507 .scaleY(toScale); 508 } 509 mActionButtonView.animate() 510 .alpha(0f) 511 .setDuration(fadeOutDuration) 512 .setInterpolator(Interpolators.ALPHA_OUT) 513 .withEndAction(new Runnable() { 514 @Override 515 public void run() { 516 if (animListener != null) { 517 animListener.onAnimationEnd(null); 518 } 519 mActionButtonView.setVisibility(View.INVISIBLE); 520 } 521 }) 522 .start(); 523 } else { 524 mActionButtonView.setAlpha(0f); 525 mActionButtonView.setVisibility(View.INVISIBLE); 526 if (animListener != null) { 527 animListener.onAnimationEnd(null); 528 } 529 } 530 } 531 532 /**** TaskStackAnimationHelper.Callbacks Implementation ****/ 533 534 @Override 535 public void onPrepareLaunchTargetForEnterAnimation() { 536 // These values will be animated in when onStartLaunchTargetEnterAnimation() is called 537 setDimAlphaWithoutHeader(0); 538 mActionButtonView.setAlpha(0f); 539 if (mIncompatibleAppToastView != null && 540 mIncompatibleAppToastView.getVisibility() == View.VISIBLE) { 541 mIncompatibleAppToastView.setAlpha(0f); 542 } 543 } 544 545 @Override 546 public void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration, 547 boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger) { 548 Utilities.cancelAnimationWithoutCallbacks(mDimAnimator); 549 550 // Dim the view after the app window transitions down into recents 551 postAnimationTrigger.increment(); 552 AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT); 553 mDimAnimator = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this, 554 DIM_ALPHA_WITHOUT_HEADER, getDimAlpha(), transform.dimAlpha)); 555 mDimAnimator.addListener(postAnimationTrigger.decrementOnAnimationEnd()); 556 mDimAnimator.start(); 557 558 if (screenPinningEnabled) { 559 showActionButton(true /* fadeIn */, duration /* fadeInDuration */); 560 } 561 562 if (mIncompatibleAppToastView != null && 563 mIncompatibleAppToastView.getVisibility() == View.VISIBLE) { 564 mIncompatibleAppToastView.animate() 565 .alpha(1f) 566 .setDuration(duration) 567 .setInterpolator(Interpolators.ALPHA_IN) 568 .start(); 569 } 570 } 571 572 @Override 573 public void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested, 574 ReferenceCountedTrigger postAnimationTrigger) { 575 Utilities.cancelAnimationWithoutCallbacks(mDimAnimator); 576 577 // Un-dim the view before/while launching the target 578 AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT); 579 mDimAnimator = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this, 580 DIM_ALPHA, getDimAlpha(), 0)); 581 mDimAnimator.start(); 582 583 postAnimationTrigger.increment(); 584 hideActionButton(true /* fadeOut */, duration, 585 !screenPinningRequested /* scaleDown */, 586 postAnimationTrigger.decrementOnAnimationEnd()); 587 } 588 589 @Override 590 public void onStartFrontTaskEnterAnimation(boolean screenPinningEnabled) { 591 if (screenPinningEnabled) { 592 showActionButton(false /* fadeIn */, 0 /* fadeInDuration */); 593 } 594 } 595 596 /**** TaskCallbacks Implementation ****/ 597 598 public void onTaskBound(Task t, boolean touchExplorationEnabled, int displayOrientation, 599 Rect displayRect) { 600 SystemServicesProxy ssp = Recents.getSystemServices(); 601 mTouchExplorationEnabled = touchExplorationEnabled; 602 mTask = t; 603 mTask.addCallback(this); 604 mIsDisabledInSafeMode = !mTask.isSystemApp && ssp.isInSafeMode(); 605 mThumbnailView.bindToTask(mTask, mIsDisabledInSafeMode, displayOrientation, displayRect); 606 mHeaderView.bindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode); 607 608 if (!t.isDockable && ssp.hasDockedTask()) { 609 if (mIncompatibleAppToastView == null) { 610 mIncompatibleAppToastView = Utilities.findViewStubById(this, 611 R.id.incompatible_app_toast_stub).inflate(); 612 TextView msg = (TextView) findViewById(com.android.internal.R.id.message); 613 msg.setText(R.string.recents_incompatible_app_message); 614 } 615 mIncompatibleAppToastView.setVisibility(View.VISIBLE); 616 } else if (mIncompatibleAppToastView != null) { 617 mIncompatibleAppToastView.setVisibility(View.INVISIBLE); 618 } 619 } 620 621 @Override 622 public void onTaskDataLoaded(Task task, ActivityManager.TaskThumbnailInfo thumbnailInfo) { 623 // Update each of the views to the new task data 624 mThumbnailView.onTaskDataLoaded(thumbnailInfo); 625 mHeaderView.onTaskDataLoaded(); 626 } 627 628 @Override 629 public void onTaskDataUnloaded() { 630 // Unbind each of the views from the task and remove the task callback 631 mTask.removeCallback(this); 632 mThumbnailView.unbindFromTask(); 633 mHeaderView.unbindFromTask(mTouchExplorationEnabled); 634 } 635 636 @Override 637 public void onTaskStackIdChanged() { 638 // Force rebind the header, the thumbnail does not change due to stack changes 639 mHeaderView.bindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode); 640 mHeaderView.onTaskDataLoaded(); 641 } 642 643 /**** View.OnClickListener Implementation ****/ 644 645 @Override 646 public void onClick(final View v) { 647 if (mIsDisabledInSafeMode) { 648 Context context = getContext(); 649 String msg = context.getString(R.string.recents_launch_disabled_message, mTask.title); 650 if (mDisabledAppToast != null) { 651 mDisabledAppToast.cancel(); 652 } 653 mDisabledAppToast = Toast.makeText(context, msg, Toast.LENGTH_SHORT); 654 mDisabledAppToast.show(); 655 return; 656 } 657 658 boolean screenPinningRequested = false; 659 if (v == mActionButtonView) { 660 // Reset the translation of the action button before we animate it out 661 mActionButtonView.setTranslationZ(0f); 662 screenPinningRequested = true; 663 } 664 EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, INVALID_STACK_ID, 665 screenPinningRequested)); 666 667 MetricsLogger.action(v.getContext(), MetricsEvent.ACTION_OVERVIEW_SELECT, 668 mTask.key.getComponent().toString()); 669 } 670 671 /**** View.OnLongClickListener Implementation ****/ 672 673 @Override 674 public boolean onLongClick(View v) { 675 SystemServicesProxy ssp = Recents.getSystemServices(); 676 boolean inBounds = false; 677 Rect clipBounds = new Rect(mViewBounds.mClipBounds); 678 if (!clipBounds.isEmpty()) { 679 // If we are clipping the view to the bounds, manually do the hit test. 680 clipBounds.scale(getScaleX()); 681 inBounds = clipBounds.contains(mDownTouchPos.x, mDownTouchPos.y); 682 } else { 683 // Otherwise just make sure we're within the view's bounds. 684 inBounds = mDownTouchPos.x <= getWidth() && mDownTouchPos.y <= getHeight(); 685 } 686 if (v == this && inBounds && !ssp.hasDockedTask()) { 687 // Start listening for drag events 688 setClipViewInStack(false); 689 690 mDownTouchPos.x += ((1f - getScaleX()) * getWidth()) / 2; 691 mDownTouchPos.y += ((1f - getScaleY()) * getHeight()) / 2; 692 693 EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); 694 EventBus.getDefault().send(new DragStartEvent(mTask, this, mDownTouchPos)); 695 return true; 696 } 697 return false; 698 } 699 700 /**** Events ****/ 701 702 public final void onBusEvent(DragEndEvent event) { 703 if (!(event.dropTarget instanceof TaskStack.DockState)) { 704 event.addPostAnimationCallback(() -> { 705 // Reset the clip state for the drag view after the end animation completes 706 setClipViewInStack(true); 707 }); 708 } 709 EventBus.getDefault().unregister(this); 710 } 711 712 public final void onBusEvent(DragEndCancelledEvent event) { 713 // Reset the clip state for the drag view after the cancel animation completes 714 event.addPostAnimationCallback(() -> { 715 setClipViewInStack(true); 716 }); 717 } 718} 719