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