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