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