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