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