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