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