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