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