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