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