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