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