TaskView.java revision 2536c7ed446203ea12b38cf05a88e603f8d1b768
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.content.res.Resources;
24import android.graphics.Color;
25import android.graphics.Outline;
26import android.graphics.Paint;
27import android.graphics.PorterDuff;
28import android.graphics.PorterDuffColorFilter;
29import android.graphics.Rect;
30import android.util.AttributeSet;
31import android.view.View;
32import android.view.ViewOutlineProvider;
33import android.view.animation.AccelerateInterpolator;
34import android.view.animation.AnimationUtils;
35import android.view.animation.Interpolator;
36import android.widget.FrameLayout;
37import com.android.systemui.R;
38import com.android.systemui.recents.Constants;
39import com.android.systemui.recents.RecentsActivityLaunchState;
40import com.android.systemui.recents.RecentsConfiguration;
41import com.android.systemui.recents.events.EventBus;
42import com.android.systemui.recents.events.ui.DismissTaskEvent;
43import com.android.systemui.recents.misc.Utilities;
44import com.android.systemui.recents.model.Task;
45import com.android.systemui.statusbar.phone.PhoneStatusBar;
46
47/* A task view */
48public class TaskView extends FrameLayout implements Task.TaskCallbacks,
49        View.OnClickListener {
50
51    /** The TaskView callbacks */
52    interface TaskViewCallbacks {
53        public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
54        public void onTaskViewClipStateChanged(TaskView tv);
55        public void onTaskViewFocusChanged(TaskView tv, boolean focused);
56    }
57
58    RecentsConfiguration mConfig;
59
60    float mTaskProgress;
61    ObjectAnimator mTaskProgressAnimator;
62    float mMaxDimScale;
63    int mDimAlpha;
64    AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1f);
65    PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
66    Paint mDimLayerPaint = new Paint();
67    float mActionButtonTranslationZ;
68
69    Task mTask;
70    boolean mTaskDataLoaded;
71    boolean mIsFocused;
72    boolean mFocusAnimationsEnabled;
73    boolean mClipViewInStack;
74    AnimateableViewBounds mViewBounds;
75
76    View mContent;
77    TaskViewThumbnail mThumbnailView;
78    TaskViewHeader mHeaderView;
79    View mActionButtonView;
80    TaskViewCallbacks mCb;
81
82    Interpolator mFastOutSlowInInterpolator;
83    Interpolator mFastOutLinearInInterpolator;
84    Interpolator mQuintOutInterpolator;
85
86    // Optimizations
87    ValueAnimator.AnimatorUpdateListener mUpdateDimListener =
88            new ValueAnimator.AnimatorUpdateListener() {
89                @Override
90                public void onAnimationUpdate(ValueAnimator animation) {
91                    setTaskProgress((Float) animation.getAnimatedValue());
92                }
93            };
94
95
96    public TaskView(Context context) {
97        this(context, null);
98    }
99
100    public TaskView(Context context, AttributeSet attrs) {
101        this(context, attrs, 0);
102    }
103
104    public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
105        this(context, attrs, defStyleAttr, 0);
106    }
107
108    public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
109        super(context, attrs, defStyleAttr, defStyleRes);
110        Resources res = context.getResources();
111        mConfig = RecentsConfiguration.getInstance();
112        mMaxDimScale = res.getInteger(R.integer.recents_max_task_stack_view_dim) / 255f;
113        mClipViewInStack = true;
114        mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize(
115                R.dimen.recents_task_view_rounded_corners_radius));
116        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
117                com.android.internal.R.interpolator.fast_out_slow_in);
118        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
119                com.android.internal.R.interpolator.fast_out_linear_in);
120        mQuintOutInterpolator = AnimationUtils.loadInterpolator(context,
121                com.android.internal.R.interpolator.decelerate_quint);
122        setTaskProgress(getTaskProgress());
123        setDim(getDim());
124        if (mConfig.fakeShadows) {
125            setBackground(new FakeShadowDrawable(res, mConfig));
126        }
127        setOutlineProvider(mViewBounds);
128    }
129
130    /** Set callback */
131    void setCallbacks(TaskViewCallbacks cb) {
132        mCb = cb;
133    }
134
135    /** Resets this TaskView for reuse. */
136    void reset() {
137        resetViewProperties();
138        resetNoUserInteractionState();
139        setClipViewInStack(false);
140        setCallbacks(null);
141    }
142
143    /** Gets the task */
144    Task getTask() {
145        return mTask;
146    }
147
148    /** Returns the view bounds. */
149    AnimateableViewBounds getViewBounds() {
150        return mViewBounds;
151    }
152
153    @Override
154    protected void onFinishInflate() {
155        // Bind the views
156        mContent = findViewById(R.id.task_view_content);
157        mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar);
158        mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail);
159        mThumbnailView.updateClipToTaskBar(mHeaderView);
160        mActionButtonView = findViewById(R.id.lock_to_app_fab);
161        mActionButtonView.setOutlineProvider(new ViewOutlineProvider() {
162            @Override
163            public void getOutline(View view, Outline outline) {
164                // Set the outline to match the FAB background
165                outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight());
166            }
167        });
168        mActionButtonTranslationZ = mActionButtonView.getTranslationZ();
169    }
170
171    @Override
172    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
173        int width = MeasureSpec.getSize(widthMeasureSpec);
174        int height = MeasureSpec.getSize(heightMeasureSpec);
175
176        int widthWithoutPadding = width - mPaddingLeft - mPaddingRight;
177        int heightWithoutPadding = height - mPaddingTop - mPaddingBottom;
178        int taskBarHeight = getResources().getDimensionPixelSize(R.dimen.recents_task_bar_height);
179
180        // Measure the content
181        mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
182                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
183
184        // Measure the bar view, and action button
185        mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
186                MeasureSpec.makeMeasureSpec(taskBarHeight, MeasureSpec.EXACTLY));
187        mActionButtonView.measure(
188                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST),
189                MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST));
190        // Measure the thumbnail to be square
191        mThumbnailView.measure(
192                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
193                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
194        setMeasuredDimension(width, height);
195        invalidateOutline();
196    }
197
198    /** Synchronizes this view's properties with the task's transform */
199    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
200        updateViewPropertiesToTaskTransform(toTransform, duration, null);
201    }
202
203    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration,
204                                             ValueAnimator.AnimatorUpdateListener updateCallback) {
205        // Apply the transform
206        toTransform.applyToTaskView(this, duration, mFastOutSlowInInterpolator, false,
207                !mConfig.fakeShadows, updateCallback);
208
209        // Update the task progress
210        Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator);
211        if (duration <= 0) {
212            setTaskProgress(toTransform.p);
213        } else {
214            mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p);
215            mTaskProgressAnimator.setDuration(duration);
216            mTaskProgressAnimator.addUpdateListener(mUpdateDimListener);
217            mTaskProgressAnimator.start();
218        }
219    }
220
221    /** Resets this view's properties */
222    void resetViewProperties() {
223        setDim(0);
224        setLayerType(View.LAYER_TYPE_NONE, null);
225        TaskViewTransform.reset(this);
226        if (mActionButtonView != null) {
227            mActionButtonView.setScaleX(1f);
228            mActionButtonView.setScaleY(1f);
229            mActionButtonView.setAlpha(1f);
230            mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
231        }
232    }
233
234    /**
235     * When we are un/filtering, this method will set up the transform that we are animating to,
236     * in order to hide the task.
237     */
238    void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
239        // Fade the view out and slide it away
240        toTransform.alpha = 0f;
241        toTransform.translationY += 200;
242        toTransform.translationZ = 0;
243    }
244
245    /**
246     * When we are un/filtering, this method will setup the transform that we are animating from,
247     * in order to show the task.
248     */
249    void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) {
250        // Fade the view in
251        fromTransform.alpha = 0f;
252    }
253
254    /** Prepares this task view for the enter-recents animations.  This is called earlier in the
255     * first layout because the actual animation into recents may take a long time. */
256    void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
257                                             boolean occludesLaunchTarget, int offscreenY) {
258        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
259        int initialDim = getDim();
260        if (launchState.launchedHasConfigurationChanged) {
261            // Just load the views as-is
262        } else if (launchState.launchedFromAppWithThumbnail) {
263            if (isTaskViewLaunchTargetTask) {
264                // Set the dim to 0 so we can animate it in
265                initialDim = 0;
266                // Hide the action button
267                mActionButtonView.setAlpha(0f);
268            } else if (occludesLaunchTarget) {
269                // Move the task view off screen (below) so we can animate it in
270                setTranslationY(offscreenY);
271            }
272
273        } else if (launchState.launchedFromHome) {
274            // Move the task view off screen (below) so we can animate it in
275            setTranslationY(offscreenY);
276            setTranslationZ(0);
277            setScaleX(1f);
278            setScaleY(1f);
279        }
280        // Apply the current dim
281        setDim(initialDim);
282        // Prepare the thumbnail view alpha
283        mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask);
284    }
285
286    /** Animates this task view as it enters recents */
287    void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
288        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
289        Resources res = mContext.getResources();
290        final TaskViewTransform transform = ctx.currentTaskTransform;
291        final int transitionEnterFromAppDelay = res.getInteger(
292                R.integer.recents_enter_from_app_transition_duration);
293        final int transitionEnterFromHomeDelay = res.getInteger(
294                R.integer.recents_enter_from_home_transition_duration);
295        final int taskViewEnterFromAppDuration = res.getInteger(
296                R.integer.recents_task_enter_from_app_duration);
297        final int taskViewEnterFromHomeDuration = res.getInteger(
298                R.integer.recents_task_enter_from_home_duration);
299        final int taskViewEnterFromHomeStaggerDelay = res.getInteger(
300                R.integer.recents_task_enter_from_home_stagger_delay);
301        final int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
302                R.dimen.recents_task_view_affiliate_group_enter_offset);
303        int startDelay = 0;
304
305        if (launchState.launchedFromAppWithThumbnail) {
306            if (mTask.isLaunchTarget) {
307                // Animate the dim/overlay
308                if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) {
309                    // Animate the thumbnail alpha before the dim animation (to prevent updating the
310                    // hardware layer)
311                    mThumbnailView.startEnterRecentsAnimation(transitionEnterFromAppDelay,
312                            new Runnable() {
313                                @Override
314                                public void run() {
315                                    animateDimToProgress(0, taskViewEnterFromAppDuration,
316                                            ctx.postAnimationTrigger.decrementOnAnimationEnd());
317                                }
318                            });
319                } else {
320                    // Immediately start the dim animation
321                    animateDimToProgress(transitionEnterFromAppDelay,
322                            taskViewEnterFromAppDuration,
323                            ctx.postAnimationTrigger.decrementOnAnimationEnd());
324                }
325                ctx.postAnimationTrigger.increment();
326
327                // Animate the action button in
328                fadeInActionButton(transitionEnterFromAppDelay,
329                        taskViewEnterFromAppDuration);
330            } else {
331                // Animate the task up if it was occluding the launch target
332                if (ctx.currentTaskOccludesLaunchTarget) {
333                    setTranslationY(transform.translationY + taskViewAffiliateGroupEnterOffset);
334                    setAlpha(0f);
335                    animate().alpha(1f)
336                            .translationY(transform.translationY)
337                            .setStartDelay(transitionEnterFromAppDelay)
338                            .setUpdateListener(null)
339                            .setInterpolator(mFastOutSlowInInterpolator)
340                            .setDuration(taskViewEnterFromHomeDuration)
341                            .withEndAction(new Runnable() {
342                                @Override
343                                public void run() {
344                                    // Decrement the post animation trigger
345                                    ctx.postAnimationTrigger.decrement();
346                                }
347                            })
348                            .start();
349                    ctx.postAnimationTrigger.increment();
350                }
351            }
352            startDelay = transitionEnterFromAppDelay;
353
354        } else if (launchState.launchedFromHome) {
355            // Animate the tasks up
356            int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
357            int delay = transitionEnterFromHomeDelay +
358                    frontIndex * taskViewEnterFromHomeStaggerDelay;
359
360            setScaleX(transform.scale);
361            setScaleY(transform.scale);
362            if (!mConfig.fakeShadows) {
363                animate().translationZ(transform.translationZ);
364            }
365            animate()
366                    .translationY(transform.translationY)
367                    .setStartDelay(delay)
368                    .setUpdateListener(ctx.updateListener)
369                    .setInterpolator(mQuintOutInterpolator)
370                    .setDuration(taskViewEnterFromHomeDuration +
371                            frontIndex * taskViewEnterFromHomeStaggerDelay)
372                    .withEndAction(new Runnable() {
373                        @Override
374                        public void run() {
375                            // Decrement the post animation trigger
376                            ctx.postAnimationTrigger.decrement();
377                        }
378                    })
379                    .start();
380            ctx.postAnimationTrigger.increment();
381            startDelay = delay;
382        }
383
384        // Enable the focus animations from this point onwards so that they aren't affected by the
385        // window transitions
386        postDelayed(new Runnable() {
387            @Override
388            public void run() {
389                enableFocusAnimations();
390            }
391        }, startDelay);
392    }
393
394    public void fadeInActionButton(int delay, int duration) {
395        // Hide the action button
396        mActionButtonView.setAlpha(0f);
397
398        // Animate the action button in
399        mActionButtonView.animate().alpha(1f)
400                .setStartDelay(delay)
401                .setDuration(duration)
402                .setInterpolator(PhoneStatusBar.ALPHA_IN)
403                .start();
404    }
405
406    /** Animates this task view as it leaves recents by pressing home. */
407    void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
408        int taskViewExitToHomeDuration = getResources().getInteger(
409                R.integer.recents_task_exit_to_home_duration);
410        animate()
411                .translationY(ctx.offscreenTranslationY)
412                .setStartDelay(0)
413                .setUpdateListener(null)
414                .setInterpolator(mFastOutLinearInInterpolator)
415                .setDuration(taskViewExitToHomeDuration)
416                .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
417                .start();
418        ctx.postAnimationTrigger.increment();
419    }
420
421    /** Animates this task view away when dismissing all tasks. */
422    void startDismissAllAnimation() {
423        dismissTask();
424    }
425
426    /** Animates this task view as it exits recents */
427    void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask,
428            boolean occludesLaunchTarget, boolean lockToTask) {
429        final int taskViewExitToAppDuration = mContext.getResources().getInteger(
430                R.integer.recents_task_exit_to_app_duration);
431        final int taskViewAffiliateGroupEnterOffset = mContext.getResources().getDimensionPixelSize(
432                R.dimen.recents_task_view_affiliate_group_enter_offset);
433
434        if (isLaunchingTask) {
435            // Animate the thumbnail alpha back into full opacity for the window animation out
436            mThumbnailView.startLaunchTaskAnimation(postAnimRunnable);
437
438            // Animate the dim
439            if (mDimAlpha > 0) {
440                ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
441                anim.setDuration(taskViewExitToAppDuration);
442                anim.setInterpolator(mFastOutLinearInInterpolator);
443                anim.start();
444            }
445
446            // Animate the action button away
447            if (!lockToTask) {
448                float toScale = 0.9f;
449                mActionButtonView.animate()
450                        .scaleX(toScale)
451                        .scaleY(toScale);
452            }
453            mActionButtonView.animate()
454                    .alpha(0f)
455                    .setStartDelay(0)
456                    .setDuration(taskViewExitToAppDuration)
457                    .setInterpolator(mFastOutLinearInInterpolator)
458                    .start();
459        } else {
460            // Hide the dismiss button
461            mHeaderView.startLaunchTaskDismissAnimation();
462            // If this is another view in the task grouping and is in front of the launch task,
463            // animate it away first
464            if (occludesLaunchTarget) {
465                animate().alpha(0f)
466                    .translationY(getTranslationY() + taskViewAffiliateGroupEnterOffset)
467                    .setStartDelay(0)
468                    .setUpdateListener(null)
469                    .setInterpolator(mFastOutLinearInInterpolator)
470                    .setDuration(taskViewExitToAppDuration)
471                    .start();
472            }
473        }
474    }
475
476    /** Animates the deletion of this task view */
477    void startDeleteTaskAnimation(final Runnable r, int delay) {
478        int taskViewRemoveAnimDuration = getResources().getInteger(
479                R.integer.recents_animate_task_view_remove_duration);
480        int taskViewRemoveAnimTranslationXPx = getResources().getDimensionPixelSize(
481                R.dimen.recents_task_view_remove_anim_translation_x);
482
483        // Disabling clipping with the stack while the view is animating away
484        setClipViewInStack(false);
485
486        animate().translationX(taskViewRemoveAnimTranslationXPx)
487            .alpha(0f)
488            .setStartDelay(delay)
489            .setUpdateListener(null)
490            .setInterpolator(mFastOutSlowInInterpolator)
491            .setDuration(taskViewRemoveAnimDuration)
492            .withEndAction(new Runnable() {
493                @Override
494                public void run() {
495                    if (r != null) {
496                        r.run();
497                    }
498
499                    // Re-enable clipping with the stack (we will reuse this view)
500                    setClipViewInStack(true);
501                }
502            })
503            .start();
504    }
505
506    /** Enables/disables handling touch on this task view. */
507    void setTouchEnabled(boolean enabled) {
508        setOnClickListener(enabled ? this : null);
509    }
510
511    /** Animates this task view if the user does not interact with the stack after a certain time. */
512    void startNoUserInteractionAnimation() {
513        mHeaderView.startNoUserInteractionAnimation();
514    }
515
516    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
517    void setNoUserInteractionState() {
518        mHeaderView.setNoUserInteractionState();
519    }
520
521    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
522    void resetNoUserInteractionState() {
523        mHeaderView.resetNoUserInteractionState();
524    }
525
526    /** Dismisses this task. */
527    void dismissTask() {
528        // Animate out the view and call the callback
529        final TaskView tv = this;
530        startDeleteTaskAnimation(new Runnable() {
531            @Override
532            public void run() {
533                EventBus.getDefault().send(new DismissTaskEvent(mTask, tv));
534            }
535        }, 0);
536    }
537
538    /**
539     * Returns whether this view should be clipped, or any views below should clip against this
540     * view.
541     */
542    boolean shouldClipViewInStack() {
543        return mClipViewInStack && (getVisibility() == View.VISIBLE);
544    }
545
546    /** Sets whether this view should be clipped, or clipped against. */
547    void setClipViewInStack(boolean clip) {
548        if (clip != mClipViewInStack) {
549            mClipViewInStack = clip;
550            if (mCb != null) {
551                mCb.onTaskViewClipStateChanged(this);
552            }
553        }
554    }
555
556    /** Sets the current task progress. */
557    public void setTaskProgress(float p) {
558        mTaskProgress = p;
559        mViewBounds.setAlpha(p);
560        updateDimFromTaskProgress();
561    }
562
563    /** Returns the current task progress. */
564    public float getTaskProgress() {
565        return mTaskProgress;
566    }
567
568    /** Returns the current dim. */
569    public void setDim(int dim) {
570        mDimAlpha = dim;
571        if (mConfig.useHardwareLayers) {
572            // Defer setting hardware layers if we have not yet measured, or there is no dim to draw
573            if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
574                mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0));
575                mDimLayerPaint.setColorFilter(mDimColorFilter);
576                mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
577            }
578        } else {
579            float dimAlpha = mDimAlpha / 255.0f;
580            if (mThumbnailView != null) {
581                mThumbnailView.setDimAlpha(dimAlpha);
582            }
583            if (mHeaderView != null) {
584                mHeaderView.setDimAlpha(dim);
585            }
586        }
587    }
588
589    /** Returns the current dim. */
590    public int getDim() {
591        return mDimAlpha;
592    }
593
594    /** Animates the dim to the task progress. */
595    void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) {
596        // Animate the dim into view as well
597        int toDim = getDimFromTaskProgress();
598        if (toDim != getDim()) {
599            ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim);
600            anim.setStartDelay(delay);
601            anim.setDuration(duration);
602            if (postAnimRunnable != null) {
603                anim.addListener(postAnimRunnable);
604            }
605            anim.start();
606        }
607    }
608
609    /** Compute the dim as a function of the scale of this view. */
610    int getDimFromTaskProgress() {
611        float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress);
612        return (int) (dim * 255);
613    }
614
615    /** Update the dim as a function of the scale of this view. */
616    void updateDimFromTaskProgress() {
617        setDim(getDimFromTaskProgress());
618    }
619
620    /**** View focus state ****/
621
622    /**
623     * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
624     * if the view is not currently visible, or we are in touch state (where we still want to keep
625     * track of focus).
626     */
627    public void setFocusedTask(boolean animateFocusedState) {
628        mIsFocused = true;
629        if (mFocusAnimationsEnabled) {
630            // Focus the header bar
631            mHeaderView.onTaskViewFocusChanged(true, animateFocusedState);
632        }
633        // Update the thumbnail alpha with the focus
634        mThumbnailView.onFocusChanged(true);
635        // Call the callback
636        if (mCb != null) {
637            mCb.onTaskViewFocusChanged(this, true);
638        }
639        // Workaround, we don't always want it focusable in touch mode, but we want the first task
640        // to be focused after the enter-recents animation, which can be triggered from either touch
641        // or keyboard
642        setFocusableInTouchMode(true);
643        requestFocus();
644        setFocusableInTouchMode(false);
645        invalidate();
646    }
647
648    /**
649     * Unsets the focused task explicitly.
650     */
651    void unsetFocusedTask() {
652        mIsFocused = false;
653        if (mFocusAnimationsEnabled) {
654            // Un-focus the header bar
655            mHeaderView.onTaskViewFocusChanged(false, true);
656        }
657
658        // Update the thumbnail alpha with the focus
659        mThumbnailView.onFocusChanged(false);
660        // Call the callback
661        if (mCb != null) {
662            mCb.onTaskViewFocusChanged(this, false);
663        }
664        invalidate();
665    }
666
667    /**
668     * Updates the explicitly focused state when the view focus changes.
669     */
670    @Override
671    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
672        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
673        if (!gainFocus) {
674            unsetFocusedTask();
675        }
676    }
677
678    /**
679     * Returns whether we have explicitly been focused.
680     */
681    public boolean isFocusedTask() {
682        return mIsFocused || isFocused();
683    }
684
685    /** Enables all focus animations. */
686    void enableFocusAnimations() {
687        boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled;
688        mFocusAnimationsEnabled = true;
689        if (mIsFocused && !wasFocusAnimationsEnabled) {
690            // Re-notify the header if we were focused and animations were not previously enabled
691            mHeaderView.onTaskViewFocusChanged(true, true);
692        }
693    }
694
695    public void disableLayersForOneFrame() {
696        mHeaderView.disableLayersForOneFrame();
697    }
698
699    /**** TaskCallbacks Implementation ****/
700
701    /** Binds this task view to the task */
702    public void onTaskBound(Task t) {
703        mTask = t;
704        mTask.setCallbacks(this);
705
706        // Hide the action button if lock to app is disabled for this view
707        int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE;
708        if (mActionButtonView.getVisibility() != lockButtonVisibility) {
709            mActionButtonView.setVisibility(lockButtonVisibility);
710            requestLayout();
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            mThumbnailView.rebindToTask(mTask);
719            mHeaderView.rebindToTask(mTask);
720            // Rebind any listeners
721            mActionButtonView.setOnClickListener(this);
722        }
723        mTaskDataLoaded = true;
724    }
725
726    @Override
727    public void onTaskDataUnloaded() {
728        if (mThumbnailView != null && mHeaderView != null) {
729            // Unbind each of the views from the task data and remove the task callback
730            mTask.setCallbacks(null);
731            mThumbnailView.unbindFromTask();
732            mHeaderView.unbindFromTask();
733            // Unbind any listeners
734            mActionButtonView.setOnClickListener(null);
735        }
736        mTaskDataLoaded = false;
737    }
738
739    @Override
740    public void onMultiStackDebugTaskStackIdChanged() {
741        mHeaderView.rebindToTask(mTask);
742    }
743
744    /**** View.OnClickListener Implementation ****/
745
746    @Override
747     public void onClick(final View v) {
748        if (v == mActionButtonView) {
749            // Reset the translation of the action button before we animate it out
750            mActionButtonView.setTranslationZ(0f);
751        }
752        if (mCb != null) {
753            mCb.onTaskViewClicked(this, mTask, (v == mActionButtonView));
754        }
755    }
756}
757