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