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