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