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