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