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