TaskView.java revision de0591a0788c96757cce1eed93a7e8bc8bd0ef01
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.AnimatorSet;
22import android.animation.ObjectAnimator;
23import android.animation.ValueAnimator;
24import android.content.Context;
25import android.content.res.Resources;
26import android.graphics.Color;
27import android.graphics.Outline;
28import android.graphics.Paint;
29import android.graphics.Point;
30import android.graphics.PorterDuff;
31import android.graphics.PorterDuffColorFilter;
32import android.graphics.Rect;
33import android.util.AttributeSet;
34import android.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 mClipViewInStack;
82    AnimateableViewBounds mViewBounds;
83    private AnimatorSet mClipAnimation;
84
85    View mContent;
86    TaskViewThumbnail mThumbnailView;
87    TaskViewHeader mHeaderView;
88    View mActionButtonView;
89    TaskViewCallbacks mCb;
90
91    Point mDownTouchPos = new Point();
92
93    Interpolator mFastOutSlowInInterpolator;
94    Interpolator mFastOutLinearInInterpolator;
95    Interpolator mQuintOutInterpolator;
96
97    // Optimizations
98    ValueAnimator.AnimatorUpdateListener mUpdateDimListener =
99            new ValueAnimator.AnimatorUpdateListener() {
100                @Override
101                public void onAnimationUpdate(ValueAnimator animation) {
102                    setTaskProgress((Float) animation.getAnimatedValue());
103                }
104            };
105
106
107    public TaskView(Context context) {
108        this(context, null);
109    }
110
111    public TaskView(Context context, AttributeSet attrs) {
112        this(context, attrs, 0);
113    }
114
115    public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
116        this(context, attrs, defStyleAttr, 0);
117    }
118
119    public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
120        super(context, attrs, defStyleAttr, defStyleRes);
121        RecentsConfiguration config = Recents.getConfiguration();
122        Resources res = context.getResources();
123        mMaxDimScale = res.getInteger(R.integer.recents_max_task_stack_view_dim) / 255f;
124        mClipViewInStack = true;
125        mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize(
126                R.dimen.recents_task_view_rounded_corners_radius));
127        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
128                com.android.internal.R.interpolator.fast_out_slow_in);
129        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
130                com.android.internal.R.interpolator.fast_out_linear_in);
131        mQuintOutInterpolator = AnimationUtils.loadInterpolator(context,
132                com.android.internal.R.interpolator.decelerate_quint);
133        setTaskProgress(getTaskProgress());
134        setDim(getDim());
135        if (config.fakeShadows) {
136            setBackground(new FakeShadowDrawable(res, config));
137        }
138        setOutlineProvider(mViewBounds);
139    }
140
141    /** Set callback */
142    void setCallbacks(TaskViewCallbacks cb) {
143        mCb = cb;
144    }
145
146    /** Resets this TaskView for reuse. */
147    void reset() {
148        resetViewProperties();
149        resetNoUserInteractionState();
150        setClipViewInStack(false);
151        setCallbacks(null);
152    }
153
154    /** Gets the task */
155    public Task getTask() {
156        return mTask;
157    }
158
159    /** Returns the view bounds. */
160    AnimateableViewBounds getViewBounds() {
161        return mViewBounds;
162    }
163
164    @Override
165    protected void onFinishInflate() {
166        // Bind the views
167        mContent = findViewById(R.id.task_view_content);
168        mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar);
169        mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail);
170        mActionButtonView = findViewById(R.id.lock_to_app_fab);
171        mActionButtonView.setOutlineProvider(new ViewOutlineProvider() {
172            @Override
173            public void getOutline(View view, Outline outline) {
174                // Set the outline to match the FAB background
175                outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight());
176            }
177        });
178        mActionButtonTranslationZ = mActionButtonView.getTranslationZ();
179    }
180
181    @Override
182    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
183        super.onSizeChanged(w, h, oldw, oldh);
184        mHeaderView.onTaskViewSizeChanged(w, h);
185        mThumbnailView.onTaskViewSizeChanged(w, h);
186    }
187
188    @Override
189    public boolean onInterceptTouchEvent(MotionEvent ev) {
190        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
191            mDownTouchPos.set((int) (ev.getX() * getScaleX()), (int) (ev.getY() * getScaleY()));
192        }
193        return super.onInterceptTouchEvent(ev);
194    }
195
196    @Override
197    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
198        int width = MeasureSpec.getSize(widthMeasureSpec);
199        int height = MeasureSpec.getSize(heightMeasureSpec);
200
201        int widthWithoutPadding = width - mPaddingLeft - mPaddingRight;
202        int heightWithoutPadding = height - mPaddingTop - mPaddingBottom;
203        int taskBarHeight = getResources().getDimensionPixelSize(R.dimen.recents_task_bar_height);
204
205        // Measure the content
206        mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
207                MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
208
209        // Measure the bar view, and action button
210        mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
211                MeasureSpec.makeMeasureSpec(taskBarHeight, MeasureSpec.EXACTLY));
212        mActionButtonView.measure(
213                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST),
214                MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST));
215        // Measure the thumbnail to be square
216        mThumbnailView.measure(
217                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
218                MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
219        mThumbnailView.updateClipToTaskBar(mHeaderView);
220
221        setMeasuredDimension(width, height);
222        invalidateOutline();
223    }
224
225    /** Synchronizes this view's properties with the task's transform */
226    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int clipBottom,
227            int duration, Interpolator interpolator,
228            ValueAnimator.AnimatorUpdateListener updateCallback) {
229        RecentsConfiguration config = Recents.getConfiguration();
230        Utilities.cancelAnimationWithoutCallbacks(mClipAnimation);
231
232        // Apply the transform
233        toTransform.applyToTaskView(this, duration, interpolator, false,
234                !config.fakeShadows, updateCallback);
235
236        // Update the clipping
237        if (duration > 0) {
238            mClipAnimation = new AnimatorSet();
239            mClipAnimation.playTogether(
240                    ObjectAnimator.ofInt(mViewBounds, AnimateableViewBounds.CLIP_BOTTOM,
241                            mViewBounds.getClipBottom(), clipBottom),
242                    ObjectAnimator.ofInt(this, TaskViewTransform.LEFT, getLeft(),
243                            (int) toTransform.rect.left),
244                    ObjectAnimator.ofInt(this, TaskViewTransform.TOP, getTop(),
245                            (int) toTransform.rect.top),
246                    ObjectAnimator.ofInt(this, TaskViewTransform.RIGHT, getRight(),
247                            (int) toTransform.rect.right),
248                    ObjectAnimator.ofInt(this, TaskViewTransform.BOTTOM, getBottom(),
249                            (int) toTransform.rect.bottom),
250                    ObjectAnimator.ofFloat(mThumbnailView, TaskViewThumbnail.BITMAP_SCALE,
251                            mThumbnailView.getBitmapScale(), toTransform.thumbnailScale));
252            mClipAnimation.setStartDelay(toTransform.startDelay);
253            mClipAnimation.setDuration(duration);
254            mClipAnimation.setInterpolator(interpolator);
255            mClipAnimation.start();
256        } else {
257            mViewBounds.setClipBottom(clipBottom, false /* forceUpdate */);
258            mThumbnailView.setBitmapScale(toTransform.thumbnailScale);
259            setLeftTopRightBottom((int) toTransform.rect.left, (int) toTransform.rect.top,
260                    (int) toTransform.rect.right, (int) toTransform.rect.bottom);
261        }
262        if (!config.useHardwareLayers) {
263            mThumbnailView.updateThumbnailVisibility(clipBottom - getPaddingBottom());
264        }
265
266        // Update the task progress
267        Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator);
268        if (duration <= 0) {
269            setTaskProgress(toTransform.p);
270        } else {
271            mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p);
272            mTaskProgressAnimator.setDuration(duration);
273            mTaskProgressAnimator.addUpdateListener(mUpdateDimListener);
274            mTaskProgressAnimator.start();
275        }
276    }
277
278    /** Resets this view's properties */
279    void resetViewProperties() {
280        setDim(0);
281        setLayerType(View.LAYER_TYPE_NONE, null);
282        setVisibility(View.VISIBLE);
283        getViewBounds().reset();
284        TaskViewTransform.reset(this);
285        if (mActionButtonView != null) {
286            mActionButtonView.setScaleX(1f);
287            mActionButtonView.setScaleY(1f);
288            mActionButtonView.setAlpha(1f);
289            mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
290        }
291    }
292
293    /** Prepares this task view for the enter-recents animations.  This is called earlier in the
294     * first layout because the actual animation into recents may take a long time. */
295    void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, boolean hideTask,
296            boolean occludesLaunchTarget, int offscreenY) {
297        RecentsConfiguration config = Recents.getConfiguration();
298        RecentsActivityLaunchState launchState = config.getLaunchState();
299        int initialDim = getDim();
300        if (hideTask) {
301            setVisibility(View.INVISIBLE);
302        } else if (launchState.launchedHasConfigurationChanged) {
303            // Just load the views as-is
304        } else if (launchState.launchedFromAppWithThumbnail) {
305            if (isTaskViewLaunchTargetTask) {
306                // Set the dim to 0 so we can animate it in
307                initialDim = 0;
308                // Hide the action button
309                mActionButtonView.setAlpha(0f);
310            } else if (occludesLaunchTarget) {
311                // Move the task view off screen (below) so we can animate it in
312                setTranslationY(offscreenY);
313            }
314
315        } else if (launchState.launchedFromHome) {
316            // Move the task view off screen (below) so we can animate it in
317            setTranslationY(offscreenY);
318            setTranslationZ(0);
319            setScaleX(1f);
320            setScaleY(1f);
321        }
322        // Apply the current dim
323        setDim(initialDim);
324    }
325
326    /** Animates this task view as it enters recents */
327    void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
328        RecentsConfiguration config = Recents.getConfiguration();
329        RecentsActivityLaunchState launchState = config.getLaunchState();
330        Resources res = mContext.getResources();
331        final TaskViewTransform transform = ctx.currentTaskTransform;
332        final int taskViewEnterFromAppDuration = res.getInteger(
333                R.integer.recents_task_enter_from_app_duration);
334        final int taskViewEnterFromHomeDuration = res.getInteger(
335                R.integer.recents_task_enter_from_home_duration);
336        final int taskViewEnterFromHomeStaggerDelay = res.getInteger(
337                R.integer.recents_task_enter_from_home_stagger_delay);
338        final int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
339                R.dimen.recents_task_view_affiliate_group_enter_offset);
340
341        if (launchState.launchedFromAppWithThumbnail) {
342            if (mTask.isLaunchTarget) {
343                // Immediately start the dim animation
344                animateDimToProgress(taskViewEnterFromAppDuration,
345                        ctx.postAnimationTrigger.decrementOnAnimationEnd());
346                ctx.postAnimationTrigger.increment();
347
348                // Animate the action button in
349                fadeInActionButton(taskViewEnterFromAppDuration);
350            } else {
351                // Animate the task up if it was occluding the launch target
352                if (ctx.currentTaskOccludesLaunchTarget) {
353                    setTranslationY(taskViewAffiliateGroupEnterOffset);
354                    setAlpha(0f);
355                    animate().alpha(1f)
356                            .translationY(0)
357                            .setUpdateListener(null)
358                            .setListener(new AnimatorListenerAdapter() {
359                                private boolean hasEnded;
360
361                                // We use the animation listener instead of withEndAction() to
362                                // ensure that onAnimationEnd() is called when the animator is
363                                // cancelled
364                                @Override
365                                public void onAnimationEnd(Animator animation) {
366                                    if (hasEnded) return;
367                                    ctx.postAnimationTrigger.decrement();
368                                    hasEnded = true;
369                                }
370                            })
371                            .setInterpolator(mFastOutSlowInInterpolator)
372                            .setDuration(taskViewEnterFromHomeDuration)
373                            .start();
374                    ctx.postAnimationTrigger.increment();
375                }
376            }
377
378        } else if (launchState.launchedFromHome) {
379            // Animate the tasks up
380            int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
381            int delay = frontIndex * taskViewEnterFromHomeStaggerDelay;
382
383            setScaleX(transform.scale);
384            setScaleY(transform.scale);
385            if (!config.fakeShadows) {
386                animate().translationZ(transform.translationZ);
387            }
388            animate()
389                    .translationY(0)
390                    .setStartDelay(delay)
391                    .setUpdateListener(ctx.updateListener)
392                    .setListener(new AnimatorListenerAdapter() {
393                        private boolean hasEnded;
394
395                        // We use the animation listener instead of withEndAction() to ensure that
396                        // onAnimationEnd() is called when the animator is cancelled
397                        @Override
398                        public void onAnimationEnd(Animator animation) {
399                            if (hasEnded) return;
400                            ctx.postAnimationTrigger.decrement();
401                            hasEnded = true;
402                        }
403                    })
404                    .setInterpolator(mQuintOutInterpolator)
405                    .setDuration(taskViewEnterFromHomeDuration +
406                            frontIndex * taskViewEnterFromHomeStaggerDelay)
407                    .start();
408            ctx.postAnimationTrigger.increment();
409        }
410    }
411
412    public void cancelEnterRecentsAnimation() {
413        animate().cancel();
414    }
415
416    public void fadeInActionButton(int duration) {
417        // Hide the action button
418        mActionButtonView.setAlpha(0f);
419
420        // Animate the action button in
421        mActionButtonView.animate().alpha(1f)
422                .setDuration(duration)
423                .setInterpolator(PhoneStatusBar.ALPHA_IN)
424                .start();
425    }
426
427    /** Animates this task view as it leaves recents by pressing home. */
428    void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
429        int taskViewExitToHomeDuration = getResources().getInteger(
430                R.integer.recents_task_exit_to_home_duration);
431        animate()
432                .translationY(ctx.offscreenTranslationY)
433                .setStartDelay(0)
434                .setUpdateListener(null)
435                .setListener(null)
436                .setInterpolator(mFastOutLinearInInterpolator)
437                .setDuration(taskViewExitToHomeDuration)
438                .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
439                .start();
440        ctx.postAnimationTrigger.increment();
441    }
442
443    /** Animates this task view as it exits recents */
444    void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask,
445            boolean occludesLaunchTarget, boolean lockToTask) {
446        final int taskViewExitToAppDuration = mContext.getResources().getInteger(
447                R.integer.recents_task_exit_to_app_duration);
448        final int taskViewAffiliateGroupEnterOffset = mContext.getResources().getDimensionPixelSize(
449                R.dimen.recents_task_view_affiliate_group_enter_offset);
450
451        if (isLaunchingTask) {
452            // Animate the dim
453            if (mDimAlpha > 0) {
454                ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
455                anim.setDuration(taskViewExitToAppDuration);
456                anim.setInterpolator(mFastOutLinearInInterpolator);
457                anim.start();
458            }
459
460            // Animate the action button away
461            if (!lockToTask) {
462                float toScale = 0.9f;
463                mActionButtonView.animate()
464                        .scaleX(toScale)
465                        .scaleY(toScale);
466            }
467            mActionButtonView.animate()
468                    .alpha(0f)
469                    .setStartDelay(0)
470                    .setDuration(taskViewExitToAppDuration)
471                    .setInterpolator(mFastOutLinearInInterpolator)
472                    .withEndAction(postAnimRunnable)
473                    .start();
474        } else {
475            // Hide the dismiss button
476            mHeaderView.startLaunchTaskDismissAnimation(postAnimRunnable);
477            // If this is another view in the task grouping and is in front of the launch task,
478            // animate it away first
479            if (occludesLaunchTarget) {
480                animate().alpha(0f)
481                    .translationY(getTranslationY() + taskViewAffiliateGroupEnterOffset)
482                    .setStartDelay(0)
483                    .setUpdateListener(null)
484                    .setListener(null)
485                    .setInterpolator(mFastOutLinearInInterpolator)
486                    .setDuration(taskViewExitToAppDuration)
487                    .start();
488            }
489        }
490    }
491
492    /** Animates the deletion of this task view */
493    void startDeleteTaskAnimation(final Runnable r, int delay) {
494        int taskViewRemoveAnimDuration = getResources().getInteger(
495                R.integer.recents_animate_task_view_remove_duration);
496        int taskViewRemoveAnimTranslationXPx = getResources().getDimensionPixelSize(
497                R.dimen.recents_task_view_remove_anim_translation_x);
498
499        // Disabling clipping with the stack while the view is animating away
500        setClipViewInStack(false);
501
502        animate().translationX(taskViewRemoveAnimTranslationXPx)
503            .alpha(0f)
504            .setStartDelay(delay)
505            .setUpdateListener(null)
506            .setListener(null)
507            .setInterpolator(mFastOutSlowInInterpolator)
508            .setDuration(taskViewRemoveAnimDuration)
509            .withEndAction(new Runnable() {
510                @Override
511                public void run() {
512                    if (r != null) {
513                        r.run();
514                    }
515
516                    // Re-enable clipping with the stack (we will reuse this view)
517                    setClipViewInStack(true);
518                }
519            })
520            .start();
521    }
522
523    /** Enables/disables handling touch on this task view. */
524    void setTouchEnabled(boolean enabled) {
525        setOnClickListener(enabled ? this : null);
526    }
527
528    /** Animates this task view if the user does not interact with the stack after a certain time. */
529    void startNoUserInteractionAnimation() {
530        mHeaderView.startNoUserInteractionAnimation();
531    }
532
533    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
534    void setNoUserInteractionState() {
535        mHeaderView.setNoUserInteractionState();
536    }
537
538    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
539    void resetNoUserInteractionState() {
540        mHeaderView.resetNoUserInteractionState();
541    }
542
543    /** Dismisses this task. */
544    void dismissTask() {
545        // Animate out the view and call the callback
546        final TaskView tv = this;
547        startDeleteTaskAnimation(new Runnable() {
548            @Override
549            public void run() {
550                EventBus.getDefault().send(new DismissTaskViewEvent(mTask, tv));
551            }
552        }, 0);
553    }
554
555    /**
556     * Returns whether this view should be clipped, or any views below should clip against this
557     * view.
558     */
559    boolean shouldClipViewInStack() {
560        // Never clip for freeform tasks or if invisible
561        if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE) {
562            return false;
563        }
564        return mClipViewInStack;
565    }
566
567    /** Sets whether this view should be clipped, or clipped against. */
568    void setClipViewInStack(boolean clip) {
569        if (clip != mClipViewInStack) {
570            mClipViewInStack = clip;
571            if (mCb != null) {
572                mCb.onTaskViewClipStateChanged(this);
573            }
574        }
575    }
576
577    /** Sets the current task progress. */
578    public void setTaskProgress(float p) {
579        mTaskProgress = p;
580        mViewBounds.setAlpha(p);
581        updateDimFromTaskProgress();
582    }
583
584    /** Returns the current task progress. */
585    public float getTaskProgress() {
586        return mTaskProgress;
587    }
588
589    /** Returns the current dim. */
590    public void setDim(int dim) {
591        RecentsConfiguration config = Recents.getConfiguration();
592
593        mDimAlpha = dim;
594        if (config.useHardwareLayers) {
595            // Defer setting hardware layers if we have not yet measured, or there is no dim to draw
596            if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
597                mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0));
598                mDimLayerPaint.setColorFilter(mDimColorFilter);
599                mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
600            }
601        } else {
602            float dimAlpha = mDimAlpha / 255.0f;
603            if (mThumbnailView != null) {
604                mThumbnailView.setDimAlpha(dimAlpha);
605            }
606            if (mHeaderView != null) {
607                mHeaderView.setDimAlpha(dim);
608            }
609        }
610    }
611
612    /** Returns the current dim. */
613    public int getDim() {
614        return mDimAlpha;
615    }
616
617    /** Animates the dim to the task progress. */
618    void animateDimToProgress(int duration, Animator.AnimatorListener postAnimRunnable) {
619        // Animate the dim into view as well
620        int toDim = getDimFromTaskProgress();
621        if (toDim != getDim()) {
622            ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim);
623            anim.setDuration(duration);
624            if (postAnimRunnable != null) {
625                anim.addListener(postAnimRunnable);
626            }
627            anim.start();
628        } else {
629            postAnimRunnable.onAnimationEnd(null);
630        }
631    }
632
633    /** Compute the dim as a function of the scale of this view. */
634    int getDimFromTaskProgress() {
635        float x = mTaskProgress < 0
636                ? 1f
637                : mDimInterpolator.getInterpolation(1f - mTaskProgress);
638        float dim = mMaxDimScale * x;
639        return (int) (dim * 255);
640    }
641
642    /** Update the dim as a function of the scale of this view. */
643    void updateDimFromTaskProgress() {
644        setDim(getDimFromTaskProgress());
645    }
646
647    /**** View focus state ****/
648
649    /**
650     * Explicitly sets the focused state of this task.
651     */
652    public void setFocusedState(boolean isFocused, boolean animated, boolean requestViewFocus) {
653        if (DEBUG) {
654            Log.d(TAG, "setFocusedState: " + mTask.activityLabel + " focused: " + isFocused +
655                    " animated: " + animated + " requestViewFocus: " + requestViewFocus +
656                    " isFocused(): " + isFocused() +
657                    " isAccessibilityFocused(): " + isAccessibilityFocused());
658        }
659
660        SystemServicesProxy ssp = Recents.getSystemServices();
661        mHeaderView.onTaskViewFocusChanged(isFocused, animated);
662        if (isFocused) {
663            if (requestViewFocus && !isFocused()) {
664                requestFocus();
665            }
666            if (requestViewFocus && !isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
667                requestAccessibilityFocus();
668            }
669        } else {
670            if (isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
671                clearAccessibilityFocus();
672            }
673        }
674    }
675
676    /**** TaskCallbacks Implementation ****/
677
678    /** Binds this task view to the task */
679    public void onTaskBound(Task t) {
680        mTask = t;
681        mTask.setCallbacks(this);
682
683        // Hide the action button if lock to app is disabled for this view
684        int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE;
685        if (mActionButtonView.getVisibility() != lockButtonVisibility) {
686            mActionButtonView.setVisibility(lockButtonVisibility);
687            requestLayout();
688        }
689    }
690
691    @Override
692    public void onTaskDataLoaded() {
693        if (mThumbnailView != null && mHeaderView != null) {
694            // Bind each of the views to the new task data
695            mThumbnailView.rebindToTask(mTask);
696            mHeaderView.rebindToTask(mTask);
697
698            // Rebind any listeners
699            mActionButtonView.setOnClickListener(this);
700            setOnLongClickListener(this);
701        }
702        mTaskDataLoaded = true;
703    }
704
705    @Override
706    public void onTaskDataUnloaded() {
707        if (mThumbnailView != null && mHeaderView != null) {
708            // Unbind each of the views from the task data and remove the task callback
709            mTask.setCallbacks(null);
710            mThumbnailView.unbindFromTask();
711            mHeaderView.unbindFromTask();
712            // Unbind any listeners
713            mActionButtonView.setOnClickListener(null);
714        }
715        mTaskDataLoaded = false;
716    }
717
718    @Override
719    public void onTaskStackIdChanged() {
720        mHeaderView.rebindToTask(mTask);
721    }
722
723    /**** View.OnClickListener Implementation ****/
724
725    @Override
726     public void onClick(final View v) {
727        if (v == mActionButtonView) {
728            // Reset the translation of the action button before we animate it out
729            mActionButtonView.setTranslationZ(0f);
730        }
731        if (mCb != null) {
732            mCb.onTaskViewClicked(this, mTask, (v == mActionButtonView));
733        }
734    }
735
736    /**** View.OnLongClickListener Implementation ****/
737
738    @Override
739    public boolean onLongClick(View v) {
740        SystemServicesProxy ssp = Recents.getSystemServices();
741        // Since we are clipping the view to the bounds, manually do the hit test
742        Rect clipBounds = new Rect(mViewBounds.mClipBounds);
743        clipBounds.scale(getScaleX());
744        boolean inBounds = clipBounds.contains(mDownTouchPos.x, mDownTouchPos.y);
745        if (v == this && inBounds && !ssp.hasDockedTask()) {
746            // Start listening for drag events
747            setClipViewInStack(false);
748
749            // Enlarge the view slightly
750            final float finalScale = getScaleX() * 1.05f;
751            animate()
752                    .scaleX(finalScale)
753                    .scaleY(finalScale)
754                    .setDuration(175)
755                    .setUpdateListener(null)
756                    .setListener(null)
757                    .setInterpolator(mFastOutSlowInInterpolator)
758                    .start();
759
760            mDownTouchPos.x += ((1f - getScaleX()) * getWidth()) / 2;
761            mDownTouchPos.y += ((1f - getScaleY()) * getHeight()) / 2;
762
763            EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
764            EventBus.getDefault().send(new DragStartEvent(mTask, this, mDownTouchPos));
765            return true;
766        }
767        return false;
768    }
769
770    /**** Events ****/
771
772    public final void onBusEvent(DragEndEvent event) {
773        if (!(event.dropTarget instanceof TaskStack.DockState)) {
774            event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
775                @Override
776                public void run() {
777                    // Animate the drag view back from where it is, to the view location, then after
778                    // it returns, update the clip state
779                    setClipViewInStack(true);
780                }
781            });
782        }
783        EventBus.getDefault().unregister(this);
784    }
785}
786