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