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