TaskView.java revision bf8e290376b8ee6d04c330b22f5275e82103def3
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents.views;
18
19import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
20
21import android.animation.Animator;
22import android.animation.AnimatorSet;
23import android.animation.ObjectAnimator;
24import android.animation.ValueAnimator;
25import android.app.ActivityManager;
26import android.content.Context;
27import android.content.res.Resources;
28import android.graphics.Outline;
29import android.graphics.Point;
30import android.graphics.Rect;
31import android.util.AttributeSet;
32import android.util.FloatProperty;
33import android.util.Property;
34import android.view.MotionEvent;
35import android.view.View;
36import android.view.ViewDebug;
37import android.view.ViewOutlineProvider;
38import android.widget.TextView;
39import android.widget.Toast;
40
41import com.android.internal.logging.MetricsLogger;
42import com.android.internal.logging.MetricsProto.MetricsEvent;
43import com.android.systemui.Interpolators;
44import com.android.systemui.R;
45import com.android.systemui.recents.Recents;
46import com.android.systemui.recents.RecentsActivity;
47import com.android.systemui.recents.RecentsConfiguration;
48import com.android.systemui.recents.events.EventBus;
49import com.android.systemui.recents.events.activity.LaunchTaskEvent;
50import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
51import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
52import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
53import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
54import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
55import com.android.systemui.recents.misc.ReferenceCountedTrigger;
56import com.android.systemui.recents.misc.SystemServicesProxy;
57import com.android.systemui.recents.misc.Utilities;
58import com.android.systemui.recents.model.Task;
59import com.android.systemui.recents.model.TaskStack;
60
61import 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 */
69public 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    protected 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 = createOutlineProvider();
180        if (config.fakeShadows) {
181            setBackground(new FakeShadowDrawable(res, config));
182        }
183        setOutlineProvider(mViewBounds);
184        setOnLongClickListener(this);
185    }
186
187    /** Set callback */
188    void setCallbacks(TaskViewCallbacks cb) {
189        mCb = cb;
190    }
191
192    /**
193     * Called from RecentsActivity when it is relaunched.
194     */
195    void onReload(boolean isResumingFromVisible) {
196        if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
197            resetNoUserInteractionState();
198        }
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 = (TaskViewHeader) findViewById(R.id.task_view_bar);
224        mThumbnailView = (TaskViewThumbnail) 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
272    @Override
273    protected void measureContents(int width, int height) {
274        int widthWithoutPadding = width - mPaddingLeft - mPaddingRight;
275        int heightWithoutPadding = height - mPaddingTop - mPaddingBottom;
276        int widthSpec = MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY);
277        int heightSpec = MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY);
278
279        // Measure the content
280        measureChildren(widthSpec, heightSpec);
281
282        setMeasuredDimension(width, height);
283    }
284
285    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform,
286            AnimationProps toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) {
287        RecentsConfiguration config = Recents.getConfiguration();
288        cancelTransformAnimation();
289
290        // Compose the animations for the transform
291        mTmpAnimators.clear();
292        toTransform.applyToTaskView(this, mTmpAnimators, toAnimation, !config.fakeShadows);
293        if (toAnimation.isImmediate()) {
294            if (Float.compare(getDimAlpha(), toTransform.dimAlpha) != 0) {
295                setDimAlpha(toTransform.dimAlpha);
296            }
297            if (Float.compare(mViewBounds.getAlpha(), toTransform.viewOutlineAlpha) != 0) {
298                mViewBounds.setAlpha(toTransform.viewOutlineAlpha);
299            }
300            // Manually call back to the animator listener and update callback
301            if (toAnimation.getListener() != null) {
302                toAnimation.getListener().onAnimationEnd(null);
303            }
304            if (updateCallback != null) {
305                updateCallback.onAnimationUpdate(null);
306            }
307        } else {
308            // Both the progress and the update are a function of the bounds movement of the task
309            if (Float.compare(getDimAlpha(), toTransform.dimAlpha) != 0) {
310                mDimAnimator = ObjectAnimator.ofFloat(this, DIM_ALPHA, getDimAlpha(),
311                        toTransform.dimAlpha);
312                mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, mDimAnimator));
313            }
314            if (Float.compare(mViewBounds.getAlpha(), toTransform.viewOutlineAlpha) != 0) {
315                mOutlineAnimator = ObjectAnimator.ofFloat(this, VIEW_OUTLINE_ALPHA,
316                        mViewBounds.getAlpha(), toTransform.viewOutlineAlpha);
317                mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, mOutlineAnimator));
318            }
319            if (updateCallback != null) {
320                ValueAnimator updateCallbackAnim = ValueAnimator.ofInt(0, 1);
321                updateCallbackAnim.addUpdateListener(updateCallback);
322                mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, updateCallbackAnim));
323            }
324
325            // Create the animator
326            mTransformAnimation = toAnimation.createAnimator(mTmpAnimators);
327            mTransformAnimation.start();
328            mTargetAnimationTransform.copyFrom(toTransform);
329        }
330    }
331
332    /** Resets this view's properties */
333    void resetViewProperties() {
334        cancelTransformAnimation();
335        setDimAlpha(0);
336        setVisibility(View.VISIBLE);
337        getViewBounds().reset();
338        getHeaderView().reset();
339        TaskViewTransform.reset(this);
340
341        mActionButtonView.setScaleX(1f);
342        mActionButtonView.setScaleY(1f);
343        mActionButtonView.setAlpha(0f);
344        mActionButtonView.setTranslationX(0f);
345        mActionButtonView.setTranslationY(0f);
346        mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
347        if (mIncompatibleAppToastView != null) {
348            mIncompatibleAppToastView.setVisibility(View.INVISIBLE);
349        }
350    }
351
352    /**
353     * @return whether we are animating towards {@param transform}
354     */
355    boolean isAnimatingTo(TaskViewTransform transform) {
356        return mTransformAnimation != null && mTransformAnimation.isStarted()
357                && mTargetAnimationTransform.isSame(transform);
358    }
359
360    /**
361     * Cancels any current transform animations.
362     */
363    public void cancelTransformAnimation() {
364        Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
365        Utilities.cancelAnimationWithoutCallbacks(mDimAnimator);
366        Utilities.cancelAnimationWithoutCallbacks(mOutlineAnimator);
367    }
368
369    /** Enables/disables handling touch on this task view. */
370    public void setTouchEnabled(boolean enabled) {
371        setOnClickListener(enabled ? this : null);
372    }
373
374    /** Animates this task view if the user does not interact with the stack after a certain time. */
375    public void startNoUserInteractionAnimation() {
376        mHeaderView.startNoUserInteractionAnimation();
377    }
378
379    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
380    void setNoUserInteractionState() {
381        mHeaderView.setNoUserInteractionState();
382    }
383
384    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
385    void resetNoUserInteractionState() {
386        mHeaderView.resetNoUserInteractionState();
387    }
388
389    /** Dismisses this task. */
390    void dismissTask() {
391        // Animate out the view and call the callback
392        final TaskView tv = this;
393        DismissTaskViewEvent dismissEvent = new DismissTaskViewEvent(tv);
394        dismissEvent.addPostAnimationCallback(new Runnable() {
395            @Override
396            public void run() {
397                EventBus.getDefault().send(new TaskViewDismissedEvent(mTask, tv,
398                        new AnimationProps(TaskStackView.DEFAULT_SYNC_STACK_DURATION,
399                                Interpolators.FAST_OUT_SLOW_IN)));
400            }
401        });
402        EventBus.getDefault().send(dismissEvent);
403    }
404
405    /**
406     * Returns whether this view should be clipped, or any views below should clip against this
407     * view.
408     */
409    boolean shouldClipViewInStack() {
410        // Never clip for freeform tasks or if invisible
411        if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE) {
412            return false;
413        }
414        return mClipViewInStack;
415    }
416
417    /** Sets whether this view should be clipped, or clipped against. */
418    void setClipViewInStack(boolean clip) {
419        if (clip != mClipViewInStack) {
420            mClipViewInStack = clip;
421            if (mCb != null) {
422                mCb.onTaskViewClipStateChanged(this);
423            }
424        }
425    }
426
427    public TaskViewHeader getHeaderView() {
428        return mHeaderView;
429    }
430
431    /**
432     * Sets the current dim.
433     */
434    public void setDimAlpha(float dimAlpha) {
435        mDimAlpha = dimAlpha;
436        mThumbnailView.setDimAlpha(dimAlpha);
437        mHeaderView.setDimAlpha(dimAlpha);
438    }
439
440    /**
441     * Sets the current dim without updating the header's dim.
442     */
443    public void setDimAlphaWithoutHeader(float dimAlpha) {
444        mDimAlpha = dimAlpha;
445        mThumbnailView.setDimAlpha(dimAlpha);
446    }
447
448    /**
449     * Returns the current dim.
450     */
451    public float getDimAlpha() {
452        return mDimAlpha;
453    }
454
455    /**
456     * Explicitly sets the focused state of this task.
457     */
458    public void setFocusedState(boolean isFocused, boolean requestViewFocus) {
459        if (isFocused) {
460            if (requestViewFocus && !isFocused()) {
461                requestFocus();
462            }
463        } else {
464            if (isAccessibilityFocused() && mTouchExplorationEnabled) {
465                clearAccessibilityFocus();
466            }
467        }
468    }
469
470    /**
471     * Shows the action button.
472     * @param fadeIn whether or not to animate the action button in.
473     * @param fadeInDuration the duration of the action button animation, only used if
474     *                       {@param fadeIn} is true.
475     */
476    public void showActionButton(boolean fadeIn, int fadeInDuration) {
477        mActionButtonView.setVisibility(View.VISIBLE);
478
479        if (fadeIn && mActionButtonView.getAlpha() < 1f) {
480            mActionButtonView.animate()
481                    .alpha(1f)
482                    .scaleX(1f)
483                    .scaleY(1f)
484                    .setDuration(fadeInDuration)
485                    .setInterpolator(Interpolators.ALPHA_IN)
486                    .start();
487        } else {
488            mActionButtonView.setScaleX(1f);
489            mActionButtonView.setScaleY(1f);
490            mActionButtonView.setAlpha(1f);
491            mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
492        }
493    }
494
495    /**
496     * Immediately hides the action button.
497     *
498     * @param fadeOut whether or not to animate the action button out.
499     */
500    public void hideActionButton(boolean fadeOut, int fadeOutDuration, boolean scaleDown,
501            final Animator.AnimatorListener animListener) {
502        if (fadeOut && mActionButtonView.getAlpha() > 0f) {
503            if (scaleDown) {
504                float toScale = 0.9f;
505                mActionButtonView.animate()
506                        .scaleX(toScale)
507                        .scaleY(toScale);
508            }
509            mActionButtonView.animate()
510                    .alpha(0f)
511                    .setDuration(fadeOutDuration)
512                    .setInterpolator(Interpolators.ALPHA_OUT)
513                    .withEndAction(new Runnable() {
514                        @Override
515                        public void run() {
516                            if (animListener != null) {
517                                animListener.onAnimationEnd(null);
518                            }
519                            mActionButtonView.setVisibility(View.INVISIBLE);
520                        }
521                    })
522                    .start();
523        } else {
524            mActionButtonView.setAlpha(0f);
525            mActionButtonView.setVisibility(View.INVISIBLE);
526            if (animListener != null) {
527                animListener.onAnimationEnd(null);
528            }
529        }
530    }
531
532    /**** TaskStackAnimationHelper.Callbacks Implementation ****/
533
534    @Override
535    public void onPrepareLaunchTargetForEnterAnimation() {
536        // These values will be animated in when onStartLaunchTargetEnterAnimation() is called
537        setDimAlphaWithoutHeader(0);
538        mActionButtonView.setAlpha(0f);
539        if (mIncompatibleAppToastView != null &&
540                mIncompatibleAppToastView.getVisibility() == View.VISIBLE) {
541            mIncompatibleAppToastView.setAlpha(0f);
542        }
543    }
544
545    @Override
546    public void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration,
547            boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger) {
548        Utilities.cancelAnimationWithoutCallbacks(mDimAnimator);
549
550        // Dim the view after the app window transitions down into recents
551        postAnimationTrigger.increment();
552        AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT);
553        mDimAnimator = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this,
554                DIM_ALPHA_WITHOUT_HEADER, getDimAlpha(), transform.dimAlpha));
555        mDimAnimator.addListener(postAnimationTrigger.decrementOnAnimationEnd());
556        mDimAnimator.start();
557
558        if (screenPinningEnabled) {
559            showActionButton(true /* fadeIn */, duration /* fadeInDuration */);
560        }
561
562        if (mIncompatibleAppToastView != null &&
563                mIncompatibleAppToastView.getVisibility() == View.VISIBLE) {
564            mIncompatibleAppToastView.animate()
565                    .alpha(1f)
566                    .setDuration(duration)
567                    .setInterpolator(Interpolators.ALPHA_IN)
568                    .start();
569        }
570    }
571
572    @Override
573    public void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested,
574            ReferenceCountedTrigger postAnimationTrigger) {
575        Utilities.cancelAnimationWithoutCallbacks(mDimAnimator);
576
577        // Un-dim the view before/while launching the target
578        AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT);
579        mDimAnimator = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this,
580                DIM_ALPHA, getDimAlpha(), 0));
581        mDimAnimator.start();
582
583        postAnimationTrigger.increment();
584        hideActionButton(true /* fadeOut */, duration,
585                !screenPinningRequested /* scaleDown */,
586                postAnimationTrigger.decrementOnAnimationEnd());
587    }
588
589    @Override
590    public void onStartFrontTaskEnterAnimation(boolean screenPinningEnabled) {
591        if (screenPinningEnabled) {
592            showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
593        }
594    }
595
596    /**** TaskCallbacks Implementation ****/
597
598    public void onTaskBound(Task t, boolean touchExplorationEnabled, int displayOrientation,
599            Rect displayRect) {
600        SystemServicesProxy ssp = Recents.getSystemServices();
601        mTouchExplorationEnabled = touchExplorationEnabled;
602        mTask = t;
603        mTask.addCallback(this);
604        mIsDisabledInSafeMode = !mTask.isSystemApp && ssp.isInSafeMode();
605        mThumbnailView.bindToTask(mTask, mIsDisabledInSafeMode, displayOrientation, displayRect);
606        mHeaderView.bindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode);
607
608        if (!t.isDockable && ssp.hasDockedTask()) {
609            if (mIncompatibleAppToastView == null) {
610                mIncompatibleAppToastView = Utilities.findViewStubById(this,
611                        R.id.incompatible_app_toast_stub).inflate();
612                TextView msg = (TextView) findViewById(com.android.internal.R.id.message);
613                msg.setText(R.string.recents_incompatible_app_message);
614            }
615            mIncompatibleAppToastView.setVisibility(View.VISIBLE);
616        } else if (mIncompatibleAppToastView != null) {
617            mIncompatibleAppToastView.setVisibility(View.INVISIBLE);
618        }
619    }
620
621    @Override
622    public void onTaskDataLoaded(Task task, ActivityManager.TaskThumbnailInfo thumbnailInfo) {
623        // Update each of the views to the new task data
624        mThumbnailView.onTaskDataLoaded(thumbnailInfo);
625        mHeaderView.onTaskDataLoaded();
626    }
627
628    @Override
629    public void onTaskDataUnloaded() {
630        // Unbind each of the views from the task and remove the task callback
631        mTask.removeCallback(this);
632        mThumbnailView.unbindFromTask();
633        mHeaderView.unbindFromTask(mTouchExplorationEnabled);
634    }
635
636    @Override
637    public void onTaskStackIdChanged() {
638        // Force rebind the header, the thumbnail does not change due to stack changes
639        mHeaderView.bindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode);
640        mHeaderView.onTaskDataLoaded();
641    }
642
643    /**** View.OnClickListener Implementation ****/
644
645    @Override
646     public void onClick(final View v) {
647        if (mIsDisabledInSafeMode) {
648            Context context = getContext();
649            String msg = context.getString(R.string.recents_launch_disabled_message, mTask.title);
650            if (mDisabledAppToast != null) {
651                mDisabledAppToast.cancel();
652            }
653            mDisabledAppToast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
654            mDisabledAppToast.show();
655            return;
656        }
657
658        boolean screenPinningRequested = false;
659        if (v == mActionButtonView) {
660            // Reset the translation of the action button before we animate it out
661            mActionButtonView.setTranslationZ(0f);
662            screenPinningRequested = true;
663        }
664        EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, INVALID_STACK_ID,
665                screenPinningRequested));
666
667        MetricsLogger.action(v.getContext(), MetricsEvent.ACTION_OVERVIEW_SELECT,
668                mTask.key.getComponent().toString());
669    }
670
671    /**** View.OnLongClickListener Implementation ****/
672
673    @Override
674    public boolean onLongClick(View v) {
675        SystemServicesProxy ssp = Recents.getSystemServices();
676        boolean inBounds = false;
677        Rect clipBounds = new Rect(mViewBounds.mClipBounds);
678        if (!clipBounds.isEmpty()) {
679            // If we are clipping the view to the bounds, manually do the hit test.
680            clipBounds.scale(getScaleX());
681            inBounds = clipBounds.contains(mDownTouchPos.x, mDownTouchPos.y);
682        } else {
683            // Otherwise just make sure we're within the view's bounds.
684            inBounds = mDownTouchPos.x <= getWidth() && mDownTouchPos.y <= getHeight();
685        }
686        if (v == this && inBounds && !ssp.hasDockedTask()) {
687            // Start listening for drag events
688            setClipViewInStack(false);
689
690            mDownTouchPos.x += ((1f - getScaleX()) * getWidth()) / 2;
691            mDownTouchPos.y += ((1f - getScaleY()) * getHeight()) / 2;
692
693            EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
694            EventBus.getDefault().send(new DragStartEvent(mTask, this, mDownTouchPos));
695            return true;
696        }
697        return false;
698    }
699
700    /**** Events ****/
701
702    public final void onBusEvent(DragEndEvent event) {
703        if (!(event.dropTarget instanceof TaskStack.DockState)) {
704            event.addPostAnimationCallback(() -> {
705                // Reset the clip state for the drag view after the end animation completes
706                setClipViewInStack(true);
707            });
708        }
709        EventBus.getDefault().unregister(this);
710    }
711
712    public final void onBusEvent(DragEndCancelledEvent event) {
713        // Reset the clip state for the drag view after the cancel animation completes
714        event.addPostAnimationCallback(() -> {
715            setClipViewInStack(true);
716        });
717    }
718}
719