TaskStackView.java revision a10370fc2eb8eb95631592160c5f6281b9d75722
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.app.Activity;
25import android.content.Context;
26import android.graphics.Canvas;
27import android.graphics.Rect;
28import android.graphics.Region;
29import android.view.LayoutInflater;
30import android.view.MotionEvent;
31import android.view.VelocityTracker;
32import android.view.View;
33import android.view.ViewConfiguration;
34import android.view.ViewParent;
35import android.widget.FrameLayout;
36import android.widget.OverScroller;
37import com.android.systemui.R;
38import com.android.systemui.recents.Console;
39import com.android.systemui.recents.Constants;
40import com.android.systemui.recents.RecentsConfiguration;
41import com.android.systemui.recents.RecentsTaskLoader;
42import com.android.systemui.recents.SystemServicesProxy;
43import com.android.systemui.recents.Utilities;
44import com.android.systemui.recents.model.Task;
45import com.android.systemui.recents.model.TaskStack;
46
47import java.util.ArrayList;
48
49
50/* The visual representation of a task stack view */
51public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
52        TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
53        View.OnClickListener {
54
55    /** The TaskView callbacks */
56    interface TaskStackViewCallbacks {
57        public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
58    }
59
60    TaskStack mStack;
61    TaskStackViewTouchHandler mTouchHandler;
62    TaskStackViewCallbacks mCb;
63    ViewPool<TaskView, Task> mViewPool;
64
65    // The various rects that define the stack view
66    Rect mRect = new Rect();
67    Rect mStackRect = new Rect();
68    Rect mStackRectSansPeek = new Rect();
69    Rect mTaskRect = new Rect();
70
71    // The virtual stack scroll that we use for the card layout
72    int mStackScroll;
73    int mMinScroll;
74    int mMaxScroll;
75    int mStashedScroll;
76    OverScroller mScroller;
77    ObjectAnimator mScrollAnimator;
78
79    // Optimizations
80    int mHwLayersRefCount;
81    int mStackViewsAnimationDuration;
82    boolean mStackViewsDirty = true;
83    boolean mAwaitingFirstLayout = true;
84    int[] mTmpVisibleRange = new int[2];
85    Rect mTmpRect = new Rect();
86    Rect mTmpRect2 = new Rect();
87    LayoutInflater mInflater;
88
89    public TaskStackView(Context context, TaskStack stack) {
90        super(context);
91        mStack = stack;
92        mStack.setCallbacks(this);
93        mScroller = new OverScroller(context);
94        mTouchHandler = new TaskStackViewTouchHandler(context, this);
95        mViewPool = new ViewPool<TaskView, Task>(context, this);
96        mInflater = LayoutInflater.from(context);
97    }
98
99    /** Sets the callbacks */
100    void setCallbacks(TaskStackViewCallbacks cb) {
101        mCb = cb;
102    }
103
104    /** Requests that the views be synchronized with the model */
105    void requestSynchronizeStackViewsWithModel() {
106        requestSynchronizeStackViewsWithModel(0);
107    }
108    void requestSynchronizeStackViewsWithModel(int duration) {
109        Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
110                "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow);
111        if (!mStackViewsDirty) {
112            invalidate();
113        }
114        if (mAwaitingFirstLayout) {
115            // Skip the animation if we are awaiting first layout
116            mStackViewsAnimationDuration = 0;
117        } else {
118            mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
119        }
120        mStackViewsDirty = true;
121    }
122
123    // XXX: Optimization: Use a mapping of Task -> View
124    private TaskView getChildViewForTask(Task t) {
125        int childCount = getChildCount();
126        for (int i = 0; i < childCount; i++) {
127            TaskView tv = (TaskView) getChildAt(i);
128            if (tv.getTask() == t) {
129                return tv;
130            }
131        }
132        return null;
133    }
134
135    /** Update/get the transform */
136    public TaskViewTransform getStackTransform(int indexInStack, int stackScroll) {
137        TaskViewTransform transform = new TaskViewTransform();
138
139        // Return early if we have an invalid index
140        if (indexInStack < 0) return transform;
141
142        // Map the items to an continuous position relative to the specified scroll
143        int numPeekCards = Constants.Values.TaskStackView.StackPeekNumCards;
144        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
145        float peekHeight = Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
146        float t = ((indexInStack * overlapHeight) - stackScroll) / overlapHeight;
147        float boundedT = Math.max(t, -(numPeekCards + 1));
148
149        // Set the scale relative to its position
150        float minScale = Constants.Values.TaskStackView.StackPeekMinScale;
151        float scaleRange = 1f - minScale;
152        float scaleInc = scaleRange / numPeekCards;
153        float scale = Math.max(minScale, Math.min(1f, 1f + (boundedT * scaleInc)));
154        float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2;
155        transform.scale = scale;
156
157        // Set the translation
158        if (boundedT < 0f) {
159            transform.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
160                    numPeekCards) * peekHeight - scaleYOffset);
161        } else {
162            transform.translationY = (int) (boundedT * overlapHeight - scaleYOffset);
163        }
164
165        // Update the rect and visibility
166        transform.rect.set(mTaskRect);
167        if (t < -(numPeekCards + 1)) {
168            transform.visible = false;
169        } else {
170            transform.rect.offset(0, transform.translationY);
171            Utilities.scaleRectAboutCenter(transform.rect, scale);
172            transform.visible = Rect.intersects(mRect, transform.rect);
173        }
174        transform.t = t;
175        return transform;
176    }
177
178    /**
179     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
180     */
181    private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
182                                                            int stackScroll,
183                                                            int[] visibleRangeOut) {
184        // XXX: Optimization: Use binary search to find the visible range
185
186        ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
187        int taskCount = tasks.size();
188        int firstVisibleIndex = -1;
189        int lastVisibleIndex = -1;
190        for (int i = 0; i < taskCount; i++) {
191            TaskViewTransform transform = getStackTransform(i, stackScroll);
192            taskTransforms.add(transform);
193            if (transform.visible) {
194                if (firstVisibleIndex < 0) {
195                    firstVisibleIndex = i;
196                }
197                lastVisibleIndex = i;
198            }
199        }
200        if (visibleRangeOut != null) {
201            visibleRangeOut[0] = firstVisibleIndex;
202            visibleRangeOut[1] = lastVisibleIndex;
203        }
204        return taskTransforms;
205    }
206
207    /** Synchronizes the views with the model */
208    void synchronizeStackViewsWithModel() {
209        Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
210                "[TaskStackView|synchronizeViewsWithModel]",
211                "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow);
212        if (mStackViewsDirty) {
213            // XXX: Consider using TaskViewTransform pool to prevent allocations
214            // XXX: Iterate children views, update transforms and remove all that are not visible
215            //      For all remaining tasks, update transforms and if visible add the view
216
217            // Get all the task transforms
218            int[] visibleRange = mTmpVisibleRange;
219            int stackScroll = getStackScroll();
220            ArrayList<Task> tasks = mStack.getTasks();
221            ArrayList<TaskViewTransform> taskTransforms = getStackTransforms(tasks, stackScroll,
222                    visibleRange);
223
224            // Update the visible state of all the tasks
225            int taskCount = tasks.size();
226            for (int i = 0; i < taskCount; i++) {
227                Task task = tasks.get(i);
228                TaskViewTransform transform = taskTransforms.get(i);
229                TaskView tv = getChildViewForTask(task);
230
231                if (transform.visible) {
232                    if (tv == null) {
233                        tv = mViewPool.pickUpViewFromPool(task, task);
234                        // When we are picking up a new view from the view pool, prepare it for any
235                        // following animation by putting it in a reasonable place
236                        if (mStackViewsAnimationDuration > 0 && i != 0) {
237                            int fromIndex = (transform.t < 0) ? (visibleRange[0] - 1) :
238                                    (visibleRange[1] + 1);
239                            tv.updateViewPropertiesToTaskTransform(null,
240                                    getStackTransform(fromIndex, stackScroll), 0);
241                        }
242                    }
243                } else {
244                    if (tv != null) {
245                        mViewPool.returnViewToPool(tv);
246                    }
247                }
248            }
249
250            // Update all the remaining view children
251            // NOTE: We have to iterate in reverse where because we are removing views directly
252            int childCount = getChildCount();
253            for (int i = childCount - 1; i >= 0; i--) {
254                TaskView tv = (TaskView) getChildAt(i);
255                Task task = tv.getTask();
256                int taskIndex = mStack.indexOfTask(task);
257                if (taskIndex < 0 || !taskTransforms.get(taskIndex).visible) {
258                    mViewPool.returnViewToPool(tv);
259                } else {
260                    tv.updateViewPropertiesToTaskTransform(null, taskTransforms.get(taskIndex),
261                            mStackViewsAnimationDuration);
262                }
263            }
264
265            Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
266                    "  [TaskStackView|viewChildren]", "" + getChildCount());
267
268            mStackViewsAnimationDuration = 0;
269            mStackViewsDirty = false;
270        }
271    }
272
273    /** Sets the current stack scroll */
274    public void setStackScroll(int value) {
275        mStackScroll = value;
276        requestSynchronizeStackViewsWithModel();
277    }
278    /** Sets the current stack scroll without synchronizing the stack view with the model */
279    public void setStackScrollRaw(int value) {
280        mStackScroll = value;
281    }
282
283    /** Gets the current stack scroll */
284    public int getStackScroll() {
285        return mStackScroll;
286    }
287
288    /** Animates the stack scroll into bounds */
289    ObjectAnimator animateBoundScroll(int duration) {
290        int curScroll = getStackScroll();
291        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
292        if (newScroll != curScroll) {
293            // Enable hw layers on the stack
294            addHwLayersRefCount("animateBoundScroll");
295
296            // Abort any current animations
297            abortScroller();
298            abortBoundScrollAnimation();
299
300            // Start a new scroll animation
301            animateScroll(curScroll, newScroll, duration);
302            mScrollAnimator.start();
303        }
304        return mScrollAnimator;
305    }
306
307    /** Animates the stack scroll */
308    void animateScroll(int curScroll, int newScroll, int duration) {
309        mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
310        mScrollAnimator.setDuration(duration);
311        mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
312            @Override
313            public void onAnimationUpdate(ValueAnimator animation) {
314                setStackScroll((Integer) animation.getAnimatedValue());
315            }
316        });
317        mScrollAnimator.addListener(new AnimatorListenerAdapter() {
318            @Override
319            public void onAnimationEnd(Animator animation) {
320                // Disable hw layers on the stack
321                decHwLayersRefCount("animateBoundScroll");
322            }
323        });
324    }
325
326    /** Aborts any current stack scrolls */
327    void abortBoundScrollAnimation() {
328        if (mScrollAnimator != null) {
329            mScrollAnimator.cancel();
330            mScrollAnimator.removeAllListeners();
331        }
332    }
333
334    void abortScroller() {
335        if (!mScroller.isFinished()) {
336            // Abort the scroller
337            mScroller.abortAnimation();
338            // And disable hw layers on the stack
339            decHwLayersRefCount("flingScroll");
340        }
341    }
342
343    /** Bounds the current scroll if necessary */
344    public boolean boundScroll() {
345        int curScroll = getStackScroll();
346        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
347        if (newScroll != curScroll) {
348            setStackScroll(newScroll);
349            return true;
350        }
351        return false;
352    }
353
354    /**
355     * Bounds the current scroll if necessary, but does not synchronize the stack view with the
356     * model.
357     */
358    public boolean boundScrollRaw() {
359        int curScroll = getStackScroll();
360        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
361        if (newScroll != curScroll) {
362            setStackScrollRaw(newScroll);
363            return true;
364        }
365        return false;
366    }
367
368    /** Returns whether the current scroll is out of bounds */
369    boolean isScrollOutOfBounds() {
370        return (getStackScroll() < 0) || (getStackScroll() > mMaxScroll);
371    }
372
373    /** Updates the min and max virtual scroll bounds */
374    void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
375        // Compute the min and max scroll values
376        int numTasks = Math.max(1, mStack.getTaskCount());
377        int taskHeight = mTaskRect.height();
378        int stackHeight = mStackRectSansPeek.height();
379        int maxScrollHeight = taskHeight + (int) ((numTasks - 1) *
380                Constants.Values.TaskStackView.StackOverlapPct * taskHeight);
381        mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
382        mMaxScroll = maxScrollHeight - stackHeight;
383
384        // Debug logging
385        if (Constants.DebugFlags.UI.MeasureAndLayout) {
386            Console.log("  [TaskStack|minScroll] " + mMinScroll);
387            Console.log("  [TaskStack|maxScroll] " + mMaxScroll);
388        }
389
390        if (boundScrollToNewMinMax) {
391            boundScroll();
392        }
393    }
394
395    /** Enables the hw layers and increments the hw layer requirement ref count */
396    void addHwLayersRefCount(String reason) {
397        Console.log(Constants.DebugFlags.UI.HwLayers,
398                "[TaskStackView|addHwLayersRefCount] refCount: " +
399                        mHwLayersRefCount + "->" + (mHwLayersRefCount + 1) + " " + reason);
400        if (mHwLayersRefCount == 0) {
401            // Enable hw layers on each of the children
402            int childCount = getChildCount();
403            for (int i = 0; i < childCount; i++) {
404                TaskView tv = (TaskView) getChildAt(i);
405                tv.enableHwLayers();
406            }
407        }
408        mHwLayersRefCount++;
409    }
410
411    /** Decrements the hw layer requirement ref count and disables the hw layers when we don't
412        need them anymore. */
413    void decHwLayersRefCount(String reason) {
414        Console.log(Constants.DebugFlags.UI.HwLayers,
415                "[TaskStackView|decHwLayersRefCount] refCount: " +
416                        mHwLayersRefCount + "->" + (mHwLayersRefCount - 1) + " " + reason);
417        mHwLayersRefCount--;
418        if (mHwLayersRefCount == 0) {
419            // Disable hw layers on each of the children
420            int childCount = getChildCount();
421            for (int i = 0; i < childCount; i++) {
422                TaskView tv = (TaskView) getChildAt(i);
423                tv.disableHwLayers();
424            }
425        } else if (mHwLayersRefCount < 0) {
426            new Throwable("Invalid hw layers ref count").printStackTrace();
427            Console.logError(getContext(), "Invalid HW layers ref count");
428        }
429    }
430
431    @Override
432    public void computeScroll() {
433        if (mScroller.computeScrollOffset()) {
434            setStackScroll(mScroller.getCurrY());
435            invalidate();
436
437            // If we just finished scrolling, then disable the hw layers
438            if (mScroller.isFinished()) {
439                decHwLayersRefCount("finishedFlingScroll");
440            }
441        }
442    }
443
444    @Override
445    public boolean onInterceptTouchEvent(MotionEvent ev) {
446        return mTouchHandler.onInterceptTouchEvent(ev);
447    }
448
449    @Override
450    public boolean onTouchEvent(MotionEvent ev) {
451        return mTouchHandler.onTouchEvent(ev);
452    }
453
454    @Override
455    public void dispatchDraw(Canvas canvas) {
456        Console.log(Constants.DebugFlags.UI.Draw, "[TaskStackView|dispatchDraw]", "",
457                Console.AnsiPurple);
458        synchronizeStackViewsWithModel();
459        super.dispatchDraw(canvas);
460    }
461
462    @Override
463    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
464        if (Constants.DebugFlags.App.EnableTaskStackClipping) {
465            TaskView tv = (TaskView) child;
466            TaskView nextTv = null;
467            int curIndex = indexOfChild(tv);
468            if ((curIndex > -1) && (curIndex < (getChildCount() - 1))) {
469                // Clip against the next view (if we aren't animating its alpha)
470                nextTv = (TaskView) getChildAt(curIndex + 1);
471                if (nextTv.getAlpha() == 1f) {
472                    Rect curRect = tv.getClippingRect(mTmpRect, false);
473                    Rect nextRect = nextTv.getClippingRect(mTmpRect2, true);
474                    RecentsConfiguration config = RecentsConfiguration.getInstance();
475                    // The hit rects are relative to the task view, which needs to be offset by the
476                    // system bar height
477                    curRect.offset(0, config.systemInsets.top);
478                    nextRect.offset(0, config.systemInsets.top);
479                    // Compute the clip region
480                    Region clipRegion = new Region();
481                    clipRegion.op(curRect, Region.Op.UNION);
482                    clipRegion.op(nextRect, Region.Op.DIFFERENCE);
483                    // Clip the canvas
484                    int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
485                    canvas.clipRegion(clipRegion);
486                    boolean invalidate = super.drawChild(canvas, child, drawingTime);
487                    canvas.restoreToCount(saveCount);
488                    return invalidate;
489                }
490            }
491        }
492        return super.drawChild(canvas, child, drawingTime);
493    }
494
495    /** Computes the stack and task rects */
496    public void computeRects(int width, int height, int insetBottom) {
497        // Note: We let the stack view be the full height because we want the cards to go under the
498        //       navigation bar if possible.  However, the stack rects which we use to calculate
499        //       max scroll, etc. need to take the nav bar into account
500
501        // Compute the stack rects
502        mRect.set(0, 0, width, height);
503        mStackRect.set(mRect);
504        mStackRect.bottom -= insetBottom;
505
506        int smallestDimension = Math.min(width, height);
507        int padding = (int) (Constants.Values.TaskStackView.StackPaddingPct * smallestDimension / 2f);
508        mStackRect.inset(padding, padding);
509        mStackRectSansPeek.set(mStackRect);
510        mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
511
512        // Compute the task rect
513        int minHeight = (int) (mStackRect.height() -
514                (Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height()));
515        int size = Math.min(minHeight, Math.min(mStackRect.width(), mStackRect.height()));
516        int centerX = mStackRect.centerX();
517        mTaskRect.set(centerX - size / 2, mStackRectSansPeek.top,
518                centerX + size / 2, mStackRectSansPeek.top + size);
519
520        // Update the scroll bounds
521        updateMinMaxScroll(false);
522    }
523
524    @Override
525    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
526        int width = MeasureSpec.getSize(widthMeasureSpec);
527        int height = MeasureSpec.getSize(heightMeasureSpec);
528        Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[TaskStackView|measure]",
529                "width: " + width + " height: " + height +
530                " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen);
531
532        // Compute our stack/task rects
533        RecentsConfiguration config = RecentsConfiguration.getInstance();
534        computeRects(width, height, config.systemInsets.bottom);
535
536        // Debug logging
537        if (Constants.DebugFlags.UI.MeasureAndLayout) {
538            Console.log("  [TaskStack|fullRect] " + mRect);
539            Console.log("  [TaskStack|stackRect] " + mStackRect);
540            Console.log("  [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
541            Console.log("  [TaskStack|taskRect] " + mTaskRect);
542        }
543
544        // If this is the first layout, then scroll to the front of the stack and synchronize the
545        // stack views immediately
546        if (mAwaitingFirstLayout) {
547            setStackScroll(mMaxScroll);
548            requestSynchronizeStackViewsWithModel();
549            synchronizeStackViewsWithModel();
550
551            // Animate the icon of the first task view
552            if (Constants.Values.TaskView.AnimateFrontTaskIconOnEnterRecents) {
553                TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
554                if (tv != null) {
555                    tv.animateOnEnterRecents();
556                }
557            }
558        }
559
560        // Measure each of the children
561        int childCount = getChildCount();
562        for (int i = 0; i < childCount; i++) {
563            TaskView t = (TaskView) getChildAt(i);
564            t.measure(MeasureSpec.makeMeasureSpec(mTaskRect.width(), MeasureSpec.EXACTLY),
565                    MeasureSpec.makeMeasureSpec(mTaskRect.height(), MeasureSpec.EXACTLY));
566        }
567
568        setMeasuredDimension(width, height);
569    }
570
571    @Override
572    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
573        Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[TaskStackView|layout]",
574                "" + new Rect(left, top, right, bottom), Console.AnsiGreen);
575
576        // Debug logging
577        if (Constants.DebugFlags.UI.MeasureAndLayout) {
578            Console.log("  [TaskStack|fullRect] " + mRect);
579            Console.log("  [TaskStack|stackRect] " + mStackRect);
580            Console.log("  [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
581            Console.log("  [TaskStack|taskRect] " + mTaskRect);
582        }
583
584        // Layout each of the children
585        int childCount = getChildCount();
586        for (int i = 0; i < childCount; i++) {
587            TaskView t = (TaskView) getChildAt(i);
588            t.layout(mTaskRect.left, mStackRectSansPeek.top,
589                    mTaskRect.right, mStackRectSansPeek.top + mTaskRect.height());
590        }
591
592        if (mAwaitingFirstLayout) {
593            mAwaitingFirstLayout = false;
594        }
595    }
596
597    @Override
598    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
599        super.onScrollChanged(l, t, oldl, oldt);
600        requestSynchronizeStackViewsWithModel();
601    }
602
603    public boolean isTransformedTouchPointInView(float x, float y, View child) {
604        return isTransformedTouchPointInView(x, y, child, null);
605    }
606
607    /**** TaskStackCallbacks Implementation ****/
608
609    @Override
610    public void onStackTaskAdded(TaskStack stack, Task t) {
611        requestSynchronizeStackViewsWithModel();
612    }
613
614    @Override
615    public void onStackTaskRemoved(TaskStack stack, Task t) {
616        // Remove the view associated with this task, we can't rely on updateTransforms
617        // to work here because the task is no longer in the list
618        int childCount = getChildCount();
619        for (int i = childCount - 1; i >= 0; i--) {
620            TaskView tv = (TaskView) getChildAt(i);
621            if (tv.getTask() == t) {
622                mViewPool.returnViewToPool(tv);
623                break;
624            }
625        }
626
627        updateMinMaxScroll(true);
628        requestSynchronizeStackViewsWithModel(Constants.Values.TaskStackView.Animation.TaskRemovedReshuffleDuration);
629    }
630
631    @Override
632    public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curStack,
633                                Task filteredTask) {
634        // NOTE: This code assumes that the current (unfiltered) stack is a superset of the new
635        // (filtered) stack
636        // XXX: Use HW Layers
637
638        // Stash the scroll for us to restore to when we unfilter
639        mStashedScroll = getStackScroll();
640
641        // Compute the transforms of the items in the current stack
642        final ArrayList<TaskViewTransform> curTaskTransforms =
643                getStackTransforms(curStack, mStashedScroll, null);
644
645        // Bound the new stack scroll
646        updateMinMaxScroll(false);
647        boundScrollRaw();
648
649        // Compute the transforms of the items in the new stack
650        final ArrayList<TaskViewTransform> taskTransforms =
651                getStackTransforms(mStack.getTasks(), getStackScroll(), null);
652
653        // Animate all of the existing views on screen either out of view (if they are not visible
654        // in the new stack) or to their final positions in the new stack
655        final ArrayList<TaskView> childrenToReturnToPool = new ArrayList<TaskView>();
656        final ArrayList<Task> tasks = mStack.getTasks();
657        ArrayList<Animator> childViewAnims = new ArrayList<Animator>();
658        int childCount = getChildCount();
659        for (int i = 0; i < childCount; i++) {
660            TaskView tv = (TaskView) getChildAt(i);
661            Task task = tv.getTask();
662            TaskViewTransform toTransform;
663            int taskIndex = tasks.indexOf(task);
664            if ((taskIndex < 0) || !taskTransforms.get(taskIndex).visible) {
665                // Compose a new transform that animates the task view out of view
666                TaskViewTransform fromTransform = curTaskTransforms.get(curStack.indexOf(task));
667                toTransform = new TaskViewTransform(fromTransform);
668                tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0);
669                tv.prepareTaskTransformForFilterTaskHidden(toTransform);
670
671                childrenToReturnToPool.add(tv);
672            } else {
673                toTransform = taskTransforms.get(taskIndex);
674            }
675            childViewAnims.add(tv.getAnimatorToTaskTransform(toTransform));
676        }
677
678        AnimatorSet childViewAnimSet = new AnimatorSet();
679        childViewAnimSet.setDuration(
680                Constants.Values.TaskStackView.Animation.FilteredCurrentViewsDuration);
681        childViewAnimSet.addListener(new AnimatorListenerAdapter() {
682            @Override
683            public void onAnimationEnd(Animator animation) {
684                // Return all the removed children to the view pool
685                for (TaskView tv : childrenToReturnToPool) {
686                    mViewPool.returnViewToPool(tv);
687                }
688
689                // For views that are not already visible, animate them in
690                int taskCount = tasks.size();
691                for (int i = 0; i < taskCount; i++) {
692                    Task task = tasks.get(i);
693                    TaskViewTransform toTransform = taskTransforms.get(i);
694                    if (toTransform.visible) {
695                        TaskViewTransform fromTransform =
696                                curTaskTransforms.get(curStack.indexOf(task));
697                        TaskView tv = getChildViewForTask(task);
698                        if (tv == null) {
699                            tv = mViewPool.pickUpViewFromPool(task, task);
700
701                            // Animate from the current position to the new position
702                            tv.prepareTaskTransformForFilterTaskVisible(fromTransform);
703                            tv.updateViewPropertiesToTaskTransform(fromTransform,
704                                    toTransform,
705                                    Constants.Values.TaskStackView.Animation.FilteredNewViewsDuration);
706                        }
707                    }
708                }
709                invalidate();
710            }
711        });
712        childViewAnimSet.playTogether(childViewAnims);
713        childViewAnimSet.start();
714    }
715
716    @Override
717    public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curStack) {
718        // Compute the transforms of the items in the current stack
719        final int curScroll = getStackScroll();
720        final ArrayList<TaskViewTransform> curTaskTransforms =
721                getStackTransforms(curStack, curScroll, null);
722
723        // Restore the stashed scroll
724        updateMinMaxScroll(false);
725        setStackScrollRaw(mStashedScroll);
726        boundScrollRaw();
727
728        // Compute the transforms of the items in the new stack
729        final ArrayList<TaskViewTransform> taskTransforms =
730                getStackTransforms(mStack.getTasks(), getStackScroll(), null);
731
732        // Animate all of the existing views out of view (if they are not in the visible range in
733        // the new stack) or to their final positions in the new stack
734        final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
735        final ArrayList<Task> tasks = mStack.getTasks();
736        ArrayList<Animator> childViewAnims = new ArrayList<Animator>();
737        int childCount = getChildCount();
738        for (int i = 0; i < childCount; i++) {
739            TaskView tv = (TaskView) getChildAt(i);
740            Task task = tv.getTask();
741            int taskIndex = tasks.indexOf(task);
742            TaskViewTransform transform;
743
744            // If the view is no longer visible, then we should just animate it out
745            if (taskIndex < 0 || !taskTransforms.get(taskIndex).visible) {
746                transform = new TaskViewTransform(curTaskTransforms.get(curStack.indexOf(task)));
747                tv.prepareTaskTransformForFilterTaskVisible(transform);
748                childrenToRemove.add(tv);
749            } else {
750                transform = taskTransforms.get(taskIndex);
751            }
752            childViewAnims.add(tv.getAnimatorToTaskTransform(transform));
753        }
754
755        AnimatorSet childViewAnimSet = new AnimatorSet();
756        childViewAnimSet.setDuration(
757                Constants.Values.TaskStackView.Animation.UnfilteredCurrentViewsDuration);
758        childViewAnimSet.addListener(new AnimatorListenerAdapter() {
759            @Override
760            public void onAnimationEnd(Animator animation) {
761                // Return all the removed children to the view pool
762                for (TaskView tv : childrenToRemove) {
763                    mViewPool.returnViewToPool(tv);
764                }
765
766                // Increment the hw layers ref count
767                addHwLayersRefCount("unfilteredNewViews");
768
769                // For views that are not already visible, animate them in
770                ArrayList<Animator> newViewAnims = new ArrayList<Animator>();
771                AnimatorSet newViewAnimSet = new AnimatorSet();
772                int taskCount = tasks.size();
773                int offset = 0;
774                for (int i = 0; i < taskCount; i++) {
775                    Task task = tasks.get(i);
776                    TaskViewTransform toTransform = taskTransforms.get(i);
777                    if (toTransform.visible) {
778                        TaskView tv = getChildViewForTask(task);
779                        if (tv == null) {
780                            // For views that are not already visible, animate them in
781                            tv = mViewPool.pickUpViewFromPool(task, task);
782
783                            // Animate in this new view
784                            TaskViewTransform fromTransform = new TaskViewTransform(toTransform);
785                            tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
786                            tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0);
787                            newViewAnims.add(tv.getAnimatorToTaskTransform(toTransform));
788                            offset++;
789                        }
790                    }
791                }
792
793                // Run the animation
794                newViewAnimSet.setDuration(
795                        Constants.Values.TaskStackView.Animation.UnfilteredNewViewsDuration);
796                newViewAnimSet.playTogether(newViewAnims);
797                newViewAnimSet.addListener(new AnimatorListenerAdapter() {
798                    @Override
799                    public void onAnimationEnd(Animator animation) {
800                        // Decrement the hw layers ref count
801                        decHwLayersRefCount("unfilteredNewViews");
802                    }
803                });
804                newViewAnimSet.start();
805
806                invalidate();
807            }
808        });
809        childViewAnimSet.playTogether(childViewAnims);
810        childViewAnimSet.start();
811    }
812
813    /**** ViewPoolConsumer Implementation ****/
814
815    @Override
816    public TaskView createView(Context context) {
817        Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]");
818        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
819    }
820
821    @Override
822    public void prepareViewToEnterPool(TaskView tv) {
823        Task task = tv.getTask();
824        tv.resetViewProperties();
825        Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]",
826                tv.getTask() + " tv: " + tv);
827
828        // Report that this tasks's data is no longer being used
829        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
830        loader.unloadTaskData(task);
831
832        // Detach the view from the hierarchy
833        detachViewFromParent(tv);
834
835        // Disable hw layers on this view
836        tv.disableHwLayers();
837    }
838
839    @Override
840    public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) {
841        Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]",
842                "isNewView: " + isNewView);
843
844        // Setup and attach the view to the window
845        Task task = prepareData;
846        // We try and rebind the task (this MUST be done before the task filled)
847        tv.onTaskBound(task);
848        // Request that this tasks's data be filled
849        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
850        loader.loadTaskData(task);
851
852        // Find the index where this task should be placed in the children
853        int insertIndex = -1;
854        int childCount = getChildCount();
855        for (int i = 0; i < childCount; i++) {
856            Task tvTask = ((TaskView) getChildAt(i)).getTask();
857            if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) {
858                insertIndex = i;
859                break;
860            }
861        }
862
863        // Add/attach the view to the hierarchy
864        Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "  [TaskStackView|insertIndex]",
865                "" + insertIndex);
866        if (isNewView) {
867            addView(tv, insertIndex);
868
869            // Set the callbacks and listeners for this new view
870            tv.setOnClickListener(this);
871            tv.setCallbacks(this);
872        } else {
873            attachViewToParent(tv, insertIndex, tv.getLayoutParams());
874        }
875
876        // Enable hw layers on this view if hw layers are enabled on the stack
877        if (mHwLayersRefCount > 0) {
878            tv.enableHwLayers();
879        }
880    }
881
882    @Override
883    public boolean hasPreferredData(TaskView tv, Task preferredData) {
884        return (tv.getTask() == preferredData);
885    }
886
887    /**** TaskViewCallbacks Implementation ****/
888
889    @Override
890    public void onTaskIconClicked(TaskView tv) {
891        Console.log(Constants.DebugFlags.UI.ClickEvents, "[TaskStack|Clicked|Icon]",
892                tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(),
893                Console.AnsiCyan);
894        if (Constants.DebugFlags.App.EnableTaskFiltering) {
895            if (mStack.hasFilteredTasks()) {
896                mStack.unfilterTasks();
897            } else {
898                mStack.filterTasks(tv.getTask());
899            }
900        } else {
901            Console.logError(getContext(), "Task Filtering TBD");
902        }
903    }
904
905    /**** View.OnClickListener Implementation ****/
906
907    @Override
908    public void onClick(View v) {
909        TaskView tv = (TaskView) v;
910        Task task = tv.getTask();
911        Console.log(Constants.DebugFlags.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
912                task + " cb: " + mCb);
913
914        if (mCb != null) {
915            mCb.onTaskLaunched(this, tv, mStack, task);
916        }
917    }
918}
919
920/* Handles touch events */
921class TaskStackViewTouchHandler implements SwipeHelper.Callback {
922    static int INACTIVE_POINTER_ID = -1;
923
924    TaskStackView mSv;
925    VelocityTracker mVelocityTracker;
926
927    boolean mIsScrolling;
928
929    int mInitialMotionX, mInitialMotionY;
930    int mLastMotionX, mLastMotionY;
931    int mActivePointerId = INACTIVE_POINTER_ID;
932    TaskView mActiveTaskView = null;
933
934    int mTotalScrollMotion;
935    int mMinimumVelocity;
936    int mMaximumVelocity;
937    // The scroll touch slop is used to calculate when we start scrolling
938    int mScrollTouchSlop;
939    // The page touch slop is used to calculate when we start swiping
940    float mPagingTouchSlop;
941
942    SwipeHelper mSwipeHelper;
943    boolean mInterceptedBySwipeHelper;
944
945    public TaskStackViewTouchHandler(Context context, TaskStackView sv) {
946        ViewConfiguration configuration = ViewConfiguration.get(context);
947        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
948        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
949        mScrollTouchSlop = configuration.getScaledTouchSlop();
950        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
951        mSv = sv;
952
953
954        float densityScale = context.getResources().getDisplayMetrics().density;
955        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop);
956        mSwipeHelper.setMinAlpha(1f);
957    }
958
959    /** Velocity tracker helpers */
960    void initOrResetVelocityTracker() {
961        if (mVelocityTracker == null) {
962            mVelocityTracker = VelocityTracker.obtain();
963        } else {
964            mVelocityTracker.clear();
965        }
966    }
967    void initVelocityTrackerIfNotExists() {
968        if (mVelocityTracker == null) {
969            mVelocityTracker = VelocityTracker.obtain();
970        }
971    }
972    void recycleVelocityTracker() {
973        if (mVelocityTracker != null) {
974            mVelocityTracker.recycle();
975            mVelocityTracker = null;
976        }
977    }
978
979    /** Returns the view at the specified coordinates */
980    TaskView findViewAtPoint(int x, int y) {
981        int childCount = mSv.getChildCount();
982        for (int i = childCount - 1; i >= 0; i--) {
983            TaskView tv = (TaskView) mSv.getChildAt(i);
984            if (tv.getVisibility() == View.VISIBLE) {
985                if (mSv.isTransformedTouchPointInView(x, y, tv)) {
986                    return tv;
987                }
988            }
989        }
990        return null;
991    }
992
993    /** Touch preprocessing for handling below */
994    public boolean onInterceptTouchEvent(MotionEvent ev) {
995        Console.log(Constants.DebugFlags.UI.TouchEvents,
996                "[TaskStackViewTouchHandler|interceptTouchEvent]",
997                Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
998
999        // Return early if we have no children
1000        boolean hasChildren = (mSv.getChildCount() > 0);
1001        if (!hasChildren) {
1002            return false;
1003        }
1004
1005        // Pass through to swipe helper if we are swiping
1006        mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
1007        if (mInterceptedBySwipeHelper) {
1008            return true;
1009        }
1010
1011        boolean wasScrolling = !mSv.mScroller.isFinished() ||
1012                (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning());
1013        int action = ev.getAction();
1014        switch (action & MotionEvent.ACTION_MASK) {
1015            case MotionEvent.ACTION_DOWN: {
1016                // Save the touch down info
1017                mInitialMotionX = mLastMotionX = (int) ev.getX();
1018                mInitialMotionY = mLastMotionY = (int) ev.getY();
1019                mActivePointerId = ev.getPointerId(0);
1020                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
1021                // Stop the current scroll if it is still flinging
1022                mSv.abortScroller();
1023                mSv.abortBoundScrollAnimation();
1024                // Initialize the velocity tracker
1025                initOrResetVelocityTracker();
1026                mVelocityTracker.addMovement(ev);
1027                // Check if the scroller is finished yet
1028                mIsScrolling = !mSv.mScroller.isFinished();
1029                break;
1030            }
1031            case MotionEvent.ACTION_MOVE: {
1032                if (mActivePointerId == INACTIVE_POINTER_ID) break;
1033
1034                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1035                int y = (int) ev.getY(activePointerIndex);
1036                int x = (int) ev.getX(activePointerIndex);
1037                if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
1038                    // Save the touch move info
1039                    mIsScrolling = true;
1040                    // Initialize the velocity tracker if necessary
1041                    initVelocityTrackerIfNotExists();
1042                    mVelocityTracker.addMovement(ev);
1043                    // Disallow parents from intercepting touch events
1044                    final ViewParent parent = mSv.getParent();
1045                    if (parent != null) {
1046                        parent.requestDisallowInterceptTouchEvent(true);
1047                    }
1048                    // Enable HW layers
1049                    mSv.addHwLayersRefCount("stackScroll");
1050                }
1051
1052                mLastMotionX = x;
1053                mLastMotionY = y;
1054                break;
1055            }
1056            case MotionEvent.ACTION_CANCEL:
1057            case MotionEvent.ACTION_UP: {
1058                // Animate the scroll back if we've cancelled
1059                mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration);
1060                // Disable HW layers
1061                if (mIsScrolling) {
1062                    mSv.decHwLayersRefCount("stackScroll");
1063                }
1064                // Reset the drag state and the velocity tracker
1065                mIsScrolling = false;
1066                mActivePointerId = INACTIVE_POINTER_ID;
1067                mActiveTaskView = null;
1068                mTotalScrollMotion = 0;
1069                recycleVelocityTracker();
1070                break;
1071            }
1072        }
1073
1074        return wasScrolling || mIsScrolling;
1075    }
1076
1077    /** Handles touch events once we have intercepted them */
1078    public boolean onTouchEvent(MotionEvent ev) {
1079        Console.log(Constants.DebugFlags.UI.TouchEvents,
1080                "[TaskStackViewTouchHandler|touchEvent]",
1081                Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
1082
1083        // Short circuit if we have no children
1084        boolean hasChildren = (mSv.getChildCount() > 0);
1085        if (!hasChildren) {
1086            return false;
1087        }
1088
1089        // Pass through to swipe helper if we are swiping
1090        if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
1091            return true;
1092        }
1093
1094        // Update the velocity tracker
1095        initVelocityTrackerIfNotExists();
1096        mVelocityTracker.addMovement(ev);
1097
1098        int action = ev.getAction();
1099        switch (action & MotionEvent.ACTION_MASK) {
1100            case MotionEvent.ACTION_DOWN: {
1101                // Save the touch down info
1102                mInitialMotionX = mLastMotionX = (int) ev.getX();
1103                mInitialMotionY = mLastMotionY = (int) ev.getY();
1104                mActivePointerId = ev.getPointerId(0);
1105                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
1106                // Stop the current scroll if it is still flinging
1107                mSv.abortScroller();
1108                mSv.abortBoundScrollAnimation();
1109                // Initialize the velocity tracker
1110                initOrResetVelocityTracker();
1111                mVelocityTracker.addMovement(ev);
1112                // Disallow parents from intercepting touch events
1113                final ViewParent parent = mSv.getParent();
1114                if (parent != null) {
1115                    parent.requestDisallowInterceptTouchEvent(true);
1116                }
1117                break;
1118            }
1119            case MotionEvent.ACTION_POINTER_DOWN: {
1120                final int index = ev.getActionIndex();
1121                mActivePointerId = ev.getPointerId(index);
1122                mLastMotionX = (int) ev.getX(index);
1123                mLastMotionY = (int) ev.getY(index);
1124                break;
1125            }
1126            case MotionEvent.ACTION_MOVE: {
1127                if (mActivePointerId == INACTIVE_POINTER_ID) break;
1128
1129                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1130                int x = (int) ev.getX(activePointerIndex);
1131                int y = (int) ev.getY(activePointerIndex);
1132                int deltaY = mLastMotionY - y;
1133                if (!mIsScrolling) {
1134                    if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
1135                        mIsScrolling = true;
1136                        // Initialize the velocity tracker
1137                        initOrResetVelocityTracker();
1138                        mVelocityTracker.addMovement(ev);
1139                        // Disallow parents from intercepting touch events
1140                        final ViewParent parent = mSv.getParent();
1141                        if (parent != null) {
1142                            parent.requestDisallowInterceptTouchEvent(true);
1143                        }
1144                        // Enable HW layers
1145                        mSv.addHwLayersRefCount("stackScroll");
1146                    }
1147                }
1148                if (mIsScrolling) {
1149                    mSv.setStackScroll(mSv.getStackScroll() + deltaY);
1150                    if (mSv.isScrollOutOfBounds()) {
1151                        mVelocityTracker.clear();
1152                    }
1153                }
1154                mLastMotionX = x;
1155                mLastMotionY = y;
1156                mTotalScrollMotion += Math.abs(deltaY);
1157                break;
1158            }
1159            case MotionEvent.ACTION_UP: {
1160                final VelocityTracker velocityTracker = mVelocityTracker;
1161                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1162                int velocity = (int) velocityTracker.getYVelocity(mActivePointerId);
1163
1164                if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
1165                    // Enable HW layers on the stack
1166                    mSv.addHwLayersRefCount("flingScroll");
1167                    int overscrollRange = (int) (Math.min(1f,
1168                            Math.abs((float) velocity / mMaximumVelocity)) *
1169                            Constants.Values.TaskStackView.TaskStackOverscrollRange);
1170
1171                    Console.log(Constants.DebugFlags.UI.TouchEvents,
1172                            "[TaskStackViewTouchHandler|fling]",
1173                            "scroll: " + mSv.getStackScroll() + " velocity: " + velocity +
1174                                    " maxVelocity: " + mMaximumVelocity +
1175                                    " overscrollRange: " + overscrollRange,
1176                            Console.AnsiGreen);
1177
1178                    // Fling scroll
1179                    mSv.mScroller.fling(0, mSv.getStackScroll(),
1180                            0, -velocity,
1181                            0, 0,
1182                            mSv.mMinScroll, mSv.mMaxScroll,
1183                            0, overscrollRange);
1184                    // Invalidate to kick off computeScroll
1185                    mSv.invalidate();
1186                } else if (mSv.isScrollOutOfBounds()) {
1187                    // Animate the scroll back into bounds
1188                    // XXX: Make this animation a function of the velocity OR distance
1189                    mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration);
1190                }
1191
1192                if (mIsScrolling) {
1193                    // Disable HW layers
1194                    mSv.decHwLayersRefCount("stackScroll");
1195                }
1196                mActivePointerId = INACTIVE_POINTER_ID;
1197                mIsScrolling = false;
1198                mTotalScrollMotion = 0;
1199                recycleVelocityTracker();
1200                break;
1201            }
1202            case MotionEvent.ACTION_POINTER_UP: {
1203                int pointerIndex = ev.getActionIndex();
1204                int pointerId = ev.getPointerId(pointerIndex);
1205                if (pointerId == mActivePointerId) {
1206                    // Select a new active pointer id and reset the motion state
1207                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
1208                    mActivePointerId = ev.getPointerId(newPointerIndex);
1209                    mLastMotionX = (int) ev.getX(newPointerIndex);
1210                    mLastMotionY = (int) ev.getY(newPointerIndex);
1211                    mVelocityTracker.clear();
1212                }
1213                break;
1214            }
1215            case MotionEvent.ACTION_CANCEL: {
1216                if (mIsScrolling) {
1217                    // Disable HW layers
1218                    mSv.decHwLayersRefCount("stackScroll");
1219                }
1220                if (mSv.isScrollOutOfBounds()) {
1221                    // Animate the scroll back into bounds
1222                    // XXX: Make this animation a function of the velocity OR distance
1223                    mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration);
1224                }
1225                mActivePointerId = INACTIVE_POINTER_ID;
1226                mIsScrolling = false;
1227                mTotalScrollMotion = 0;
1228                recycleVelocityTracker();
1229                break;
1230            }
1231        }
1232        return true;
1233    }
1234
1235    /**** SwipeHelper Implementation ****/
1236
1237    @Override
1238    public View getChildAtPosition(MotionEvent ev) {
1239        return findViewAtPoint((int) ev.getX(), (int) ev.getY());
1240    }
1241
1242    @Override
1243    public boolean canChildBeDismissed(View v) {
1244        return true;
1245    }
1246
1247    @Override
1248    public void onBeginDrag(View v) {
1249        // Enable HW layers
1250        mSv.addHwLayersRefCount("swipeBegin");
1251        // Disallow parents from intercepting touch events
1252        final ViewParent parent = mSv.getParent();
1253        if (parent != null) {
1254            parent.requestDisallowInterceptTouchEvent(true);
1255        }
1256    }
1257
1258    @Override
1259    public void onChildDismissed(View v) {
1260        TaskView tv = (TaskView) v;
1261        Task task = tv.getTask();
1262        Activity activity = (Activity) mSv.getContext();
1263
1264        // Remove the task from the view
1265        mSv.mStack.removeTask(task);
1266
1267        // Remove any stored data from the loader
1268        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
1269        loader.deleteTaskData(task);
1270
1271        // Remove the task from activity manager
1272        RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(tv.getTask().key.id);
1273
1274        // If there are no remaining tasks, then either unfilter the current stack, or just close
1275        // the activity if there are no filtered stacks
1276        if (mSv.mStack.getTaskCount() == 0) {
1277            boolean shouldFinishActivity = true;
1278            if (mSv.mStack.hasFilteredTasks()) {
1279                mSv.mStack.unfilterTasks();
1280                shouldFinishActivity = (mSv.mStack.getTaskCount() == 0);
1281            }
1282            if (shouldFinishActivity) {
1283                activity.finish();
1284            }
1285        }
1286
1287        // Disable HW layers
1288        mSv.decHwLayersRefCount("swipeComplete");
1289    }
1290
1291    @Override
1292    public void onSnapBackCompleted(View v) {
1293        // Do Nothing
1294    }
1295
1296    @Override
1297    public void onDragCancelled(View v) {
1298        // Disable HW layers
1299        mSv.decHwLayersRefCount("swipeCancelled");
1300    }
1301}
1302