TaskView.java revision a0e88b5013d708ac6ed6518817d83c64c87ae4b1
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents.views;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.ValueAnimator;
23import android.content.Context;
24import android.graphics.Color;
25import android.graphics.Paint;
26import android.graphics.PorterDuff;
27import android.graphics.PorterDuffColorFilter;
28import android.graphics.Rect;
29import android.util.AttributeSet;
30import android.view.View;
31import android.view.animation.AccelerateInterpolator;
32import android.widget.FrameLayout;
33import com.android.systemui.R;
34import com.android.systemui.recents.AlternateRecentsComponent;
35import com.android.systemui.recents.Constants;
36import com.android.systemui.recents.RecentsConfiguration;
37import com.android.systemui.recents.model.RecentsTaskLoader;
38import com.android.systemui.recents.model.Task;
39
40/* A task view */
41public class TaskView extends FrameLayout implements Task.TaskCallbacks,
42        TaskViewFooter.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener {
43    /** The TaskView callbacks */
44    interface TaskViewCallbacks {
45        public void onTaskViewAppIconClicked(TaskView tv);
46        public void onTaskViewAppInfoClicked(TaskView tv);
47        public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
48        public void onTaskViewDismissed(TaskView tv);
49        public void onTaskViewClipStateChanged(TaskView tv);
50        public void onTaskViewFullScreenTransitionCompleted();
51        public void onTaskViewFocusChanged(TaskView tv, boolean focused);
52    }
53
54    RecentsConfiguration mConfig;
55
56    float mTaskProgress;
57    ObjectAnimator mTaskProgressAnimator;
58    float mMaxDimScale;
59    int mDim;
60    AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1.25f);
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        if (mConfig.launchedFromAppWithScreenshot) {
226            if (isTaskViewLaunchTargetTask) {
227                mHeaderView.prepareEnterRecentsAnimation();
228                // Hide the footer during the transition in, and animate it out afterwards?
229                if (mFooterView != null) {
230                    mFooterView.animateFooterVisibility(false, 0);
231                }
232            } else {
233                // Don't do anything for the side views when animating in
234            }
235
236        } else if (mConfig.launchedFromAppWithThumbnail) {
237            if (isTaskViewLaunchTargetTask) {
238                // Hide the front most task bar view so we can animate it in
239                mHeaderView.prepareEnterRecentsAnimation();
240                // Hide the action button if it exists
241                mActionButtonView.setAlpha(0f);
242                // Set the dim to 0 so we can animate it in
243                setDim(0);
244            } else if (occludesLaunchTarget) {
245                // Move the task view off screen (below) so we can animate it in
246                setTranslationY(offscreenY);
247            }
248
249        } else if (mConfig.launchedFromHome) {
250            // Move the task view off screen (below) so we can animate it in
251            setTranslationY(offscreenY);
252            if (Constants.DebugFlags.App.EnableShadows) {
253                setTranslationZ(0);
254            }
255            setScaleX(1f);
256            setScaleY(1f);
257        }
258    }
259
260    /** Animates this task view as it enters recents */
261    void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
262        final TaskViewTransform transform = ctx.currentTaskTransform;
263        int startDelay = 0;
264
265        if (mConfig.launchedFromAppWithScreenshot) {
266            if (mTask.isLaunchTarget) {
267                Rect taskRect = ctx.currentTaskRect;
268                int duration = mConfig.taskViewEnterFromHomeDuration * 10;
269                int windowInsetTop = mConfig.systemInsets.top; // XXX: Should be for the window
270                float taskScale = ((float) taskRect.width() / getMeasuredWidth()) * transform.scale;
271                float scaledYOffset = ((1f - taskScale) * getMeasuredHeight()) / 2;
272                float scaledWindowInsetTop = (int) (taskScale * windowInsetTop);
273                float scaledTranslationY = taskRect.top + transform.translationY -
274                        (scaledWindowInsetTop + scaledYOffset);
275                startDelay = mConfig.taskViewEnterFromHomeDelay;
276
277                // Animate the top clip
278                mViewBounds.animateClipTop(windowInsetTop, duration,
279                        new ValueAnimator.AnimatorUpdateListener() {
280                    @Override
281                    public void onAnimationUpdate(ValueAnimator animation) {
282                        int y = (Integer) animation.getAnimatedValue();
283                        mHeaderView.setTranslationY(y);
284                    }
285                });
286                // Animate the bottom or right clip
287                int size = Math.round((taskRect.width() / taskScale));
288                if (mConfig.hasHorizontalLayout()) {
289                    mViewBounds.animateClipRight(getMeasuredWidth() - size, duration);
290                } else {
291                    mViewBounds.animateClipBottom(getMeasuredHeight() - (windowInsetTop + size), duration);
292                }
293                // Animate the task bar of the first task view
294                mHeaderView.startEnterRecentsAnimation(0, null);
295                animate()
296                        .scaleX(taskScale)
297                        .scaleY(taskScale)
298                        .translationY(scaledTranslationY)
299                        .setDuration(duration)
300                        .withEndAction(new Runnable() {
301                            @Override
302                            public void run() {
303                                setIsFullScreen(false);
304                                requestLayout();
305
306                                // Reset the clip
307                                mViewBounds.setClipTop(0);
308                                mViewBounds.setClipBottom(0);
309                                mViewBounds.setClipRight(0);
310                                // Reset the bar translation
311                                mHeaderView.setTranslationY(0);
312                                // Enable the thumbnail clip
313                                mThumbnailView.enableTaskBarClip(mHeaderView);
314                                // Animate the footer into view (if it is the front most task)
315                                animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
316
317                                // Unbind the thumbnail from the screenshot
318                                RecentsTaskLoader.getInstance().loadTaskData(mTask);
319                                // Recycle the full screen screenshot
320                                AlternateRecentsComponent.consumeLastScreenshot();
321
322                                mCb.onTaskViewFullScreenTransitionCompleted();
323
324                                // Decrement the post animation trigger
325                                ctx.postAnimationTrigger.decrement();
326                            }
327                        })
328                        .start();
329            } else {
330                // Otherwise, just enable the thumbnail clip
331                mThumbnailView.enableTaskBarClip(mHeaderView);
332
333                // Animate the footer into view
334                animateFooterVisibility(true, 0);
335            }
336            ctx.postAnimationTrigger.increment();
337
338        } else if (mConfig.launchedFromAppWithThumbnail) {
339            if (mTask.isLaunchTarget) {
340                // Animate the task bar of the first task view
341                mHeaderView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay,
342                        mThumbnailView.enableTaskBarClipAsRunnable(mHeaderView));
343
344                // Animate the dim into view as well
345                ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", getDimFromTaskProgress());
346                anim.setStartDelay(mConfig.taskBarEnterAnimDelay);
347                anim.setDuration(mConfig.taskBarEnterAnimDuration);
348                anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
349                anim.addListener(new AnimatorListenerAdapter() {
350                    @Override
351                    public void onAnimationEnd(Animator animation) {
352                        // Decrement the post animation trigger
353                        ctx.postAnimationTrigger.decrement();
354                    }
355                });
356                anim.start();
357                ctx.postAnimationTrigger.increment();
358
359                // Animate the footer into view
360                animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
361
362                // Animate the action button in
363                mActionButtonView.animate().alpha(1f)
364                        .setStartDelay(mConfig.taskBarEnterAnimDelay)
365                        .setDuration(mConfig.taskBarEnterAnimDuration)
366                        .setInterpolator(mConfig.fastOutLinearInInterpolator)
367                        .withLayer()
368                        .start();
369            } else {
370                // Enable the task bar clip
371                mThumbnailView.enableTaskBarClip(mHeaderView);
372                // Animate the task up if it was occluding the launch target
373                if (ctx.currentTaskOccludesLaunchTarget) {
374                    setTranslationY(transform.translationY + mConfig.taskViewAffiliateGroupEnterOffsetPx);
375                    setAlpha(0f);
376                    animate().alpha(1f)
377                            .translationY(transform.translationY)
378                            .setStartDelay(mConfig.taskBarEnterAnimDelay)
379                            .setUpdateListener(null)
380                            .setInterpolator(mConfig.fastOutSlowInInterpolator)
381                            .setDuration(mConfig.taskViewEnterFromHomeDuration)
382                            .withEndAction(new Runnable() {
383                                @Override
384                                public void run() {
385                                    mThumbnailView.enableTaskBarClip(mHeaderView);
386                                    // Decrement the post animation trigger
387                                    ctx.postAnimationTrigger.decrement();
388                                }
389                            })
390                            .start();
391                    ctx.postAnimationTrigger.increment();
392                }
393            }
394            startDelay = mConfig.taskBarEnterAnimDelay;
395
396        } else if (mConfig.launchedFromHome) {
397            // Animate the tasks up
398            int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
399            int delay = mConfig.taskBarEnterAnimDelay +
400                    frontIndex * mConfig.taskViewEnterFromHomeDelay;
401            if (Constants.DebugFlags.App.EnableShadows) {
402                animate().translationZ(transform.translationZ);
403            }
404            animate()
405                    .scaleX(transform.scale)
406                    .scaleY(transform.scale)
407                    .translationY(transform.translationY)
408                    .setStartDelay(delay)
409                    .setUpdateListener(null)
410                    .setInterpolator(mConfig.quintOutInterpolator)
411                    .setDuration(mConfig.taskViewEnterFromHomeDuration)
412                    .withEndAction(new Runnable() {
413                        @Override
414                        public void run() {
415                            mThumbnailView.enableTaskBarClip(mHeaderView);
416                            // Decrement the post animation trigger
417                            ctx.postAnimationTrigger.decrement();
418                        }
419                    })
420                    .start();
421            ctx.postAnimationTrigger.increment();
422
423            // Animate the footer into view
424            animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration);
425            startDelay = delay;
426
427        } else {
428            // Otherwise, just enable the thumbnail clip
429            mThumbnailView.enableTaskBarClip(mHeaderView);
430
431            // Animate the footer into view
432            animateFooterVisibility(true, 0);
433        }
434
435        // Enable the focus animations from this point onwards so that they aren't affected by the
436        // window transitions
437        postDelayed(new Runnable() {
438            @Override
439            public void run() {
440                enableFocusAnimations();
441            }
442        }, (startDelay / 2));
443    }
444
445    /** Animates this task view as it leaves recents by pressing home. */
446    void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
447        animate()
448                .translationY(ctx.offscreenTranslationY)
449                .setStartDelay(0)
450                .setUpdateListener(null)
451                .setInterpolator(mConfig.fastOutLinearInInterpolator)
452                .setDuration(mConfig.taskViewExitToHomeDuration)
453                .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
454                .start();
455        ctx.postAnimationTrigger.increment();
456    }
457
458    /** Animates this task view as it exits recents */
459    void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask,
460            boolean occludesLaunchTarget) {
461        if (isLaunchingTask) {
462            // Disable the thumbnail clip and animate the bar out
463            mHeaderView.startLaunchTaskAnimation(mThumbnailView.disableTaskBarClipAsRunnable(), r);
464
465            // Animate the dim
466            if (mDim > 0) {
467                ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
468                anim.setDuration(mConfig.taskBarExitAnimDuration);
469                anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
470                anim.start();
471            }
472
473            // Animate the action button away
474            mActionButtonView.animate().alpha(0f)
475                    .setStartDelay(0)
476                    .setDuration(mConfig.taskBarExitAnimDuration)
477                    .setInterpolator(mConfig.fastOutLinearInInterpolator)
478                    .withLayer()
479                    .start();
480        } else {
481            // Hide the dismiss button
482            mHeaderView.startLaunchTaskDismissAnimation();
483            // If this is another view in the task grouping and is in front of the launch task,
484            // animate it away first
485            if (occludesLaunchTarget) {
486                animate().alpha(0f)
487                    .translationY(getTranslationY() + mConfig.taskViewAffiliateGroupEnterOffsetPx)
488                    .setStartDelay(0)
489                    .setUpdateListener(null)
490                    .setInterpolator(mConfig.fastOutLinearInInterpolator)
491                    .setDuration(mConfig.taskBarExitAnimDuration)
492                    .start();
493            }
494        }
495    }
496
497    /** Animates the deletion of this task view */
498    void startDeleteTaskAnimation(final Runnable r) {
499        // Disabling clipping with the stack while the view is animating away
500        setClipViewInStack(false);
501
502        animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)
503            .alpha(0f)
504            .setStartDelay(0)
505            .setUpdateListener(null)
506            .setInterpolator(mConfig.fastOutSlowInInterpolator)
507            .setDuration(mConfig.taskViewRemoveAnimDuration)
508            .withEndAction(new Runnable() {
509                @Override
510                public void run() {
511                    // We just throw this into a runnable because starting a view property
512                    // animation using layers can cause inconsisten results if we try and
513                    // update the layers while the animation is running.  In some cases,
514                    // the runnabled passed in may start an animation which also uses layers
515                    // so we defer all this by posting this.
516                    r.run();
517
518                    // Re-enable clipping with the stack (we will reuse this view)
519                    setClipViewInStack(true);
520                }
521            })
522            .start();
523    }
524
525    /** Animates this task view if the user does not interact with the stack after a certain time. */
526    void startNoUserInteractionAnimation() {
527        mHeaderView.startNoUserInteractionAnimation();
528    }
529
530    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
531    void setNoUserInteractionState() {
532        mHeaderView.setNoUserInteractionState();
533    }
534
535    /** Dismisses this task. */
536    void dismissTask() {
537        // Animate out the view and call the callback
538        final TaskView tv = this;
539        startDeleteTaskAnimation(new Runnable() {
540            @Override
541            public void run() {
542                mCb.onTaskViewDismissed(tv);
543            }
544        });
545        // Hide the footer
546        animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration);
547    }
548
549    /** Sets whether this task view is full screen or not. */
550    void setIsFullScreen(boolean isFullscreen) {
551        mIsFullScreenView = isFullscreen;
552        mHeaderView.setIsFullscreen(isFullscreen);
553        if (isFullscreen) {
554            // If we are full screen, then disable the bottom outline clip for the footer
555            mViewBounds.setOutlineClipBottom(0);
556        }
557    }
558
559    /** Returns whether this task view should currently be drawn as a full screen view. */
560    boolean isFullScreenView() {
561        return mIsFullScreenView;
562    }
563
564    /**
565     * Returns whether this view should be clipped, or any views below should clip against this
566     * view.
567     */
568    boolean shouldClipViewInStack() {
569        return mClipViewInStack && !mIsFullScreenView && (getVisibility() == View.VISIBLE);
570    }
571
572    /** Sets whether this view should be clipped, or clipped against. */
573    void setClipViewInStack(boolean clip) {
574        if (clip != mClipViewInStack) {
575            mClipViewInStack = clip;
576            mCb.onTaskViewClipStateChanged(this);
577        }
578    }
579
580    /** Gets the max footer height. */
581    public int getMaxFooterHeight() {
582        if (mFooterView != null) {
583            return mFooterView.mMaxFooterHeight;
584        } else {
585            return 0;
586        }
587    }
588
589    /** Animates the footer into and out of view. */
590    void animateFooterVisibility(boolean visible, int duration) {
591        // Hide the footer if we are a full screen view
592        if (mIsFullScreenView) return;
593        // Hide the footer if the current task can not be locked to
594        if (!mTask.lockToTaskEnabled || !mTask.lockToThisTask) return;
595        // Otherwise, animate the visibility
596        if (mFooterView != null) {
597            mFooterView.animateFooterVisibility(visible, duration);
598        }
599    }
600
601    /** Sets the current task progress. */
602    public void setTaskProgress(float p) {
603        mTaskProgress = p;
604        updateDimFromTaskProgress();
605    }
606
607    /** Returns the current task progress. */
608    public float getTaskProgress() {
609        return mTaskProgress;
610    }
611
612    /** Returns the current dim. */
613    public void setDim(int dim) {
614        mDim = dim;
615        int inverse = 255 - mDim;
616        mDimColorFilter.setColor(Color.argb(0xFF, inverse, inverse, inverse));
617        mLayerPaint.setColorFilter(mDimColorFilter);
618        setLayerType(LAYER_TYPE_HARDWARE, mLayerPaint);
619    }
620
621    /** Returns the current dim. */
622    public int getDim() {
623        return mDim;
624    }
625
626    /** Compute the dim as a function of the scale of this view. */
627    int getDimFromTaskProgress() {
628        float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress);
629        return (int) (dim * 255);
630    }
631
632    /** Update the dim as a function of the scale of this view. */
633    void updateDimFromTaskProgress() {
634        setDim(getDimFromTaskProgress());
635    }
636
637    /**** View focus state ****/
638
639    /**
640     * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
641     * if the view is not currently visible, or we are in touch state (where we still want to keep
642     * track of focus).
643     */
644    public void setFocusedTask() {
645        mIsFocused = true;
646        if (mFocusAnimationsEnabled) {
647            // Focus the header bar
648            mHeaderView.onTaskViewFocusChanged(true);
649        }
650        // Call the callback
651        mCb.onTaskViewFocusChanged(this, true);
652        // Workaround, we don't always want it focusable in touch mode, but we want the first task
653        // to be focused after the enter-recents animation, which can be triggered from either touch
654        // or keyboard
655        setFocusableInTouchMode(true);
656        requestFocus();
657        setFocusableInTouchMode(false);
658        invalidate();
659    }
660
661    /**
662     * Updates the explicitly focused state when the view focus changes.
663     */
664    @Override
665    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
666        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
667        if (!gainFocus) {
668            mIsFocused = false;
669            if (mFocusAnimationsEnabled) {
670                // Un-focus the header bar
671                mHeaderView.onTaskViewFocusChanged(false);
672            }
673            // Call the callback
674            mCb.onTaskViewFocusChanged(this, false);
675            invalidate();
676        }
677    }
678
679    /**
680     * Returns whether we have explicitly been focused.
681     */
682    public boolean isFocusedTask() {
683        return mIsFocused || isFocused();
684    }
685
686    /** Enables all focus animations. */
687    void enableFocusAnimations() {
688        boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled;
689        mFocusAnimationsEnabled = true;
690        if (mIsFocused && !wasFocusAnimationsEnabled) {
691            // Re-notify the header if we were focused and animations were not previously enabled
692            mHeaderView.onTaskViewFocusChanged(true);
693        }
694    }
695
696    /**** TaskCallbacks Implementation ****/
697
698    /** Binds this task view to the task */
699    public void onTaskBound(Task t) {
700        mTask = t;
701        mTask.setCallbacks(this);
702        if (getMeasuredWidth() == 0) {
703            // If we haven't yet measured, we should just set the footer height with any animation
704            animateFooterVisibility(t.lockToThisTask, 0);
705        } else {
706            animateFooterVisibility(t.lockToThisTask, mConfig.taskViewLockToAppLongAnimDuration);
707        }
708        // Hide the action button if lock to app is disabled
709        if (!t.lockToTaskEnabled && mActionButtonView.getVisibility() != View.GONE) {
710            mActionButtonView.setVisibility(View.GONE);
711        }
712    }
713
714    @Override
715    public void onTaskDataLoaded() {
716        if (mThumbnailView != null && mHeaderView != null) {
717            // Bind each of the views to the new task data
718            if (mIsFullScreenView) {
719                mThumbnailView.bindToScreenshot(AlternateRecentsComponent.getLastScreenshot());
720            } else {
721                mThumbnailView.rebindToTask(mTask);
722            }
723            mHeaderView.rebindToTask(mTask);
724            // Rebind any listeners
725            mHeaderView.mApplicationIcon.setOnClickListener(this);
726            mHeaderView.mDismissButton.setOnClickListener(this);
727            if (mFooterView != null) {
728                mFooterView.setOnClickListener(this);
729            }
730            mActionButtonView.setOnClickListener(this);
731            if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
732                if (mConfig.developerOptionsEnabled) {
733                    mHeaderView.mApplicationIcon.setOnLongClickListener(this);
734                }
735            }
736        }
737        mTaskDataLoaded = true;
738    }
739
740    @Override
741    public void onTaskDataUnloaded() {
742        if (mThumbnailView != null && mHeaderView != null) {
743            // Unbind each of the views from the task data and remove the task callback
744            mTask.setCallbacks(null);
745            mThumbnailView.unbindFromTask();
746            mHeaderView.unbindFromTask();
747            // Unbind any listeners
748            mHeaderView.mApplicationIcon.setOnClickListener(null);
749            mHeaderView.mDismissButton.setOnClickListener(null);
750            if (mFooterView != null) {
751                mFooterView.setOnClickListener(null);
752            }
753            mActionButtonView.setOnClickListener(null);
754            if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
755                mHeaderView.mApplicationIcon.setOnLongClickListener(null);
756            }
757        }
758        mTaskDataLoaded = false;
759    }
760
761    /** Enables/disables handling touch on this task view. */
762    void setTouchEnabled(boolean enabled) {
763        setOnClickListener(enabled ? this : null);
764    }
765
766    /**** TaskViewFooter.TaskFooterViewCallbacks ****/
767
768    @Override
769    public void onTaskFooterHeightChanged(int height, int maxHeight) {
770        if (mIsFullScreenView) {
771            // Disable the bottom outline clip when fullscreen
772            mViewBounds.setOutlineClipBottom(0);
773        } else {
774            // Update the bottom clip in our outline provider
775            mViewBounds.setOutlineClipBottom(maxHeight - height);
776        }
777    }
778
779    /**** View.OnClickListener Implementation ****/
780
781    @Override
782     public void onClick(final View v) {
783        // We purposely post the handler delayed to allow for the touch feedback to draw
784        final TaskView tv = this;
785        postDelayed(new Runnable() {
786            @Override
787            public void run() {
788                if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) {
789                    mCb.onTaskViewAppIconClicked(tv);
790                } else if (v == mHeaderView.mDismissButton) {
791                    dismissTask();
792                } else {
793                    mCb.onTaskViewClicked(tv, tv.getTask(),
794                            (v == mFooterView || v == mActionButtonView));
795                }
796            }
797        }, 125);
798    }
799
800    /**** View.OnLongClickListener Implementation ****/
801
802    @Override
803    public boolean onLongClick(View v) {
804        if (v == mHeaderView.mApplicationIcon) {
805            mCb.onTaskViewAppInfoClicked(this);
806            return true;
807        }
808        return false;
809    }
810}
811