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