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