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