TaskView.java revision e5f1faa9f8009a723ab21aed6fe5ab325c61442b
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.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.ValueAnimator;
23import android.content.Context;
24import android.content.res.Resources;
25import android.graphics.Bitmap;
26import android.graphics.Canvas;
27import android.graphics.Color;
28import android.graphics.Outline;
29import android.graphics.Paint;
30import android.graphics.Point;
31import android.graphics.PorterDuff;
32import android.graphics.PorterDuffColorFilter;
33import android.util.AttributeSet;
34import android.util.Log;
35import android.view.MotionEvent;
36import android.view.View;
37import android.view.ViewOutlineProvider;
38import android.view.animation.AccelerateInterpolator;
39import android.view.animation.AnimationUtils;
40import android.view.animation.Interpolator;
41import android.widget.FrameLayout;
42import com.android.systemui.R;
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 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(heightWithoutPadding, 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(heightWithoutPadding, 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 taskViewEnterFromAppDuration = res.getInteger(
316                R.integer.recents_task_enter_from_app_duration);
317        final int taskViewEnterFromHomeDuration = res.getInteger(
318                R.integer.recents_task_enter_from_home_duration);
319        final int taskViewEnterFromHomeStaggerDelay = res.getInteger(
320                R.integer.recents_task_enter_from_home_stagger_delay);
321        final int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
322                R.dimen.recents_task_view_affiliate_group_enter_offset);
323
324        if (launchState.launchedFromAppWithThumbnail) {
325            if (mTask.isLaunchTarget) {
326                // Immediately start the dim animation
327                animateDimToProgress(taskViewEnterFromAppDuration,
328                        ctx.postAnimationTrigger.decrementOnAnimationEnd());
329                ctx.postAnimationTrigger.increment();
330
331                // Animate the action button in
332                fadeInActionButton(taskViewEnterFromAppDuration);
333            } else {
334                // Animate the task up if it was occluding the launch target
335                if (ctx.currentTaskOccludesLaunchTarget) {
336                    setTranslationY(transform.translationY + taskViewAffiliateGroupEnterOffset);
337                    setAlpha(0f);
338                    animate().alpha(1f)
339                            .translationY(transform.translationY)
340                            .setUpdateListener(null)
341                            .setListener(new AnimatorListenerAdapter() {
342                                private boolean hasEnded;
343
344                                // We use the animation listener instead of withEndAction() to
345                                // ensure that onAnimationEnd() is called when the animator is
346                                // cancelled
347                                @Override
348                                public void onAnimationEnd(Animator animation) {
349                                    if (hasEnded) return;
350                                    ctx.postAnimationTrigger.decrement();
351                                    hasEnded = true;
352                                }
353                            })
354                            .setInterpolator(mFastOutSlowInInterpolator)
355                            .setDuration(taskViewEnterFromHomeDuration)
356                            .start();
357                    ctx.postAnimationTrigger.increment();
358                }
359            }
360
361        } else if (launchState.launchedFromHome) {
362            // Animate the tasks up
363            int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
364            int delay = frontIndex * taskViewEnterFromHomeStaggerDelay;
365
366            setScaleX(transform.scale);
367            setScaleY(transform.scale);
368            if (!config.fakeShadows) {
369                animate().translationZ(transform.translationZ);
370            }
371            animate()
372                    .translationY(transform.translationY)
373                    .setStartDelay(delay)
374                    .setUpdateListener(ctx.updateListener)
375                    .setListener(new AnimatorListenerAdapter() {
376                        private boolean hasEnded;
377
378                        // We use the animation listener instead of withEndAction() to ensure that
379                        // onAnimationEnd() is called when the animator is cancelled
380                        @Override
381                        public void onAnimationEnd(Animator animation) {
382                            if (hasEnded) return;
383                            ctx.postAnimationTrigger.decrement();
384                            hasEnded = true;
385                        }
386                    })
387                    .setInterpolator(mQuintOutInterpolator)
388                    .setDuration(taskViewEnterFromHomeDuration +
389                            frontIndex * taskViewEnterFromHomeStaggerDelay)
390                    .start();
391            ctx.postAnimationTrigger.increment();
392        }
393    }
394
395    public void cancelEnterRecentsAnimation() {
396        animate().cancel();
397    }
398
399    public void fadeInActionButton(int duration) {
400        // Hide the action button
401        mActionButtonView.setAlpha(0f);
402
403        // Animate the action button in
404        mActionButtonView.animate().alpha(1f)
405                .setDuration(duration)
406                .setInterpolator(PhoneStatusBar.ALPHA_IN)
407                .start();
408    }
409
410    /** Animates this task view as it leaves recents by pressing home. */
411    void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
412        int taskViewExitToHomeDuration = getResources().getInteger(
413                R.integer.recents_task_exit_to_home_duration);
414        animate()
415                .translationY(ctx.offscreenTranslationY)
416                .setStartDelay(0)
417                .setUpdateListener(null)
418                .setInterpolator(mFastOutLinearInInterpolator)
419                .setDuration(taskViewExitToHomeDuration)
420                .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
421                .start();
422        ctx.postAnimationTrigger.increment();
423    }
424
425    /** Animates this task view as it exits recents */
426    void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask,
427            boolean occludesLaunchTarget, boolean lockToTask) {
428        final int taskViewExitToAppDuration = mContext.getResources().getInteger(
429                R.integer.recents_task_exit_to_app_duration);
430        final int taskViewAffiliateGroupEnterOffset = mContext.getResources().getDimensionPixelSize(
431                R.dimen.recents_task_view_affiliate_group_enter_offset);
432
433        if (isLaunchingTask) {
434            // Animate the thumbnail alpha back into full opacity for the window animation out
435            mThumbnailView.startLaunchTaskAnimation(postAnimRunnable);
436
437            // Animate the dim
438            if (mDimAlpha > 0) {
439                ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
440                anim.setDuration(taskViewExitToAppDuration);
441                anim.setInterpolator(mFastOutLinearInInterpolator);
442                anim.start();
443            }
444
445            // Animate the action button away
446            if (!lockToTask) {
447                float toScale = 0.9f;
448                mActionButtonView.animate()
449                        .scaleX(toScale)
450                        .scaleY(toScale);
451            }
452            mActionButtonView.animate()
453                    .alpha(0f)
454                    .setStartDelay(0)
455                    .setDuration(taskViewExitToAppDuration)
456                    .setInterpolator(mFastOutLinearInInterpolator)
457                    .start();
458        } else {
459            // Hide the dismiss button
460            mHeaderView.startLaunchTaskDismissAnimation();
461            // If this is another view in the task grouping and is in front of the launch task,
462            // animate it away first
463            if (occludesLaunchTarget) {
464                animate().alpha(0f)
465                    .translationY(getTranslationY() + taskViewAffiliateGroupEnterOffset)
466                    .setStartDelay(0)
467                    .setUpdateListener(null)
468                    .setInterpolator(mFastOutLinearInInterpolator)
469                    .setDuration(taskViewExitToAppDuration)
470                    .start();
471            }
472        }
473    }
474
475    /** Animates the deletion of this task view */
476    void startDeleteTaskAnimation(final Runnable r, int delay) {
477        int taskViewRemoveAnimDuration = getResources().getInteger(
478                R.integer.recents_animate_task_view_remove_duration);
479        int taskViewRemoveAnimTranslationXPx = getResources().getDimensionPixelSize(
480                R.dimen.recents_task_view_remove_anim_translation_x);
481
482        // Disabling clipping with the stack while the view is animating away
483        setClipViewInStack(false);
484
485        animate().translationX(taskViewRemoveAnimTranslationXPx)
486            .alpha(0f)
487            .setStartDelay(delay)
488            .setUpdateListener(null)
489            .setInterpolator(mFastOutSlowInInterpolator)
490            .setDuration(taskViewRemoveAnimDuration)
491            .withEndAction(new Runnable() {
492                @Override
493                public void run() {
494                    if (r != null) {
495                        r.run();
496                    }
497
498                    // Re-enable clipping with the stack (we will reuse this view)
499                    setClipViewInStack(true);
500                }
501            })
502            .start();
503    }
504
505    /** Enables/disables handling touch on this task view. */
506    void setTouchEnabled(boolean enabled) {
507        setOnClickListener(enabled ? this : null);
508    }
509
510    /** Animates this task view if the user does not interact with the stack after a certain time. */
511    void startNoUserInteractionAnimation() {
512        mHeaderView.startNoUserInteractionAnimation();
513    }
514
515    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
516    void setNoUserInteractionState() {
517        mHeaderView.setNoUserInteractionState();
518    }
519
520    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
521    void resetNoUserInteractionState() {
522        mHeaderView.resetNoUserInteractionState();
523    }
524
525    /** Dismisses this task. */
526    void dismissTask() {
527        // Animate out the view and call the callback
528        final TaskView tv = this;
529        startDeleteTaskAnimation(new Runnable() {
530            @Override
531            public void run() {
532                EventBus.getDefault().send(new DismissTaskViewEvent(mTask, tv));
533            }
534        }, 0);
535    }
536
537    /**
538     * Returns whether this view should be clipped, or any views below should clip against this
539     * view.
540     */
541    boolean shouldClipViewInStack() {
542        // Never clip for freeform tasks or if invisible
543        if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE) {
544            return false;
545        }
546        return mClipViewInStack;
547    }
548
549    /** Sets whether this view should be clipped, or clipped against. */
550    void setClipViewInStack(boolean clip) {
551        if (clip != mClipViewInStack) {
552            mClipViewInStack = clip;
553            if (mCb != null) {
554                mCb.onTaskViewClipStateChanged(this);
555            }
556        }
557    }
558
559    /** Sets the current task progress. */
560    public void setTaskProgress(float p) {
561        mTaskProgress = p;
562        mViewBounds.setAlpha(p);
563        updateDimFromTaskProgress();
564    }
565
566    /** Returns the current task progress. */
567    public float getTaskProgress() {
568        return mTaskProgress;
569    }
570
571    /** Returns the current dim. */
572    public void setDim(int dim) {
573        RecentsConfiguration config = Recents.getConfiguration();
574
575        mDimAlpha = dim;
576        if (config.useHardwareLayers) {
577            // Defer setting hardware layers if we have not yet measured, or there is no dim to draw
578            if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
579                mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0));
580                mDimLayerPaint.setColorFilter(mDimColorFilter);
581                mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
582            }
583        } else {
584            float dimAlpha = mDimAlpha / 255.0f;
585            if (mThumbnailView != null) {
586                mThumbnailView.setDimAlpha(dimAlpha);
587            }
588            if (mHeaderView != null) {
589                mHeaderView.setDimAlpha(dim);
590            }
591        }
592    }
593
594    /** Returns the current dim. */
595    public int getDim() {
596        return mDimAlpha;
597    }
598
599    /** Animates the dim to the task progress. */
600    void animateDimToProgress(int duration, Animator.AnimatorListener postAnimRunnable) {
601        // Animate the dim into view as well
602        int toDim = getDimFromTaskProgress();
603        if (toDim != getDim()) {
604            ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim);
605            anim.setDuration(duration);
606            if (postAnimRunnable != null) {
607                anim.addListener(postAnimRunnable);
608            }
609            anim.start();
610        } else {
611            postAnimRunnable.onAnimationEnd(null);
612        }
613    }
614
615    /** Compute the dim as a function of the scale of this view. */
616    int getDimFromTaskProgress() {
617        // TODO: Temporarily disable the dim on the stack
618        /*
619        float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress);
620        return (int) (dim * 255);
621        */
622        return 0;
623    }
624
625    /** Update the dim as a function of the scale of this view. */
626    void updateDimFromTaskProgress() {
627        setDim(getDimFromTaskProgress());
628    }
629
630    /**** View focus state ****/
631
632    /**
633     * Explicitly sets the focused state of this task.
634     */
635    public void setFocusedState(boolean isFocused, boolean animated, boolean requestViewFocus) {
636        if (DEBUG) {
637            Log.d(TAG, "setFocusedState: " + mTask.activityLabel + " focused: " + isFocused +
638                    " mIsFocused: " + mIsFocused + " animated: " + animated +
639                    " requestViewFocus: " + requestViewFocus + " isFocused(): " + isFocused() +
640                    " isAccessibilityFocused(): " + isAccessibilityFocused());
641        }
642
643        SystemServicesProxy ssp = Recents.getSystemServices();
644        mIsFocused = isFocused;
645        mIsFocusAnimated = animated;
646        mHeaderView.onTaskViewFocusChanged(isFocused, animated);
647        mThumbnailView.onFocusChanged(isFocused);
648        if (isFocused) {
649            if (requestViewFocus && !isFocused()) {
650                requestFocus();
651            }
652            if (requestViewFocus && !isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
653                requestAccessibilityFocus();
654            }
655        } else {
656            if (isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
657                clearAccessibilityFocus();
658            }
659        }
660    }
661
662    /**
663     * Returns whether we have explicitly been focused.
664     */
665    public boolean isFocusedTask() {
666        return mIsFocused;
667    }
668
669    /**
670     * Returns whether this focused task is animated.
671     */
672    public boolean isFocusAnimated() {
673        return mIsFocusAnimated;
674    }
675
676    public void disableLayersForOneFrame() {
677        mHeaderView.disableLayersForOneFrame();
678    }
679
680    /**** TaskCallbacks Implementation ****/
681
682    /** Binds this task view to the task */
683    public void onTaskBound(Task t) {
684        mTask = t;
685        mTask.setCallbacks(this);
686
687        // Hide the action button if lock to app is disabled for this view
688        int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE;
689        if (mActionButtonView.getVisibility() != lockButtonVisibility) {
690            mActionButtonView.setVisibility(lockButtonVisibility);
691            requestLayout();
692        }
693    }
694
695    @Override
696    public void onTaskDataLoaded() {
697        SystemServicesProxy ssp = Recents.getSystemServices();
698        RecentsConfiguration config = Recents.getConfiguration();
699        if (mThumbnailView != null && mHeaderView != null) {
700            // Bind each of the views to the new task data
701            mThumbnailView.rebindToTask(mTask);
702            mHeaderView.rebindToTask(mTask);
703            // Rebind any listeners
704            mActionButtonView.setOnClickListener(this);
705
706            // Only enable long-click if we have a freeform workspace to drag to/from, or if we
707            // aren't already docked
708            if (ssp.hasFreeformWorkspaceSupport() || !config.hasDockedTasks) {
709                setOnLongClickListener(this);
710            } else {
711                setOnLongClickListener(null);
712            }
713        }
714        mTaskDataLoaded = true;
715    }
716
717    @Override
718    public void onTaskDataUnloaded() {
719        if (mThumbnailView != null && mHeaderView != null) {
720            // Unbind each of the views from the task data and remove the task callback
721            mTask.setCallbacks(null);
722            mThumbnailView.unbindFromTask();
723            mHeaderView.unbindFromTask();
724            // Unbind any listeners
725            mActionButtonView.setOnClickListener(null);
726        }
727        mTaskDataLoaded = false;
728    }
729
730    @Override
731    public void onTaskStackIdChanged() {
732        mHeaderView.rebindToTask(mTask);
733    }
734
735    /**** View.OnClickListener Implementation ****/
736
737    @Override
738     public void onClick(final View v) {
739        if (v == mActionButtonView) {
740            // Reset the translation of the action button before we animate it out
741            mActionButtonView.setTranslationZ(0f);
742        }
743        if (mCb != null) {
744            mCb.onTaskViewClicked(this, mTask, (v == mActionButtonView));
745        }
746    }
747
748    /**** View.OnLongClickListener Implementation ****/
749
750    @Override
751    public boolean onLongClick(View v) {
752        if (v == this) {
753            // Start listening for drag events
754            setClipViewInStack(false);
755
756            final float finalScale = getScaleX() * 1.05f;
757            final int width = getWidth();
758            final int height = getHeight();
759            Bitmap dragBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
760            Canvas c = new Canvas(dragBitmap);
761            mThumbnailView.draw(c);
762            mHeaderView.draw(c);
763            c.setBitmap(null);
764
765            // The downTouchPos is relative to the currently transformed TaskView, but we will be
766            // dragging a copy of the full task view, which makes it easier for us to animate them
767            // when the user drops
768            mDownTouchPos.x += ((1f - getScaleX()) * width) / 2;
769            mDownTouchPos.y += ((1f - getScaleY()) * height) / 2;
770
771            // Initiate the drag
772            final DragView dragView = new DragView(getContext(), dragBitmap, mDownTouchPos);
773            dragView.setOutlineProvider(new ViewOutlineProvider() {
774                @Override
775                public void getOutline(View view, Outline outline) {
776                    outline.setRect(0, 0, width, height);
777                }
778            });
779            dragView.setScaleX(getScaleX());
780            dragView.setScaleY(getScaleY());
781            dragView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
782                @Override
783                public void onViewAttachedToWindow(View v) {
784                    // Hide this task view after the drag view is attached
785                    setVisibility(View.INVISIBLE);
786                    // Animate the alpha slightly to indicate dragging
787                    dragView.setElevation(getElevation());
788                    dragView.setTranslationZ(getTranslationZ());
789                    dragView.animate()
790                            .scaleX(finalScale)
791                            .scaleY(finalScale)
792                            .setDuration(175)
793                            .setInterpolator(mFastOutSlowInInterpolator)
794                            .start();
795                }
796
797                @Override
798                public void onViewDetachedFromWindow(View v) {
799                    // Do nothing
800                }
801            });
802            EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
803            EventBus.getDefault().send(new DragStartEvent(mTask, this, dragView));
804            return true;
805        }
806        return false;
807    }
808
809    /**** Events ****/
810
811    public final void onBusEvent(DragEndEvent event) {
812        if (!(event.dropTarget instanceof TaskStack.DockState)) {
813            event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
814                @Override
815                public void run() {
816                    // Show this task view
817                    setVisibility(View.VISIBLE);
818
819                    // Animate the drag view back from where it is, to the view location, then after
820                    // it returns, update the clip state
821                    setClipViewInStack(true);
822                }
823            });
824        }
825        EventBus.getDefault().unregister(this);
826    }
827}
828