1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents.views;
18
19import android.animation.Animator;
20import android.animation.ObjectAnimator;
21import android.animation.ValueAnimator;
22import android.content.Context;
23import android.graphics.*;
24import android.util.AttributeSet;
25import android.view.View;
26import android.view.ViewOutlineProvider;
27import android.view.animation.AccelerateInterpolator;
28import android.widget.FrameLayout;
29import com.android.systemui.R;
30import com.android.systemui.recents.Constants;
31import com.android.systemui.recents.RecentsConfiguration;
32import com.android.systemui.recents.misc.Utilities;
33import com.android.systemui.recents.model.Task;
34import com.android.systemui.statusbar.phone.PhoneStatusBar;
35
36/* A task view */
37public class TaskView extends FrameLayout implements Task.TaskCallbacks,
38        View.OnClickListener, View.OnLongClickListener {
39
40    /** The TaskView callbacks */
41    interface TaskViewCallbacks {
42        public void onTaskViewAppIconClicked(TaskView tv);
43        public void onTaskViewAppInfoClicked(TaskView tv);
44        public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
45        public void onTaskViewDismissed(TaskView tv);
46        public void onTaskViewClipStateChanged(TaskView tv);
47        public void onTaskViewFocusChanged(TaskView tv, boolean focused);
48    }
49
50    RecentsConfiguration mConfig;
51
52    float mTaskProgress;
53    ObjectAnimator mTaskProgressAnimator;
54    float mMaxDimScale;
55    int mDimAlpha;
56    AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1f);
57    PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
58    Paint mDimLayerPaint = new Paint();
59    float mActionButtonTranslationZ;
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        mActionButtonTranslationZ = mActionButtonView.getTranslationZ();
149    }
150
151    @Override
152    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
153        int width = MeasureSpec.getSize(widthMeasureSpec);
154        int height = MeasureSpec.getSize(heightMeasureSpec);
155
156        int widthWithoutPadding = width - mPaddingLeft - mPaddingRight;
157        int heightWithoutPadding = height - mPaddingTop - mPaddingBottom;
158
159        // Measure the content
160        mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
161                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
162
163        // Measure the bar view, and action button
164        mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
165                MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
166        mActionButtonView.measure(
167                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST),
168                MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST));
169        // Measure the thumbnail to be square
170        mThumbnailView.measure(
171                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
172                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
173        setMeasuredDimension(width, height);
174        invalidateOutline();
175    }
176
177    /** Synchronizes this view's properties with the task's transform */
178    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
179        updateViewPropertiesToTaskTransform(toTransform, duration, null);
180    }
181
182    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration,
183                                             ValueAnimator.AnimatorUpdateListener updateCallback) {
184        // Apply the transform
185        toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator, false,
186                !mConfig.fakeShadows, updateCallback);
187
188        // Update the task progress
189        Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator);
190        if (duration <= 0) {
191            setTaskProgress(toTransform.p);
192        } else {
193            mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p);
194            mTaskProgressAnimator.setDuration(duration);
195            mTaskProgressAnimator.addUpdateListener(mUpdateDimListener);
196            mTaskProgressAnimator.start();
197        }
198    }
199
200    /** Resets this view's properties */
201    void resetViewProperties() {
202        setDim(0);
203        setLayerType(View.LAYER_TYPE_NONE, null);
204        TaskViewTransform.reset(this);
205        if (mActionButtonView != null) {
206            mActionButtonView.setScaleX(1f);
207            mActionButtonView.setScaleY(1f);
208            mActionButtonView.setAlpha(1f);
209            mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
210        }
211    }
212
213    /**
214     * When we are un/filtering, this method will set up the transform that we are animating to,
215     * in order to hide the task.
216     */
217    void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
218        // Fade the view out and slide it away
219        toTransform.alpha = 0f;
220        toTransform.translationY += 200;
221        toTransform.translationZ = 0;
222    }
223
224    /**
225     * When we are un/filtering, this method will setup the transform that we are animating from,
226     * in order to show the task.
227     */
228    void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) {
229        // Fade the view in
230        fromTransform.alpha = 0f;
231    }
232
233    /** Prepares this task view for the enter-recents animations.  This is called earlier in the
234     * first layout because the actual animation into recents may take a long time. */
235    void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
236                                             boolean occludesLaunchTarget, int offscreenY) {
237        int initialDim = getDim();
238        if (mConfig.launchedHasConfigurationChanged) {
239            // Just load the views as-is
240        } else if (mConfig.launchedFromAppWithThumbnail) {
241            if (isTaskViewLaunchTargetTask) {
242                // Set the dim to 0 so we can animate it in
243                initialDim = 0;
244                // Hide the action button
245                mActionButtonView.setAlpha(0f);
246            } else if (occludesLaunchTarget) {
247                // Move the task view off screen (below) so we can animate it in
248                setTranslationY(offscreenY);
249            }
250
251        } else if (mConfig.launchedFromHome) {
252            // Move the task view off screen (below) so we can animate it in
253            setTranslationY(offscreenY);
254            setTranslationZ(0);
255            setScaleX(1f);
256            setScaleY(1f);
257        }
258        // Apply the current dim
259        setDim(initialDim);
260        // Prepare the thumbnail view alpha
261        mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask);
262    }
263
264    /** Animates this task view as it enters recents */
265    void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
266        final TaskViewTransform transform = ctx.currentTaskTransform;
267        int startDelay = 0;
268
269        if (mConfig.launchedFromAppWithThumbnail) {
270            if (mTask.isLaunchTarget) {
271                // Animate the dim/overlay
272                if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) {
273                    // Animate the thumbnail alpha before the dim animation (to prevent updating the
274                    // hardware layer)
275                    mThumbnailView.startEnterRecentsAnimation(mConfig.transitionEnterFromAppDelay,
276                            new Runnable() {
277                                @Override
278                                public void run() {
279                                    animateDimToProgress(0, mConfig.taskViewEnterFromAppDuration,
280                                            ctx.postAnimationTrigger.decrementOnAnimationEnd());
281                                }
282                            });
283                } else {
284                    // Immediately start the dim animation
285                    animateDimToProgress(mConfig.transitionEnterFromAppDelay,
286                            mConfig.taskViewEnterFromAppDuration,
287                            ctx.postAnimationTrigger.decrementOnAnimationEnd());
288                }
289                ctx.postAnimationTrigger.increment();
290
291                // Animate the action button in
292                fadeInActionButton(mConfig.transitionEnterFromAppDelay,
293                        mConfig.taskViewEnterFromAppDuration);
294            } else {
295                // Animate the task up if it was occluding the launch target
296                if (ctx.currentTaskOccludesLaunchTarget) {
297                    setTranslationY(transform.translationY + mConfig.taskViewAffiliateGroupEnterOffsetPx);
298                    setAlpha(0f);
299                    animate().alpha(1f)
300                            .translationY(transform.translationY)
301                            .setStartDelay(mConfig.transitionEnterFromAppDelay)
302                            .setUpdateListener(null)
303                            .setInterpolator(mConfig.fastOutSlowInInterpolator)
304                            .setDuration(mConfig.taskViewEnterFromHomeDuration)
305                            .withEndAction(new Runnable() {
306                                @Override
307                                public void run() {
308                                    // Decrement the post animation trigger
309                                    ctx.postAnimationTrigger.decrement();
310                                }
311                            })
312                            .start();
313                    ctx.postAnimationTrigger.increment();
314                }
315            }
316            startDelay = mConfig.transitionEnterFromAppDelay;
317
318        } else if (mConfig.launchedFromHome) {
319            // Animate the tasks up
320            int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
321            int delay = mConfig.transitionEnterFromHomeDelay +
322                    frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay;
323
324            setScaleX(transform.scale);
325            setScaleY(transform.scale);
326            if (!mConfig.fakeShadows) {
327                animate().translationZ(transform.translationZ);
328            }
329            animate()
330                    .translationY(transform.translationY)
331                    .setStartDelay(delay)
332                    .setUpdateListener(ctx.updateListener)
333                    .setInterpolator(mConfig.quintOutInterpolator)
334                    .setDuration(mConfig.taskViewEnterFromHomeDuration +
335                            frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay)
336                    .withEndAction(new Runnable() {
337                        @Override
338                        public void run() {
339                            // Decrement the post animation trigger
340                            ctx.postAnimationTrigger.decrement();
341                        }
342                    })
343                    .start();
344            ctx.postAnimationTrigger.increment();
345            startDelay = delay;
346        }
347
348        // Enable the focus animations from this point onwards so that they aren't affected by the
349        // window transitions
350        postDelayed(new Runnable() {
351            @Override
352            public void run() {
353                enableFocusAnimations();
354            }
355        }, startDelay);
356    }
357
358    public void fadeInActionButton(int delay, int duration) {
359        // Hide the action button
360        mActionButtonView.setAlpha(0f);
361
362        // Animate the action button in
363        mActionButtonView.animate().alpha(1f)
364                .setStartDelay(delay)
365                .setDuration(duration)
366                .setInterpolator(PhoneStatusBar.ALPHA_IN)
367                .withLayer()
368                .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.taskViewExitToAppDuration);
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.taskViewExitToAppDuration)
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.taskViewExitToAppDuration)
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                if (mCb != null) {
481                    mCb.onTaskViewDismissed(tv);
482                }
483            }
484        });
485    }
486
487    /**
488     * Returns whether this view should be clipped, or any views below should clip against this
489     * view.
490     */
491    boolean shouldClipViewInStack() {
492        return mClipViewInStack && (getVisibility() == View.VISIBLE);
493    }
494
495    /** Sets whether this view should be clipped, or clipped against. */
496    void setClipViewInStack(boolean clip) {
497        if (clip != mClipViewInStack) {
498            mClipViewInStack = clip;
499            if (mCb != null) {
500                mCb.onTaskViewClipStateChanged(this);
501            }
502        }
503    }
504
505    /** Sets the current task progress. */
506    public void setTaskProgress(float p) {
507        mTaskProgress = p;
508        mViewBounds.setAlpha(p);
509        updateDimFromTaskProgress();
510    }
511
512    /** Returns the current task progress. */
513    public float getTaskProgress() {
514        return mTaskProgress;
515    }
516
517    /** Returns the current dim. */
518    public void setDim(int dim) {
519        mDimAlpha = dim;
520        if (mConfig.useHardwareLayers) {
521            // Defer setting hardware layers if we have not yet measured, or there is no dim to draw
522            if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
523                mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0));
524                mDimLayerPaint.setColorFilter(mDimColorFilter);
525                mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
526            }
527        } else {
528            float dimAlpha = mDimAlpha / 255.0f;
529            if (mThumbnailView != null) {
530                mThumbnailView.setDimAlpha(dimAlpha);
531            }
532            if (mHeaderView != null) {
533                mHeaderView.setDimAlpha(dim);
534            }
535        }
536    }
537
538    /** Returns the current dim. */
539    public int getDim() {
540        return mDimAlpha;
541    }
542
543    /** Animates the dim to the task progress. */
544    void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) {
545        // Animate the dim into view as well
546        int toDim = getDimFromTaskProgress();
547        if (toDim != getDim()) {
548            ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim);
549            anim.setStartDelay(delay);
550            anim.setDuration(duration);
551            if (postAnimRunnable != null) {
552                anim.addListener(postAnimRunnable);
553            }
554            anim.start();
555        }
556    }
557
558    /** Compute the dim as a function of the scale of this view. */
559    int getDimFromTaskProgress() {
560        float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress);
561        return (int) (dim * 255);
562    }
563
564    /** Update the dim as a function of the scale of this view. */
565    void updateDimFromTaskProgress() {
566        setDim(getDimFromTaskProgress());
567    }
568
569    /**** View focus state ****/
570
571    /**
572     * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
573     * if the view is not currently visible, or we are in touch state (where we still want to keep
574     * track of focus).
575     */
576    public void setFocusedTask(boolean animateFocusedState) {
577        mIsFocused = true;
578        if (mFocusAnimationsEnabled) {
579            // Focus the header bar
580            mHeaderView.onTaskViewFocusChanged(true, animateFocusedState);
581        }
582        // Update the thumbnail alpha with the focus
583        mThumbnailView.onFocusChanged(true);
584        // Call the callback
585        if (mCb != null) {
586            mCb.onTaskViewFocusChanged(this, true);
587        }
588        // Workaround, we don't always want it focusable in touch mode, but we want the first task
589        // to be focused after the enter-recents animation, which can be triggered from either touch
590        // or keyboard
591        setFocusableInTouchMode(true);
592        requestFocus();
593        setFocusableInTouchMode(false);
594        invalidate();
595    }
596
597    /**
598     * Unsets the focused task explicitly.
599     */
600    void unsetFocusedTask() {
601        mIsFocused = false;
602        if (mFocusAnimationsEnabled) {
603            // Un-focus the header bar
604            mHeaderView.onTaskViewFocusChanged(false, true);
605        }
606
607        // Update the thumbnail alpha with the focus
608        mThumbnailView.onFocusChanged(false);
609        // Call the callback
610        if (mCb != null) {
611            mCb.onTaskViewFocusChanged(this, false);
612        }
613        invalidate();
614    }
615
616    /**
617     * Updates the explicitly focused state when the view focus changes.
618     */
619    @Override
620    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
621        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
622        if (!gainFocus) {
623            unsetFocusedTask();
624        }
625    }
626
627    /**
628     * Returns whether we have explicitly been focused.
629     */
630    public boolean isFocusedTask() {
631        return mIsFocused || isFocused();
632    }
633
634    /** Enables all focus animations. */
635    void enableFocusAnimations() {
636        boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled;
637        mFocusAnimationsEnabled = true;
638        if (mIsFocused && !wasFocusAnimationsEnabled) {
639            // Re-notify the header if we were focused and animations were not previously enabled
640            mHeaderView.onTaskViewFocusChanged(true, true);
641        }
642    }
643
644    /**** TaskCallbacks Implementation ****/
645
646    /** Binds this task view to the task */
647    public void onTaskBound(Task t) {
648        mTask = t;
649        mTask.setCallbacks(this);
650
651        // Hide the action button if lock to app is disabled for this view
652        int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE;
653        if (mActionButtonView.getVisibility() != lockButtonVisibility) {
654            mActionButtonView.setVisibility(lockButtonVisibility);
655            requestLayout();
656        }
657    }
658
659    @Override
660    public void onTaskDataLoaded() {
661        if (mThumbnailView != null && mHeaderView != null) {
662            // Bind each of the views to the new task data
663            mThumbnailView.rebindToTask(mTask);
664            mHeaderView.rebindToTask(mTask);
665            // Rebind any listeners
666            mHeaderView.mApplicationIcon.setOnClickListener(this);
667            mHeaderView.mDismissButton.setOnClickListener(this);
668            mActionButtonView.setOnClickListener(this);
669            if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
670                if (mConfig.developerOptionsEnabled) {
671                    mHeaderView.mApplicationIcon.setOnLongClickListener(this);
672                }
673            }
674        }
675        mTaskDataLoaded = true;
676    }
677
678    @Override
679    public void onTaskDataUnloaded() {
680        if (mThumbnailView != null && mHeaderView != null) {
681            // Unbind each of the views from the task data and remove the task callback
682            mTask.setCallbacks(null);
683            mThumbnailView.unbindFromTask();
684            mHeaderView.unbindFromTask();
685            // Unbind any listeners
686            mHeaderView.mApplicationIcon.setOnClickListener(null);
687            mHeaderView.mDismissButton.setOnClickListener(null);
688            mActionButtonView.setOnClickListener(null);
689            if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
690                mHeaderView.mApplicationIcon.setOnLongClickListener(null);
691            }
692        }
693        mTaskDataLoaded = false;
694    }
695
696    /** Enables/disables handling touch on this task view. */
697    void setTouchEnabled(boolean enabled) {
698        setOnClickListener(enabled ? this : null);
699    }
700
701    /**** View.OnClickListener Implementation ****/
702
703    @Override
704     public void onClick(final View v) {
705        final TaskView tv = this;
706        final boolean delayViewClick = (v != this) && (v != mActionButtonView);
707        if (delayViewClick) {
708            // We purposely post the handler delayed to allow for the touch feedback to draw
709            postDelayed(new Runnable() {
710                @Override
711                public void run() {
712                    if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) {
713                        if (mCb != null) {
714                            mCb.onTaskViewAppIconClicked(tv);
715                        }
716                    } else if (v == mHeaderView.mDismissButton) {
717                        dismissTask();
718                    }
719                }
720            }, 125);
721        } else {
722            if (v == mActionButtonView) {
723                // Reset the translation of the action button before we animate it out
724                mActionButtonView.setTranslationZ(0f);
725            }
726            if (mCb != null) {
727                mCb.onTaskViewClicked(tv, tv.getTask(), (v == mActionButtonView));
728            }
729        }
730    }
731
732    /**** View.OnLongClickListener Implementation ****/
733
734    @Override
735    public boolean onLongClick(View v) {
736        if (v == mHeaderView.mApplicationIcon) {
737            if (mCb != null) {
738                mCb.onTaskViewAppInfoClicked(this);
739                return true;
740            }
741        }
742        return false;
743    }
744}
745