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