TaskView.java revision 61560f065a334343d9c552796e1c702f4dfb664b
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.DragEndEvent;
53import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
54import com.android.systemui.recents.misc.ReferenceCountedTrigger;
55import com.android.systemui.recents.misc.SystemServicesProxy;
56import com.android.systemui.recents.misc.Utilities;
57import com.android.systemui.recents.model.Task;
58import com.android.systemui.recents.model.TaskStack;
59
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    @ViewDebug.ExportedProperty(category="recents")
135    private boolean mTaskDataLoaded;
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    TaskViewThumbnail mThumbnailView;
153    @ViewDebug.ExportedProperty(deepExport=true, prefix="header_")
154    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 = new AnimateableViewBounds(this, res.getDimensionPixelSize(
181                R.dimen.recents_task_view_shadow_rounded_corners_radius));
182        if (config.fakeShadows) {
183            setBackground(new FakeShadowDrawable(res, config));
184        }
185        setOutlineProvider(mViewBounds);
186        setOnLongClickListener(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    /** Returns the view bounds. */
210    AnimateableViewBounds getViewBounds() {
211        return mViewBounds;
212    }
213
214    @Override
215    protected void onFinishInflate() {
216        // Bind the views
217        mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar);
218        mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail);
219        mThumbnailView.updateClipToTaskBar(mHeaderView);
220        mActionButtonView = findViewById(R.id.lock_to_app_fab);
221        mActionButtonView.setOutlineProvider(new ViewOutlineProvider() {
222            @Override
223            public void getOutline(View view, Outline outline) {
224                // Set the outline to match the FAB background
225                outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight());
226                outline.setAlpha(0.35f);
227            }
228        });
229        mActionButtonView.setOnClickListener(this);
230        mActionButtonTranslationZ = mActionButtonView.getTranslationZ();
231    }
232
233    /**
234     * Update the task view when the configuration changes.
235     */
236    void onConfigurationChanged() {
237        mHeaderView.onConfigurationChanged();
238    }
239
240    @Override
241    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
242        super.onSizeChanged(w, h, oldw, oldh);
243        if (w > 0 && h > 0) {
244            mHeaderView.onTaskViewSizeChanged(w, h);
245            mThumbnailView.onTaskViewSizeChanged(w, h);
246
247            mActionButtonView.setTranslationX(w - getMeasuredWidth());
248            mActionButtonView.setTranslationY(h - getMeasuredHeight());
249        }
250    }
251
252    @Override
253    public boolean hasOverlappingRendering() {
254        return false;
255    }
256
257    @Override
258    public boolean onInterceptTouchEvent(MotionEvent ev) {
259        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
260            mDownTouchPos.set((int) (ev.getX() * getScaleX()), (int) (ev.getY() * getScaleY()));
261        }
262        return super.onInterceptTouchEvent(ev);
263    }
264
265
266    @Override
267    protected void measureContents(int width, int height) {
268        int widthWithoutPadding = width - mPaddingLeft - mPaddingRight;
269        int heightWithoutPadding = height - mPaddingTop - mPaddingBottom;
270        int widthSpec = MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY);
271        int heightSpec = MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY);
272
273        // Measure the content
274        measureChildren(widthSpec, heightSpec);
275
276        setMeasuredDimension(width, height);
277    }
278
279    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform,
280            AnimationProps toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) {
281        RecentsConfiguration config = Recents.getConfiguration();
282        cancelTransformAnimation();
283
284        // Compose the animations for the transform
285        mTmpAnimators.clear();
286        toTransform.applyToTaskView(this, mTmpAnimators, toAnimation, !config.fakeShadows);
287        if (toAnimation.isImmediate()) {
288            if (Float.compare(getDimAlpha(), toTransform.dimAlpha) != 0) {
289                setDimAlpha(toTransform.dimAlpha);
290            }
291            if (Float.compare(mViewBounds.getAlpha(), toTransform.viewOutlineAlpha) != 0) {
292                mViewBounds.setAlpha(toTransform.viewOutlineAlpha);
293            }
294            // Manually call back to the animator listener and update callback
295            if (toAnimation.getListener() != null) {
296                toAnimation.getListener().onAnimationEnd(null);
297            }
298            if (updateCallback != null) {
299                updateCallback.onAnimationUpdate(null);
300            }
301        } else {
302            // Both the progress and the update are a function of the bounds movement of the task
303            if (Float.compare(getDimAlpha(), toTransform.dimAlpha) != 0) {
304                mDimAnimator = ObjectAnimator.ofFloat(this, DIM_ALPHA, getDimAlpha(),
305                        toTransform.dimAlpha);
306                mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, mDimAnimator));
307            }
308            if (Float.compare(mViewBounds.getAlpha(), toTransform.viewOutlineAlpha) != 0) {
309                mOutlineAnimator = ObjectAnimator.ofFloat(this, VIEW_OUTLINE_ALPHA,
310                        mViewBounds.getAlpha(), toTransform.viewOutlineAlpha);
311                mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, mOutlineAnimator));
312            }
313            if (updateCallback != null) {
314                ValueAnimator updateCallbackAnim = ValueAnimator.ofInt(0, 1);
315                updateCallbackAnim.addUpdateListener(updateCallback);
316                mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, updateCallbackAnim));
317            }
318
319            // Create the animator
320            mTransformAnimation = toAnimation.createAnimator(mTmpAnimators);
321            mTransformAnimation.start();
322            mTargetAnimationTransform.copyFrom(toTransform);
323        }
324    }
325
326    /** Resets this view's properties */
327    void resetViewProperties() {
328        cancelTransformAnimation();
329        setDimAlpha(0);
330        setVisibility(View.VISIBLE);
331        getViewBounds().reset();
332        getHeaderView().reset();
333        TaskViewTransform.reset(this);
334
335        mActionButtonView.setScaleX(1f);
336        mActionButtonView.setScaleY(1f);
337        mActionButtonView.setAlpha(0f);
338        mActionButtonView.setTranslationX(0f);
339        mActionButtonView.setTranslationY(0f);
340        mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
341        if (mIncompatibleAppToastView != null) {
342            mIncompatibleAppToastView.setVisibility(View.INVISIBLE);
343        }
344    }
345
346    /**
347     * @return whether we are animating towards {@param transform}
348     */
349    boolean isAnimatingTo(TaskViewTransform transform) {
350        return mTransformAnimation != null && mTransformAnimation.isStarted()
351                && mTargetAnimationTransform.isSame(transform);
352    }
353
354    /**
355     * Cancels any current transform animations.
356     */
357    public void cancelTransformAnimation() {
358        Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
359        Utilities.cancelAnimationWithoutCallbacks(mDimAnimator);
360        Utilities.cancelAnimationWithoutCallbacks(mOutlineAnimator);
361    }
362
363    /** Enables/disables handling touch on this task view. */
364    void setTouchEnabled(boolean enabled) {
365        setOnClickListener(enabled ? this : null);
366    }
367
368    /** Animates this task view if the user does not interact with the stack after a certain time. */
369    void startNoUserInteractionAnimation() {
370        mHeaderView.startNoUserInteractionAnimation();
371    }
372
373    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
374    void setNoUserInteractionState() {
375        mHeaderView.setNoUserInteractionState();
376    }
377
378    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
379    void resetNoUserInteractionState() {
380        mHeaderView.resetNoUserInteractionState();
381    }
382
383    /** Dismisses this task. */
384    void dismissTask() {
385        // Animate out the view and call the callback
386        final TaskView tv = this;
387        DismissTaskViewEvent dismissEvent = new DismissTaskViewEvent(tv);
388        dismissEvent.addPostAnimationCallback(new Runnable() {
389            @Override
390            public void run() {
391                EventBus.getDefault().send(new TaskViewDismissedEvent(mTask, tv,
392                        new AnimationProps(TaskStackView.DEFAULT_SYNC_STACK_DURATION,
393                                Interpolators.FAST_OUT_SLOW_IN)));
394            }
395        });
396        EventBus.getDefault().send(dismissEvent);
397    }
398
399    /**
400     * Returns whether this view should be clipped, or any views below should clip against this
401     * view.
402     */
403    boolean shouldClipViewInStack() {
404        // Never clip for freeform tasks or if invisible
405        if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE) {
406            return false;
407        }
408        return mClipViewInStack;
409    }
410
411    /** Sets whether this view should be clipped, or clipped against. */
412    void setClipViewInStack(boolean clip) {
413        if (clip != mClipViewInStack) {
414            mClipViewInStack = clip;
415            if (mCb != null) {
416                mCb.onTaskViewClipStateChanged(this);
417            }
418        }
419    }
420
421    public TaskViewHeader getHeaderView() {
422        return mHeaderView;
423    }
424
425    /**
426     * Sets the current dim.
427     */
428    public void setDimAlpha(float dimAlpha) {
429        mDimAlpha = dimAlpha;
430        mThumbnailView.setDimAlpha(dimAlpha);
431        mHeaderView.setDimAlpha(dimAlpha);
432    }
433
434    /**
435     * Sets the current dim without updating the header's dim.
436     */
437    public void setDimAlphaWithoutHeader(float dimAlpha) {
438        mDimAlpha = dimAlpha;
439        mThumbnailView.setDimAlpha(dimAlpha);
440    }
441
442    /**
443     * Returns the current dim.
444     */
445    public float getDimAlpha() {
446        return mDimAlpha;
447    }
448
449    /**
450     * Explicitly sets the focused state of this task.
451     */
452    public void setFocusedState(boolean isFocused, boolean requestViewFocus) {
453        SystemServicesProxy ssp = Recents.getSystemServices();
454        if (isFocused) {
455            if (requestViewFocus && !isFocused()) {
456                requestFocus();
457            }
458            if (requestViewFocus && !isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
459                requestAccessibilityFocus();
460            }
461        } else {
462            if (isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
463                clearAccessibilityFocus();
464            }
465        }
466    }
467
468    /**
469     * Shows the action button.
470     * @param fadeIn whether or not to animate the action button in.
471     * @param fadeInDuration the duration of the action button animation, only used if
472     *                       {@param fadeIn} is true.
473     */
474    public void showActionButton(boolean fadeIn, int fadeInDuration) {
475        mActionButtonView.setVisibility(View.VISIBLE);
476
477        if (fadeIn && mActionButtonView.getAlpha() < 1f) {
478            mActionButtonView.animate()
479                    .alpha(1f)
480                    .scaleX(1f)
481                    .scaleY(1f)
482                    .setDuration(fadeInDuration)
483                    .setInterpolator(Interpolators.ALPHA_IN)
484                    .start();
485        } else {
486            mActionButtonView.setScaleX(1f);
487            mActionButtonView.setScaleY(1f);
488            mActionButtonView.setAlpha(1f);
489            mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
490        }
491    }
492
493    /**
494     * Immediately hides the action button.
495     *
496     * @param fadeOut whether or not to animate the action button out.
497     */
498    public void hideActionButton(boolean fadeOut, int fadeOutDuration, boolean scaleDown,
499            final Animator.AnimatorListener animListener) {
500        if (fadeOut && mActionButtonView.getAlpha() > 0f) {
501            if (scaleDown) {
502                float toScale = 0.9f;
503                mActionButtonView.animate()
504                        .scaleX(toScale)
505                        .scaleY(toScale);
506            }
507            mActionButtonView.animate()
508                    .alpha(0f)
509                    .setDuration(fadeOutDuration)
510                    .setInterpolator(Interpolators.ALPHA_OUT)
511                    .withEndAction(new Runnable() {
512                        @Override
513                        public void run() {
514                            if (animListener != null) {
515                                animListener.onAnimationEnd(null);
516                            }
517                            mActionButtonView.setVisibility(View.INVISIBLE);
518                        }
519                    })
520                    .start();
521        } else {
522            mActionButtonView.setAlpha(0f);
523            mActionButtonView.setVisibility(View.INVISIBLE);
524            if (animListener != null) {
525                animListener.onAnimationEnd(null);
526            }
527        }
528    }
529
530    /**** TaskStackAnimationHelper.Callbacks Implementation ****/
531
532    @Override
533    public void onPrepareLaunchTargetForEnterAnimation() {
534        // These values will be animated in when onStartLaunchTargetEnterAnimation() is called
535        setDimAlphaWithoutHeader(0);
536        mActionButtonView.setAlpha(0f);
537        if (mIncompatibleAppToastView != null &&
538                mIncompatibleAppToastView.getVisibility() == View.VISIBLE) {
539            mIncompatibleAppToastView.setAlpha(0f);
540        }
541    }
542
543    @Override
544    public void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration,
545            boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger) {
546        Utilities.cancelAnimationWithoutCallbacks(mDimAnimator);
547
548        // Dim the view after the app window transitions down into recents
549        postAnimationTrigger.increment();
550        AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT);
551        mDimAnimator = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this,
552                DIM_ALPHA_WITHOUT_HEADER, getDimAlpha(), transform.dimAlpha));
553        mDimAnimator.addListener(postAnimationTrigger.decrementOnAnimationEnd());
554        mDimAnimator.start();
555
556        if (screenPinningEnabled) {
557            showActionButton(true /* fadeIn */, duration /* fadeInDuration */);
558        }
559
560        if (mIncompatibleAppToastView != null &&
561                mIncompatibleAppToastView.getVisibility() == View.VISIBLE) {
562            mIncompatibleAppToastView.animate()
563                    .alpha(1f)
564                    .setDuration(duration)
565                    .setInterpolator(Interpolators.ALPHA_IN)
566                    .start();
567        }
568    }
569
570    @Override
571    public void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested,
572            ReferenceCountedTrigger postAnimationTrigger) {
573        Utilities.cancelAnimationWithoutCallbacks(mDimAnimator);
574
575        // Un-dim the view before/while launching the target
576        AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT);
577        mDimAnimator = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this,
578                DIM_ALPHA, getDimAlpha(), 0));
579        mDimAnimator.start();
580
581        postAnimationTrigger.increment();
582        hideActionButton(true /* fadeOut */, duration,
583                !screenPinningRequested /* scaleDown */,
584                postAnimationTrigger.decrementOnAnimationEnd());
585    }
586
587    @Override
588    public void onStartFrontTaskEnterAnimation(boolean screenPinningEnabled) {
589        if (screenPinningEnabled) {
590            showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
591        }
592    }
593
594    /**** TaskCallbacks Implementation ****/
595
596    public void onTaskBound(Task t, boolean touchExplorationEnabled, int displayOrientation,
597            Rect displayRect) {
598        SystemServicesProxy ssp = Recents.getSystemServices();
599        mTouchExplorationEnabled = touchExplorationEnabled;
600        mTask = t;
601        mTask.addCallback(this);
602        mIsDisabledInSafeMode = !mTask.isSystemApp && ssp.isInSafeMode();
603        mThumbnailView.bindToTask(mTask, mIsDisabledInSafeMode, displayOrientation, displayRect);
604        mHeaderView.bindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode);
605
606        if (!t.isDockable && ssp.hasDockedTask()) {
607            if (mIncompatibleAppToastView == null) {
608                mIncompatibleAppToastView = Utilities.findViewStubById(this,
609                        R.id.incompatible_app_toast_stub).inflate();
610                TextView msg = (TextView) findViewById(com.android.internal.R.id.message);
611                msg.setText(R.string.recents_incompatible_app_message);
612            }
613            mIncompatibleAppToastView.setVisibility(View.VISIBLE);
614        } else if (mIncompatibleAppToastView != null) {
615            mIncompatibleAppToastView.setVisibility(View.INVISIBLE);
616        }
617    }
618
619    @Override
620    public void onTaskDataLoaded(Task task, ActivityManager.TaskThumbnailInfo thumbnailInfo) {
621        // Update each of the views to the new task data
622        mThumbnailView.onTaskDataLoaded(thumbnailInfo);
623        mHeaderView.onTaskDataLoaded();
624        mTaskDataLoaded = true;
625    }
626
627    @Override
628    public void onTaskDataUnloaded() {
629        // Unbind each of the views from the task and remove the task callback
630        mTask.removeCallback(this);
631        mThumbnailView.unbindFromTask();
632        mHeaderView.unbindFromTask(mTouchExplorationEnabled);
633        mTaskDataLoaded = false;
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        // Since we are clipping the view to the bounds, manually do the hit test
677        Rect clipBounds = new Rect(mViewBounds.mClipBounds);
678        clipBounds.scale(getScaleX());
679        boolean inBounds = clipBounds.contains(mDownTouchPos.x, mDownTouchPos.y);
680        if (v == this && inBounds && !ssp.hasDockedTask()) {
681            // Start listening for drag events
682            setClipViewInStack(false);
683
684            mDownTouchPos.x += ((1f - getScaleX()) * getWidth()) / 2;
685            mDownTouchPos.y += ((1f - getScaleY()) * getHeight()) / 2;
686
687            EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
688            EventBus.getDefault().send(new DragStartEvent(mTask, this, mDownTouchPos));
689            return true;
690        }
691        return false;
692    }
693
694    /**** Events ****/
695
696    public final void onBusEvent(DragEndEvent event) {
697        if (!(event.dropTarget instanceof TaskStack.DockState)) {
698            event.addPostAnimationCallback(new Runnable() {
699                @Override
700                public void run() {
701                    // Animate the drag view back from where it is, to the view location, then after
702                    // it returns, update the clip state
703                    setClipViewInStack(true);
704                }
705            });
706        }
707        EventBus.getDefault().unregister(this);
708    }
709}
710