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