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