TaskView.java revision 5c9f4b90bf56b242467f0b5b4d2c7c5b71e6a777
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.ObjectAnimator;
21import android.animation.ValueAnimator;
22import android.content.Context;
23import android.graphics.*;
24import android.util.AttributeSet;
25import android.view.accessibility.AccessibilityManager;
26import android.view.View;
27import android.view.ViewOutlineProvider;
28import android.view.animation.AccelerateInterpolator;
29import android.widget.FrameLayout;
30import com.android.internal.logging.MetricsLogger;
31import com.android.systemui.R;
32import com.android.systemui.recents.Constants;
33import com.android.systemui.recents.RecentsConfiguration;
34import com.android.systemui.recents.misc.Utilities;
35import com.android.systemui.recents.model.Task;
36import com.android.systemui.statusbar.phone.PhoneStatusBar;
37
38/* A task view */
39public class TaskView extends FrameLayout implements Task.TaskCallbacks,
40        View.OnClickListener, View.OnLongClickListener {
41
42    /** The TaskView callbacks */
43    interface TaskViewCallbacks {
44        public void onTaskViewAppIconClicked(TaskView tv);
45        public void onTaskViewAppInfoClicked(TaskView tv);
46        public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
47        public void onTaskViewDismissed(TaskView tv);
48        public void onTaskViewClipStateChanged(TaskView tv);
49        public void onTaskViewFocusChanged(TaskView tv, boolean focused);
50
51        public void onTaskResize(TaskView tv);
52    }
53
54    RecentsConfiguration mConfig;
55
56    float mTaskProgress;
57    ObjectAnimator mTaskProgressAnimator;
58    float mMaxDimScale;
59    int mDimAlpha;
60    AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1f);
61    PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
62    Paint mDimLayerPaint = new Paint();
63    float mActionButtonTranslationZ;
64
65    Task mTask;
66    boolean mTaskDataLoaded;
67    boolean mIsFocused;
68    boolean mFocusAnimationsEnabled;
69    boolean mClipViewInStack;
70    AnimateableViewBounds mViewBounds;
71
72    View mContent;
73    TaskViewThumbnail mThumbnailView;
74    TaskViewHeader mHeaderView;
75    View mActionButtonView;
76    TaskViewCallbacks mCb;
77
78    // Optimizations
79    ValueAnimator.AnimatorUpdateListener mUpdateDimListener =
80            new ValueAnimator.AnimatorUpdateListener() {
81                @Override
82                public void onAnimationUpdate(ValueAnimator animation) {
83                    setTaskProgress((Float) animation.getAnimatedValue());
84                }
85            };
86
87
88    public TaskView(Context context) {
89        this(context, null);
90    }
91
92    public TaskView(Context context, AttributeSet attrs) {
93        this(context, attrs, 0);
94    }
95
96    public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
97        this(context, attrs, defStyleAttr, 0);
98    }
99
100    public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
101        super(context, attrs, defStyleAttr, defStyleRes);
102        mConfig = RecentsConfiguration.getInstance();
103        mMaxDimScale = mConfig.taskStackMaxDim / 255f;
104        mClipViewInStack = true;
105        mViewBounds = new AnimateableViewBounds(this, mConfig.taskViewRoundedCornerRadiusPx);
106        setTaskProgress(getTaskProgress());
107        setDim(getDim());
108        if (mConfig.fakeShadows) {
109            setBackground(new FakeShadowDrawable(context.getResources(), mConfig));
110        }
111        setOutlineProvider(mViewBounds);
112    }
113
114    /** Set callback */
115    void setCallbacks(TaskViewCallbacks cb) {
116        mCb = cb;
117    }
118
119    /** Resets this TaskView for reuse. */
120    void reset() {
121        resetViewProperties();
122        resetNoUserInteractionState();
123        setClipViewInStack(false);
124        setCallbacks(null);
125    }
126
127    /** Gets the task */
128    Task getTask() {
129        return mTask;
130    }
131
132    /** Returns the view bounds. */
133    AnimateableViewBounds getViewBounds() {
134        return mViewBounds;
135    }
136
137    @Override
138    protected void onFinishInflate() {
139        // Bind the views
140        mContent = findViewById(R.id.task_view_content);
141        mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar);
142        mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail);
143        mThumbnailView.updateClipToTaskBar(mHeaderView);
144        mActionButtonView = findViewById(R.id.lock_to_app_fab);
145        mActionButtonView.setOutlineProvider(new ViewOutlineProvider() {
146            @Override
147            public void getOutline(View view, Outline outline) {
148                // Set the outline to match the FAB background
149                outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight());
150            }
151        });
152        mActionButtonTranslationZ = mActionButtonView.getTranslationZ();
153    }
154
155    @Override
156    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
157        int width = MeasureSpec.getSize(widthMeasureSpec);
158        int height = MeasureSpec.getSize(heightMeasureSpec);
159
160        int widthWithoutPadding = width - mPaddingLeft - mPaddingRight;
161        int heightWithoutPadding = height - mPaddingTop - mPaddingBottom;
162
163        // Measure the content
164        mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
165                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
166
167        // Measure the bar view, and action button
168        mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
169                MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
170        mActionButtonView.measure(
171                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST),
172                MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST));
173        // Measure the thumbnail to be square
174        mThumbnailView.measure(
175                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
176                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
177        setMeasuredDimension(width, height);
178        invalidateOutline();
179    }
180
181    /** Synchronizes this view's properties with the task's transform */
182    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
183        updateViewPropertiesToTaskTransform(toTransform, duration, null);
184    }
185
186    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration,
187                                             ValueAnimator.AnimatorUpdateListener updateCallback) {
188        // Apply the transform
189        toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator, false,
190                !mConfig.fakeShadows, updateCallback);
191
192        // Update the task progress
193        Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator);
194        if (duration <= 0) {
195            setTaskProgress(toTransform.p);
196        } else {
197            mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p);
198            mTaskProgressAnimator.setDuration(duration);
199            mTaskProgressAnimator.addUpdateListener(mUpdateDimListener);
200            mTaskProgressAnimator.start();
201        }
202    }
203
204    /** Resets this view's properties */
205    void resetViewProperties() {
206        setDim(0);
207        setLayerType(View.LAYER_TYPE_NONE, null);
208        TaskViewTransform.reset(this);
209        if (mActionButtonView != null) {
210            mActionButtonView.setScaleX(1f);
211            mActionButtonView.setScaleY(1f);
212            mActionButtonView.setAlpha(1f);
213            mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
214        }
215    }
216
217    /**
218     * When we are un/filtering, this method will set up the transform that we are animating to,
219     * in order to hide the task.
220     */
221    void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
222        // Fade the view out and slide it away
223        toTransform.alpha = 0f;
224        toTransform.translationY += 200;
225        toTransform.translationZ = 0;
226    }
227
228    /**
229     * When we are un/filtering, this method will setup the transform that we are animating from,
230     * in order to show the task.
231     */
232    void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) {
233        // Fade the view in
234        fromTransform.alpha = 0f;
235    }
236
237    /** Prepares this task view for the enter-recents animations.  This is called earlier in the
238     * first layout because the actual animation into recents may take a long time. */
239    void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
240                                             boolean occludesLaunchTarget, int offscreenY) {
241        int initialDim = getDim();
242        if (mConfig.launchedHasConfigurationChanged) {
243            // Just load the views as-is
244        } else if (mConfig.launchedFromAppWithThumbnail) {
245            if (isTaskViewLaunchTargetTask) {
246                // Set the dim to 0 so we can animate it in
247                initialDim = 0;
248                // Hide the action button
249                mActionButtonView.setAlpha(0f);
250            } else if (occludesLaunchTarget) {
251                // Move the task view off screen (below) so we can animate it in
252                setTranslationY(offscreenY);
253            }
254
255        } else if (mConfig.launchedFromHome) {
256            // Move the task view off screen (below) so we can animate it in
257            setTranslationY(offscreenY);
258            setTranslationZ(0);
259            setScaleX(1f);
260            setScaleY(1f);
261        }
262        // Apply the current dim
263        setDim(initialDim);
264        // Prepare the thumbnail view alpha
265        mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask);
266    }
267
268    /** Animates this task view as it enters recents */
269    void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
270        final TaskViewTransform transform = ctx.currentTaskTransform;
271        int startDelay = 0;
272
273        if (mConfig.launchedFromAppWithThumbnail) {
274            if (mTask.isLaunchTarget) {
275                // Animate the dim/overlay
276                if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) {
277                    // Animate the thumbnail alpha before the dim animation (to prevent updating the
278                    // hardware layer)
279                    mThumbnailView.startEnterRecentsAnimation(mConfig.transitionEnterFromAppDelay,
280                            new Runnable() {
281                                @Override
282                                public void run() {
283                                    animateDimToProgress(0, mConfig.taskViewEnterFromAppDuration,
284                                            ctx.postAnimationTrigger.decrementOnAnimationEnd());
285                                }
286                            });
287                } else {
288                    // Immediately start the dim animation
289                    animateDimToProgress(mConfig.transitionEnterFromAppDelay,
290                            mConfig.taskViewEnterFromAppDuration,
291                            ctx.postAnimationTrigger.decrementOnAnimationEnd());
292                }
293                ctx.postAnimationTrigger.increment();
294
295                // Animate the action button in
296                fadeInActionButton(mConfig.transitionEnterFromAppDelay,
297                        mConfig.taskViewEnterFromAppDuration);
298            } else {
299                // Animate the task up if it was occluding the launch target
300                if (ctx.currentTaskOccludesLaunchTarget) {
301                    setTranslationY(transform.translationY + mConfig.taskViewAffiliateGroupEnterOffsetPx);
302                    setAlpha(0f);
303                    animate().alpha(1f)
304                            .translationY(transform.translationY)
305                            .setStartDelay(mConfig.transitionEnterFromAppDelay)
306                            .setUpdateListener(null)
307                            .setInterpolator(mConfig.fastOutSlowInInterpolator)
308                            .setDuration(mConfig.taskViewEnterFromHomeDuration)
309                            .withEndAction(new Runnable() {
310                                @Override
311                                public void run() {
312                                    // Decrement the post animation trigger
313                                    ctx.postAnimationTrigger.decrement();
314                                }
315                            })
316                            .start();
317                    ctx.postAnimationTrigger.increment();
318                }
319            }
320            startDelay = mConfig.transitionEnterFromAppDelay;
321
322        } else if (mConfig.launchedFromHome) {
323            // Animate the tasks up
324            int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
325            int delay = mConfig.transitionEnterFromHomeDelay +
326                    frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay;
327
328            setScaleX(transform.scale);
329            setScaleY(transform.scale);
330            if (!mConfig.fakeShadows) {
331                animate().translationZ(transform.translationZ);
332            }
333            animate()
334                    .translationY(transform.translationY)
335                    .setStartDelay(delay)
336                    .setUpdateListener(ctx.updateListener)
337                    .setInterpolator(mConfig.quintOutInterpolator)
338                    .setDuration(mConfig.taskViewEnterFromHomeDuration +
339                            frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay)
340                    .withEndAction(new Runnable() {
341                        @Override
342                        public void run() {
343                            // Decrement the post animation trigger
344                            ctx.postAnimationTrigger.decrement();
345                        }
346                    })
347                    .start();
348            ctx.postAnimationTrigger.increment();
349            startDelay = delay;
350        }
351
352        // Enable the focus animations from this point onwards so that they aren't affected by the
353        // window transitions
354        postDelayed(new Runnable() {
355            @Override
356            public void run() {
357                enableFocusAnimations();
358            }
359        }, startDelay);
360    }
361
362    public void fadeInActionButton(int delay, int duration) {
363        // Hide the action button
364        mActionButtonView.setAlpha(0f);
365
366        // Animate the action button in
367        mActionButtonView.animate().alpha(1f)
368                .setStartDelay(delay)
369                .setDuration(duration)
370                .setInterpolator(PhoneStatusBar.ALPHA_IN)
371                .start();
372    }
373
374    /** Animates this task view as it leaves recents by pressing home. */
375    void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
376        animate()
377                .translationY(ctx.offscreenTranslationY)
378                .setStartDelay(0)
379                .setUpdateListener(null)
380                .setInterpolator(mConfig.fastOutLinearInInterpolator)
381                .setDuration(mConfig.taskViewExitToHomeDuration)
382                .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
383                .start();
384        ctx.postAnimationTrigger.increment();
385    }
386
387    /** Animates this task view away when dismissing all tasks. */
388    void startDismissAllAnimation() {
389        dismissTask();
390    }
391
392    /** Animates this task view as it exits recents */
393    void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask,
394            boolean occludesLaunchTarget, boolean lockToTask) {
395        if (isLaunchingTask) {
396            // Animate the thumbnail alpha back into full opacity for the window animation out
397            mThumbnailView.startLaunchTaskAnimation(postAnimRunnable);
398
399            // Animate the dim
400            if (mDimAlpha > 0) {
401                ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
402                anim.setDuration(mConfig.taskViewExitToAppDuration);
403                anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
404                anim.start();
405            }
406
407            // Animate the action button away
408            if (!lockToTask) {
409                float toScale = 0.9f;
410                mActionButtonView.animate()
411                        .scaleX(toScale)
412                        .scaleY(toScale);
413            }
414            mActionButtonView.animate()
415                    .alpha(0f)
416                    .setStartDelay(0)
417                    .setDuration(mConfig.taskViewExitToAppDuration)
418                    .setInterpolator(mConfig.fastOutLinearInInterpolator)
419                    .start();
420        } else {
421            // Hide the dismiss button
422            mHeaderView.startLaunchTaskDismissAnimation();
423            // If this is another view in the task grouping and is in front of the launch task,
424            // animate it away first
425            if (occludesLaunchTarget) {
426                animate().alpha(0f)
427                    .translationY(getTranslationY() + mConfig.taskViewAffiliateGroupEnterOffsetPx)
428                    .setStartDelay(0)
429                    .setUpdateListener(null)
430                    .setInterpolator(mConfig.fastOutLinearInInterpolator)
431                    .setDuration(mConfig.taskViewExitToAppDuration)
432                    .start();
433            }
434        }
435    }
436
437    /** Animates the deletion of this task view */
438    void startDeleteTaskAnimation(final Runnable r, int delay) {
439        // Disabling clipping with the stack while the view is animating away
440        setClipViewInStack(false);
441
442        animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)
443            .alpha(0f)
444            .setStartDelay(delay)
445            .setUpdateListener(null)
446            .setInterpolator(mConfig.fastOutSlowInInterpolator)
447            .setDuration(mConfig.taskViewRemoveAnimDuration)
448            .withEndAction(new Runnable() {
449                @Override
450                public void run() {
451                    if (r != null) {
452                        r.run();
453                    }
454
455                    // Re-enable clipping with the stack (we will reuse this view)
456                    setClipViewInStack(true);
457                }
458            })
459            .start();
460    }
461
462    /** Enables/disables handling touch on this task view. */
463    void setTouchEnabled(boolean enabled) {
464        setOnClickListener(enabled ? this : null);
465    }
466
467    /** Animates this task view if the user does not interact with the stack after a certain time. */
468    void startNoUserInteractionAnimation() {
469        mHeaderView.startNoUserInteractionAnimation();
470    }
471
472    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
473    void setNoUserInteractionState() {
474        mHeaderView.setNoUserInteractionState();
475    }
476
477    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
478    void resetNoUserInteractionState() {
479        mHeaderView.resetNoUserInteractionState();
480    }
481
482    /** Dismisses this task. */
483    void dismissTask() {
484        // Animate out the view and call the callback
485        final TaskView tv = this;
486        startDeleteTaskAnimation(new Runnable() {
487            @Override
488            public void run() {
489                if (mCb != null) {
490                    mCb.onTaskViewDismissed(tv);
491                }
492            }
493        }, 0);
494    }
495
496    /**
497     * Returns whether this view should be clipped, or any views below should clip against this
498     * view.
499     */
500    boolean shouldClipViewInStack() {
501        return mClipViewInStack && (getVisibility() == View.VISIBLE);
502    }
503
504    /** Sets whether this view should be clipped, or clipped against. */
505    void setClipViewInStack(boolean clip) {
506        if (clip != mClipViewInStack) {
507            mClipViewInStack = clip;
508            if (mCb != null) {
509                mCb.onTaskViewClipStateChanged(this);
510            }
511        }
512    }
513
514    /** Sets the current task progress. */
515    public void setTaskProgress(float p) {
516        mTaskProgress = p;
517        mViewBounds.setAlpha(p);
518        updateDimFromTaskProgress();
519    }
520
521    /** Returns the current task progress. */
522    public float getTaskProgress() {
523        return mTaskProgress;
524    }
525
526    /** Returns the current dim. */
527    public void setDim(int dim) {
528        mDimAlpha = dim;
529        if (mConfig.useHardwareLayers) {
530            // Defer setting hardware layers if we have not yet measured, or there is no dim to draw
531            if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
532                mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0));
533                mDimLayerPaint.setColorFilter(mDimColorFilter);
534                mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
535            }
536        } else {
537            float dimAlpha = mDimAlpha / 255.0f;
538            if (mThumbnailView != null) {
539                mThumbnailView.setDimAlpha(dimAlpha);
540            }
541            if (mHeaderView != null) {
542                mHeaderView.setDimAlpha(dim);
543            }
544        }
545    }
546
547    /** Returns the current dim. */
548    public int getDim() {
549        return mDimAlpha;
550    }
551
552    /** Animates the dim to the task progress. */
553    void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) {
554        // Animate the dim into view as well
555        int toDim = getDimFromTaskProgress();
556        if (toDim != getDim()) {
557            ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim);
558            anim.setStartDelay(delay);
559            anim.setDuration(duration);
560            if (postAnimRunnable != null) {
561                anim.addListener(postAnimRunnable);
562            }
563            anim.start();
564        }
565    }
566
567    /** Compute the dim as a function of the scale of this view. */
568    int getDimFromTaskProgress() {
569        float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress);
570        return (int) (dim * 255);
571    }
572
573    /** Update the dim as a function of the scale of this view. */
574    void updateDimFromTaskProgress() {
575        setDim(getDimFromTaskProgress());
576    }
577
578    /**** View focus state ****/
579
580    /**
581     * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
582     * if the view is not currently visible, or we are in touch state (where we still want to keep
583     * track of focus).
584     */
585    public void setFocusedTask(boolean animateFocusedState) {
586        mIsFocused = true;
587        if (mFocusAnimationsEnabled) {
588            // Focus the header bar
589            mHeaderView.onTaskViewFocusChanged(true, animateFocusedState);
590        }
591        // Update the thumbnail alpha with the focus
592        mThumbnailView.onFocusChanged(true);
593        // Call the callback
594        if (mCb != null) {
595            mCb.onTaskViewFocusChanged(this, true);
596        }
597        // Workaround, we don't always want it focusable in touch mode, but we want the first task
598        // to be focused after the enter-recents animation, which can be triggered from either touch
599        // or keyboard
600        setFocusableInTouchMode(true);
601        requestFocus();
602        setFocusableInTouchMode(false);
603        invalidate();
604    }
605
606    /**
607     * Unsets the focused task explicitly.
608     */
609    void unsetFocusedTask() {
610        mIsFocused = false;
611        if (mFocusAnimationsEnabled) {
612            // Un-focus the header bar
613            mHeaderView.onTaskViewFocusChanged(false, true);
614        }
615
616        // Update the thumbnail alpha with the focus
617        mThumbnailView.onFocusChanged(false);
618        // Call the callback
619        if (mCb != null) {
620            mCb.onTaskViewFocusChanged(this, false);
621        }
622        invalidate();
623    }
624
625    /**
626     * Updates the explicitly focused state when the view focus changes.
627     */
628    @Override
629    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
630        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
631        if (!gainFocus) {
632            unsetFocusedTask();
633        }
634    }
635
636    /**
637     * Returns whether we have explicitly been focused.
638     */
639    public boolean isFocusedTask() {
640        return mIsFocused || isFocused();
641    }
642
643    /** Enables all focus animations. */
644    void enableFocusAnimations() {
645        boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled;
646        mFocusAnimationsEnabled = true;
647        if (mIsFocused && !wasFocusAnimationsEnabled) {
648            // Re-notify the header if we were focused and animations were not previously enabled
649            mHeaderView.onTaskViewFocusChanged(true, true);
650        }
651    }
652
653    public void disableLayersForOneFrame() {
654        mHeaderView.disableLayersForOneFrame();
655    }
656
657    /**** TaskCallbacks Implementation ****/
658
659    /** Binds this task view to the task */
660    public void onTaskBound(Task t) {
661        mTask = t;
662        mTask.setCallbacks(this);
663
664        // Hide the action button if lock to app is disabled for this view
665        int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE;
666        if (mActionButtonView.getVisibility() != lockButtonVisibility) {
667            mActionButtonView.setVisibility(lockButtonVisibility);
668            requestLayout();
669        }
670    }
671
672    @Override
673    public void onTaskDataLoaded() {
674        if (mThumbnailView != null && mHeaderView != null) {
675            // Bind each of the views to the new task data
676            mThumbnailView.rebindToTask(mTask);
677            mHeaderView.rebindToTask(mTask);
678            // Rebind any listeners
679            AccessibilityManager am = (AccessibilityManager) getContext().
680                    getSystemService(Context.ACCESSIBILITY_SERVICE);
681            if (Constants.DebugFlags.App.EnableTaskFiltering || (am != null && am.isEnabled())) {
682                mHeaderView.mApplicationIcon.setOnClickListener(this);
683            }
684            mHeaderView.mDismissButton.setOnClickListener(this);
685            if (mConfig.multiStackEnabled) {
686                mHeaderView.mMoveTaskButton.setOnClickListener(this);
687            }
688            mActionButtonView.setOnClickListener(this);
689            mHeaderView.mApplicationIcon.setOnLongClickListener(this);
690        }
691        mTaskDataLoaded = true;
692    }
693
694    @Override
695    public void onTaskDataUnloaded() {
696        if (mThumbnailView != null && mHeaderView != null) {
697            // Unbind each of the views from the task data and remove the task callback
698            mTask.setCallbacks(null);
699            mThumbnailView.unbindFromTask();
700            mHeaderView.unbindFromTask();
701            // Unbind any listeners
702            mHeaderView.mApplicationIcon.setOnClickListener(null);
703            mHeaderView.mDismissButton.setOnClickListener(null);
704            if (mConfig.multiStackEnabled) {
705                mHeaderView.mMoveTaskButton.setOnClickListener(null);
706            }
707            mActionButtonView.setOnClickListener(null);
708            mHeaderView.mApplicationIcon.setOnLongClickListener(null);
709        }
710        mTaskDataLoaded = false;
711    }
712
713    @Override
714    public void onMultiStackDebugTaskStackIdChanged() {
715        mHeaderView.rebindToTask(mTask);
716    }
717
718    /**** View.OnClickListener Implementation ****/
719
720    @Override
721     public void onClick(final View v) {
722        final TaskView tv = this;
723        final boolean delayViewClick = (v != this) && (v != mActionButtonView);
724        if (delayViewClick) {
725            // We purposely post the handler delayed to allow for the touch feedback to draw
726            postDelayed(new Runnable() {
727                @Override
728                public void run() {
729                    if (v == mHeaderView.mApplicationIcon) {
730                        if (Constants.DebugFlags.App.EnableTaskFiltering) {
731                            if (mCb != null) {
732                                mCb.onTaskViewAppIconClicked(tv);
733                            }
734                        } else {
735                            AccessibilityManager am = (AccessibilityManager) getContext().
736                                    getSystemService(Context.ACCESSIBILITY_SERVICE);
737                            if (am != null && am.isEnabled()) {
738                                if (mCb != null) {
739                                    mCb.onTaskViewAppInfoClicked(tv);
740                                }
741                            }
742                        }
743                    } else if (v == mHeaderView.mDismissButton) {
744                        dismissTask();
745                        // Keep track of deletions by the dismiss button
746                        MetricsLogger.histogram(getContext(), "overview_task_dismissed_source",
747                                Constants.Metrics.DismissSourceHeaderButton);
748                    } else if (v == mHeaderView.mMoveTaskButton) {
749                        if (mCb != null) {
750                            mCb.onTaskResize(tv);
751                        }
752                    }
753                }
754            }, 125);
755        } else {
756            if (v == mActionButtonView) {
757                // Reset the translation of the action button before we animate it out
758                mActionButtonView.setTranslationZ(0f);
759            }
760            if (mCb != null) {
761                mCb.onTaskViewClicked(tv, tv.getTask(), (v == mActionButtonView));
762            }
763        }
764    }
765
766    /**** View.OnLongClickListener Implementation ****/
767
768    @Override
769    public boolean onLongClick(View v) {
770        if (v == mHeaderView.mApplicationIcon) {
771            if (mCb != null) {
772                mCb.onTaskViewAppInfoClicked(this);
773                return true;
774            }
775        }
776        return false;
777    }
778}
779