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