TaskView.java revision aaeaac17831090594e4927b1531c4658d239a3ea
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.activity.LaunchTaskEvent;
49import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
50import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
51import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
52import com.android.systemui.recents.misc.SystemServicesProxy;
53import com.android.systemui.recents.misc.Utilities;
54import com.android.systemui.recents.model.Task;
55import com.android.systemui.recents.model.TaskStack;
56import com.android.systemui.statusbar.phone.PhoneStatusBar;
57
58import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
59
60/* A task view */
61public class TaskView extends FrameLayout implements Task.TaskCallbacks,
62        View.OnClickListener, View.OnLongClickListener {
63
64    private final static String TAG = "TaskView";
65    private final static boolean DEBUG = false;
66
67    /** The TaskView callbacks */
68    interface TaskViewCallbacks {
69        void onTaskViewClipStateChanged(TaskView tv);
70    }
71
72    float mTaskProgress;
73    ObjectAnimator mTaskProgressAnimator;
74    float mMaxDimScale;
75    int mDimAlpha;
76    AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(3f);
77    PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
78    Paint mDimLayerPaint = new Paint();
79    float mActionButtonTranslationZ;
80
81    Task mTask;
82    boolean mTaskDataLoaded;
83    boolean mClipViewInStack;
84    AnimateableViewBounds mViewBounds;
85    private AnimatorSet mClipAnimation;
86
87    View mContent;
88    TaskViewThumbnail mThumbnailView;
89    TaskViewHeader mHeaderView;
90    View mActionButtonView;
91    TaskViewCallbacks mCb;
92
93    Point mDownTouchPos = new Point();
94
95    Interpolator mFastOutSlowInInterpolator;
96    Interpolator mFastOutLinearInInterpolator;
97    Interpolator mQuintOutInterpolator;
98
99    // Optimizations
100    ValueAnimator.AnimatorUpdateListener mUpdateDimListener =
101            new ValueAnimator.AnimatorUpdateListener() {
102                @Override
103                public void onAnimationUpdate(ValueAnimator animation) {
104                    setTaskProgress((Float) animation.getAnimatedValue());
105                }
106            };
107
108
109    public TaskView(Context context) {
110        this(context, null);
111    }
112
113    public TaskView(Context context, AttributeSet attrs) {
114        this(context, attrs, 0);
115    }
116
117    public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
118        this(context, attrs, defStyleAttr, 0);
119    }
120
121    public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
122        super(context, attrs, defStyleAttr, defStyleRes);
123        RecentsConfiguration config = Recents.getConfiguration();
124        Resources res = context.getResources();
125        mMaxDimScale = res.getInteger(R.integer.recents_max_task_stack_view_dim) / 255f;
126        mClipViewInStack = true;
127        mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize(
128                R.dimen.recents_task_view_rounded_corners_radius));
129        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
130                com.android.internal.R.interpolator.fast_out_slow_in);
131        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
132                com.android.internal.R.interpolator.fast_out_linear_in);
133        mQuintOutInterpolator = AnimationUtils.loadInterpolator(context,
134                com.android.internal.R.interpolator.decelerate_quint);
135        setTaskProgress(getTaskProgress());
136        setDim(getDim());
137        if (config.fakeShadows) {
138            setBackground(new FakeShadowDrawable(res, config));
139        }
140        setOutlineProvider(mViewBounds);
141    }
142
143    /** Set callback */
144    void setCallbacks(TaskViewCallbacks cb) {
145        mCb = cb;
146    }
147
148    /** Resets this TaskView for reuse. */
149    void reset() {
150        resetViewProperties();
151        resetNoUserInteractionState();
152        setClipViewInStack(false);
153        setCallbacks(null);
154    }
155
156    /** Gets the task */
157    public Task getTask() {
158        return mTask;
159    }
160
161    /** Returns the view bounds. */
162    AnimateableViewBounds getViewBounds() {
163        return mViewBounds;
164    }
165
166    @Override
167    protected void onFinishInflate() {
168        // Bind the views
169        mContent = findViewById(R.id.task_view_content);
170        mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar);
171        mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail);
172        mActionButtonView = findViewById(R.id.lock_to_app_fab);
173        mActionButtonView.setOutlineProvider(new ViewOutlineProvider() {
174            @Override
175            public void getOutline(View view, Outline outline) {
176                // Set the outline to match the FAB background
177                outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight());
178            }
179        });
180        mActionButtonTranslationZ = mActionButtonView.getTranslationZ();
181    }
182
183    @Override
184    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
185        super.onSizeChanged(w, h, oldw, oldh);
186        mHeaderView.onTaskViewSizeChanged(w, h);
187        mThumbnailView.onTaskViewSizeChanged(w, h);
188    }
189
190    @Override
191    public boolean onInterceptTouchEvent(MotionEvent ev) {
192        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
193            mDownTouchPos.set((int) (ev.getX() * getScaleX()), (int) (ev.getY() * getScaleY()));
194        }
195        return super.onInterceptTouchEvent(ev);
196    }
197
198    @Override
199    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
200        int width = MeasureSpec.getSize(widthMeasureSpec);
201        int height = MeasureSpec.getSize(heightMeasureSpec);
202
203        int widthWithoutPadding = width - mPaddingLeft - mPaddingRight;
204        int heightWithoutPadding = height - mPaddingTop - mPaddingBottom;
205        int taskBarHeight = getResources().getDimensionPixelSize(R.dimen.recents_task_bar_height);
206
207        // Measure the content
208        mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
209                MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
210
211        // Measure the bar view, and action button
212        mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
213                MeasureSpec.makeMeasureSpec(taskBarHeight, MeasureSpec.EXACTLY));
214        mActionButtonView.measure(
215                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST),
216                MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST));
217        // Measure the thumbnail to be square
218        mThumbnailView.measure(
219                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
220                MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
221        mThumbnailView.updateClipToTaskBar(mHeaderView);
222
223        setMeasuredDimension(width, height);
224        invalidateOutline();
225    }
226
227    /** Synchronizes this view's properties with the task's transform */
228    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int clipBottom,
229            int duration, Interpolator interpolator,
230            ValueAnimator.AnimatorUpdateListener updateCallback) {
231        RecentsConfiguration config = Recents.getConfiguration();
232        Utilities.cancelAnimationWithoutCallbacks(mClipAnimation);
233
234        // Apply the transform
235        toTransform.applyToTaskView(this, duration, interpolator, false,
236                !config.fakeShadows, updateCallback);
237
238        // Update the clipping
239        if (duration > 0) {
240            mClipAnimation = new AnimatorSet();
241            mClipAnimation.playTogether(
242                    ObjectAnimator.ofInt(mViewBounds, AnimateableViewBounds.CLIP_BOTTOM,
243                            mViewBounds.getClipBottom(), clipBottom),
244                    ObjectAnimator.ofInt(this, TaskViewTransform.LEFT, getLeft(),
245                            (int) toTransform.rect.left),
246                    ObjectAnimator.ofInt(this, TaskViewTransform.TOP, getTop(),
247                            (int) toTransform.rect.top),
248                    ObjectAnimator.ofInt(this, TaskViewTransform.RIGHT, getRight(),
249                            (int) toTransform.rect.right),
250                    ObjectAnimator.ofInt(this, TaskViewTransform.BOTTOM, getBottom(),
251                            (int) toTransform.rect.bottom),
252                    ObjectAnimator.ofFloat(mThumbnailView, TaskViewThumbnail.BITMAP_SCALE,
253                            mThumbnailView.getBitmapScale(), toTransform.thumbnailScale));
254            mClipAnimation.setStartDelay(toTransform.startDelay);
255            mClipAnimation.setDuration(duration);
256            mClipAnimation.setInterpolator(interpolator);
257            mClipAnimation.start();
258        } else {
259            mViewBounds.setClipBottom(clipBottom, false /* forceUpdate */);
260            mThumbnailView.setBitmapScale(toTransform.thumbnailScale);
261            setLeftTopRightBottom((int) toTransform.rect.left, (int) toTransform.rect.top,
262                    (int) toTransform.rect.right, (int) toTransform.rect.bottom);
263        }
264        if (!config.useHardwareLayers) {
265            mThumbnailView.updateThumbnailVisibility(clipBottom - getPaddingBottom());
266        }
267
268        // Update the task progress
269        Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator);
270        if (duration <= 0) {
271            setTaskProgress(toTransform.p);
272        } else {
273            mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p);
274            mTaskProgressAnimator.setDuration(duration);
275            mTaskProgressAnimator.addUpdateListener(mUpdateDimListener);
276            mTaskProgressAnimator.start();
277        }
278    }
279
280    /** Resets this view's properties */
281    void resetViewProperties() {
282        setDim(0);
283        setVisibility(View.VISIBLE);
284        getViewBounds().reset();
285        TaskViewTransform.reset(this);
286        if (mActionButtonView != null) {
287            mActionButtonView.setScaleX(1f);
288            mActionButtonView.setScaleY(1f);
289            mActionButtonView.setAlpha(1f);
290            mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
291        }
292    }
293
294    /** Prepares this task view for the enter-recents animations.  This is called earlier in the
295     * first layout because the actual animation into recents may take a long time. */
296    void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, boolean hideTask,
297            boolean occludesLaunchTarget, int offscreenY) {
298        RecentsConfiguration config = Recents.getConfiguration();
299        RecentsActivityLaunchState launchState = config.getLaunchState();
300        int initialDim = getDim();
301        if (hideTask) {
302            setVisibility(View.INVISIBLE);
303        } else if (launchState.launchedHasConfigurationChanged) {
304            // Just load the views as-is
305        } else if (launchState.launchedFromAppWithThumbnail) {
306            if (isTaskViewLaunchTargetTask) {
307                // Set the dim to 0 so we can animate it in
308                initialDim = 0;
309                // Hide the action button
310                mActionButtonView.setAlpha(0f);
311            } else if (occludesLaunchTarget) {
312                // Move the task view off screen (below) so we can animate it in
313                setTranslationY(offscreenY);
314            }
315
316        } else if (launchState.launchedFromHome) {
317            // Move the task view off screen (below) so we can animate it in
318            setTranslationY(offscreenY);
319            setTranslationZ(0);
320            setScaleX(1f);
321            setScaleY(1f);
322        }
323        // Apply the current dim
324        setDim(initialDim);
325    }
326
327    /** Animates this task view as it enters recents */
328    void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
329        RecentsConfiguration config = Recents.getConfiguration();
330        RecentsActivityLaunchState launchState = config.getLaunchState();
331        Resources res = mContext.getResources();
332        final TaskViewTransform transform = ctx.currentTaskTransform;
333        final int taskViewEnterFromAppDuration = res.getInteger(
334                R.integer.recents_task_enter_from_app_duration);
335        final int taskViewEnterFromHomeDuration = res.getInteger(
336                R.integer.recents_task_enter_from_home_duration);
337        final int taskViewEnterFromHomeStaggerDelay = res.getInteger(
338                R.integer.recents_task_enter_from_home_stagger_delay);
339        final int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
340                R.dimen.recents_task_view_affiliate_group_enter_offset);
341
342        if (launchState.launchedFromAppWithThumbnail) {
343            if (mTask.isLaunchTarget) {
344                ctx.postAnimationTrigger.increment();
345                // Immediately start the dim animation
346                animateDimToProgress(taskViewEnterFromAppDuration,
347                        ctx.postAnimationTrigger.decrementOnAnimationEnd());
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        if (isFocused) {
662            if (requestViewFocus && !isFocused()) {
663                requestFocus();
664            }
665            if (requestViewFocus && !isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
666                requestAccessibilityFocus();
667            }
668        } else {
669            if (isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
670                clearAccessibilityFocus();
671            }
672        }
673    }
674
675    /**** TaskCallbacks Implementation ****/
676
677    /** Binds this task view to the task */
678    public void onTaskBound(Task t) {
679        mTask = t;
680        mTask.addCallback(this);
681
682        // Hide the action button if lock to app is disabled for this view
683        int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE;
684        if (mActionButtonView.getVisibility() != lockButtonVisibility) {
685            mActionButtonView.setVisibility(lockButtonVisibility);
686            requestLayout();
687        }
688    }
689
690    @Override
691    public void onTaskDataLoaded(Task task) {
692        if (mThumbnailView != null && mHeaderView != null) {
693            // Bind each of the views to the new task data
694            mThumbnailView.rebindToTask(mTask);
695            mHeaderView.rebindToTask(mTask);
696
697            // Rebind any listeners
698            mActionButtonView.setOnClickListener(this);
699            setOnLongClickListener(this);
700        }
701        mTaskDataLoaded = true;
702    }
703
704    @Override
705    public void onTaskDataUnloaded() {
706        if (mThumbnailView != null && mHeaderView != null) {
707            // Unbind each of the views from the task data and remove the task callback
708            mTask.removeCallback(this);
709            mThumbnailView.unbindFromTask();
710            mHeaderView.unbindFromTask();
711            // Unbind any listeners
712            mActionButtonView.setOnClickListener(null);
713        }
714        mTaskDataLoaded = false;
715    }
716
717    @Override
718    public void onTaskStackIdChanged() {
719        mHeaderView.rebindToTask(mTask);
720    }
721
722    /**** View.OnClickListener Implementation ****/
723
724    @Override
725     public void onClick(final View v) {
726        boolean screenPinningRequested = false;
727        if (v == mActionButtonView) {
728            // Reset the translation of the action button before we animate it out
729            mActionButtonView.setTranslationZ(0f);
730            screenPinningRequested = true;
731        }
732        EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, INVALID_STACK_ID,
733                screenPinningRequested));
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.addPostAnimationCallback(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