TaskStackView.java revision a26fb7822ddf3511796279b847cc216bee9e7f70
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents.views;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.ValueAnimator;
23import android.app.Activity;
24import android.content.ComponentName;
25import android.content.Context;
26import android.graphics.Canvas;
27import android.graphics.Rect;
28import android.graphics.Region;
29import android.util.Pair;
30import android.view.LayoutInflater;
31import android.view.MotionEvent;
32import android.view.VelocityTracker;
33import android.view.View;
34import android.view.ViewConfiguration;
35import android.view.ViewParent;
36import android.widget.FrameLayout;
37import android.widget.OverScroller;
38import com.android.systemui.R;
39import com.android.systemui.recents.Console;
40import com.android.systemui.recents.Constants;
41import com.android.systemui.recents.DozeTrigger;
42import com.android.systemui.recents.RecentsConfiguration;
43import com.android.systemui.recents.RecentsPackageMonitor;
44import com.android.systemui.recents.RecentsTaskLoader;
45import com.android.systemui.recents.ReferenceCountedTrigger;
46import com.android.systemui.recents.Utilities;
47import com.android.systemui.recents.model.Task;
48import com.android.systemui.recents.model.TaskStack;
49
50import java.util.ArrayList;
51import java.util.HashMap;
52import java.util.Set;
53
54
55/* The visual representation of a task stack view */
56public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
57        TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
58        View.OnClickListener, RecentsPackageMonitor.PackageCallbacks {
59
60    /** The TaskView callbacks */
61    interface TaskStackViewCallbacks {
62        public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
63        public void onTaskAppInfoLaunched(Task t);
64        public void onTaskRemoved(Task t);
65    }
66
67    RecentsConfiguration mConfig;
68
69    TaskStack mStack;
70    TaskStackViewTouchHandler mTouchHandler;
71    TaskStackViewCallbacks mCb;
72    ViewPool<TaskView, Task> mViewPool;
73    ArrayList<TaskViewTransform> mTaskTransforms = new ArrayList<TaskViewTransform>();
74    DozeTrigger mDozeTrigger;
75
76    // The various rects that define the stack view
77    Rect mRect = new Rect();
78    Rect mStackRect = new Rect();
79    Rect mStackRectSansPeek = new Rect();
80    Rect mTaskRect = new Rect();
81
82    // The virtual stack scroll that we use for the card layout
83    int mStackScroll;
84    int mMinScroll;
85    int mMaxScroll;
86    int mStashedScroll;
87    int mFocusedTaskIndex = -1;
88    OverScroller mScroller;
89    ObjectAnimator mScrollAnimator;
90
91    // Optimizations
92    ReferenceCountedTrigger mHwLayersTrigger;
93    int mStackViewsAnimationDuration;
94    boolean mStackViewsDirty = true;
95    boolean mAwaitingFirstLayout = true;
96    boolean mStartEnterAnimationRequestedAfterLayout;
97    ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
98    int[] mTmpVisibleRange = new int[2];
99    Rect mTmpRect = new Rect();
100    Rect mTmpRect2 = new Rect();
101    LayoutInflater mInflater;
102
103    public TaskStackView(Context context, TaskStack stack) {
104        super(context);
105        mConfig = RecentsConfiguration.getInstance();
106        mStack = stack;
107        mStack.setCallbacks(this);
108        mScroller = new OverScroller(context);
109        mTouchHandler = new TaskStackViewTouchHandler(context, this);
110        mViewPool = new ViewPool<TaskView, Task>(context, this);
111        mInflater = LayoutInflater.from(context);
112        mDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {
113            @Override
114            public void run() {
115                // Show the task bar dismiss buttons
116                int childCount = getChildCount();
117                for (int i = 0; i < childCount; i++) {
118                    TaskView tv = (TaskView) getChildAt(i);
119                    tv.animateOnNoUserInteraction();
120                }
121            }
122        });
123        mHwLayersTrigger = new ReferenceCountedTrigger(getContext(), new Runnable() {
124            @Override
125            public void run() {
126                // Enable hw layers on each of the children
127                int childCount = getChildCount();
128                for (int i = 0; i < childCount; i++) {
129                    TaskView tv = (TaskView) getChildAt(i);
130                    tv.enableHwLayers();
131                }
132            }
133        }, new Runnable() {
134            @Override
135            public void run() {
136                // Disable hw layers on each of the children
137                int childCount = getChildCount();
138                for (int i = 0; i < childCount; i++) {
139                    TaskView tv = (TaskView) getChildAt(i);
140                    tv.disableHwLayers();
141                }
142            }
143        }, new Runnable() {
144            @Override
145            public void run() {
146                new Throwable("Invalid hw layers ref count").printStackTrace();
147                Console.logError(getContext(), "Invalid HW layers ref count");
148            }
149        });
150    }
151
152    /** Sets the callbacks */
153    void setCallbacks(TaskStackViewCallbacks cb) {
154        mCb = cb;
155    }
156
157    /** Requests that the views be synchronized with the model */
158    void requestSynchronizeStackViewsWithModel() {
159        requestSynchronizeStackViewsWithModel(0);
160    }
161    void requestSynchronizeStackViewsWithModel(int duration) {
162        if (Console.Enabled) {
163            Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
164                    "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow);
165        }
166        if (!mStackViewsDirty) {
167            invalidate(mStackRect);
168        }
169        if (mAwaitingFirstLayout) {
170            // Skip the animation if we are awaiting first layout
171            mStackViewsAnimationDuration = 0;
172        } else {
173            mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
174        }
175        mStackViewsDirty = true;
176    }
177
178    // XXX: Optimization: Use a mapping of Task -> View
179    private TaskView getChildViewForTask(Task t) {
180        int childCount = getChildCount();
181        for (int i = 0; i < childCount; i++) {
182            TaskView tv = (TaskView) getChildAt(i);
183            if (tv.getTask() == t) {
184                return tv;
185            }
186        }
187        return null;
188    }
189
190    /** Update/get the transform */
191    public TaskViewTransform getStackTransform(int indexInStack, int stackScroll) {
192        TaskViewTransform transform = new TaskViewTransform();
193
194        // Return early if we have an invalid index
195        if (indexInStack < 0) return transform;
196
197        // Map the items to an continuous position relative to the specified scroll
198        int numPeekCards = Constants.Values.TaskStackView.StackPeekNumCards;
199        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
200        float peekHeight = Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
201        float t = ((indexInStack * overlapHeight) - stackScroll) / overlapHeight;
202        float boundedT = Math.max(t, -(numPeekCards + 1));
203
204        // Set the scale relative to its position
205        int numFrontScaledCards = 3;
206        float minScale = Constants.Values.TaskStackView.StackPeekMinScale;
207        float scaleRange = 1f - minScale;
208        float scaleInc = scaleRange / (numPeekCards + numFrontScaledCards);
209        float scale = Math.max(minScale, Math.min(1f, minScale +
210            ((boundedT + (numPeekCards + 1)) * scaleInc)));
211        float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2;
212        transform.scale = scale;
213
214        // Set the y translation
215        if (boundedT < 0f) {
216            transform.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
217                    numPeekCards) * peekHeight - scaleYOffset);
218        } else {
219            transform.translationY = (int) (boundedT * overlapHeight - scaleYOffset);
220        }
221
222        // Set the z translation
223        int minZ = mConfig.taskViewTranslationZMinPx;
224        int incZ = mConfig.taskViewTranslationZIncrementPx;
225        transform.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ));
226
227        // Set the alphas
228        transform.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
229
230        // Update the rect and visibility
231        transform.rect.set(mTaskRect);
232        if (t < -(numPeekCards + 1)) {
233            transform.visible = false;
234        } else {
235            transform.rect.offset(0, transform.translationY);
236            Utilities.scaleRectAboutCenter(transform.rect, transform.scale);
237            transform.visible = Rect.intersects(mRect, transform.rect);
238        }
239        transform.t = t;
240        return transform;
241    }
242
243    /**
244     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
245     */
246    private void updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
247                                       ArrayList<Task> tasks,
248                                       int stackScroll,
249                                       int[] visibleRangeOut,
250                                       boolean boundTranslationsToRect) {
251        // XXX: Optimization: Use binary search to find the visible range
252
253        int taskCount = tasks.size();
254        int firstVisibleIndex = -1;
255        int lastVisibleIndex = -1;
256        taskTransforms.clear();
257        taskTransforms.ensureCapacity(taskCount);
258        for (int i = 0; i < taskCount; i++) {
259            TaskViewTransform transform = getStackTransform(i, stackScroll);
260            taskTransforms.add(transform);
261            if (transform.visible) {
262                if (firstVisibleIndex < 0) {
263                    firstVisibleIndex = i;
264                }
265                lastVisibleIndex = i;
266            }
267
268            if (boundTranslationsToRect) {
269                transform.translationY = Math.min(transform.translationY, mRect.bottom);
270            }
271        }
272        if (visibleRangeOut != null) {
273            visibleRangeOut[0] = firstVisibleIndex;
274            visibleRangeOut[1] = lastVisibleIndex;
275        }
276    }
277
278    /**
279     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This
280     * call is less optimal than calling updateStackTransforms directly.
281     */
282    private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
283                                                            int stackScroll,
284                                                            int[] visibleRangeOut,
285                                                            boolean boundTranslationsToRect) {
286        ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
287        updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut,
288                boundTranslationsToRect);
289        return taskTransforms;
290    }
291
292    /** Synchronizes the views with the model */
293    void synchronizeStackViewsWithModel() {
294        if (Console.Enabled) {
295            Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
296                    "[TaskStackView|synchronizeViewsWithModel]",
297                    "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow);
298        }
299        if (mStackViewsDirty) {
300            // XXX: Consider using TaskViewTransform pool to prevent allocations
301            // XXX: Iterate children views, update transforms and remove all that are not visible
302            //      For all remaining tasks, update transforms and if visible add the view
303
304            // Get all the task transforms
305            int[] visibleRange = mTmpVisibleRange;
306            int stackScroll = getStackScroll();
307            ArrayList<Task> tasks = mStack.getTasks();
308            updateStackTransforms(mTaskTransforms, tasks, stackScroll, visibleRange, false);
309
310            // Update the visible state of all the tasks
311            int taskCount = tasks.size();
312            for (int i = 0; i < taskCount; i++) {
313                Task task = tasks.get(i);
314                TaskViewTransform transform = mTaskTransforms.get(i);
315                TaskView tv = getChildViewForTask(task);
316
317                if (transform.visible) {
318                    if (tv == null) {
319                        tv = mViewPool.pickUpViewFromPool(task, task);
320                        // When we are picking up a new view from the view pool, prepare it for any
321                        // following animation by putting it in a reasonable place
322                        if (mStackViewsAnimationDuration > 0 && i != 0) {
323                            int fromIndex = (transform.t < 0) ? (visibleRange[0] - 1) :
324                                    (visibleRange[1] + 1);
325                            tv.updateViewPropertiesToTaskTransform(null,
326                                    getStackTransform(fromIndex, stackScroll), 0);
327                        }
328                    }
329                } else {
330                    if (tv != null) {
331                        mViewPool.returnViewToPool(tv);
332                    }
333                }
334            }
335
336            // Update all the remaining view children
337            // NOTE: We have to iterate in reverse where because we are removing views directly
338            int childCount = getChildCount();
339            for (int i = childCount - 1; i >= 0; i--) {
340                TaskView tv = (TaskView) getChildAt(i);
341                Task task = tv.getTask();
342                int taskIndex = mStack.indexOfTask(task);
343                if (taskIndex < 0 || !mTaskTransforms.get(taskIndex).visible) {
344                    mViewPool.returnViewToPool(tv);
345                } else {
346                    tv.updateViewPropertiesToTaskTransform(null, mTaskTransforms.get(taskIndex),
347                            mStackViewsAnimationDuration);
348                }
349            }
350
351            if (Console.Enabled) {
352                Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
353                        "  [TaskStackView|viewChildren]", "" + getChildCount());
354            }
355
356            mStackViewsAnimationDuration = 0;
357            mStackViewsDirty = false;
358        }
359    }
360
361    /** Sets the current stack scroll */
362    public void setStackScroll(int value) {
363        mStackScroll = value;
364        requestSynchronizeStackViewsWithModel();
365    }
366    /** Sets the current stack scroll without synchronizing the stack view with the model */
367    public void setStackScrollRaw(int value) {
368        mStackScroll = value;
369    }
370    /** Sets the current stack scroll to the initial state when you first enter recents */
371    public void setStackScrollToInitialState() {
372        if (mStack.getTaskCount() > 2) {
373            int initialScroll = mMaxScroll - mTaskRect.height() / 2;
374            setStackScroll(initialScroll);
375        } else {
376            setStackScroll(mMaxScroll);
377        }
378    }
379
380    /**
381     * Returns the scroll to such that the task transform at that index will have t=0. (If the scroll
382     * is not bounded)
383     */
384    int getStackScrollForTaskIndex(int i) {
385        int taskHeight = mTaskRect.height();
386        return (int) (i * Constants.Values.TaskStackView.StackOverlapPct * taskHeight);
387    }
388
389    /** Gets the current stack scroll */
390    public int getStackScroll() {
391        return mStackScroll;
392    }
393
394    /** Animates the stack scroll into bounds */
395    ObjectAnimator animateBoundScroll() {
396        int curScroll = getStackScroll();
397        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
398        if (newScroll != curScroll) {
399            // Enable hw layers on the stack
400            addHwLayersRefCount("animateBoundScroll");
401
402            // Start a new scroll animation
403            animateScroll(curScroll, newScroll, new Runnable() {
404                @Override
405                public void run() {
406                    // Disable hw layers on the stack
407                    decHwLayersRefCount("animateBoundScroll");
408                }
409            });
410        }
411        return mScrollAnimator;
412    }
413
414    /** Animates the stack scroll */
415    void animateScroll(int curScroll, int newScroll, final Runnable postRunnable) {
416        // Abort any current animations
417        abortScroller();
418        abortBoundScrollAnimation();
419
420        mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
421        mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
422                curScroll, 250));
423        mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
424        mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
425            @Override
426            public void onAnimationUpdate(ValueAnimator animation) {
427                setStackScroll((Integer) animation.getAnimatedValue());
428            }
429        });
430        mScrollAnimator.addListener(new AnimatorListenerAdapter() {
431            @Override
432            public void onAnimationEnd(Animator animation) {
433                if (postRunnable != null) {
434                    postRunnable.run();
435                }
436                mScrollAnimator.removeAllListeners();
437            }
438        });
439        mScrollAnimator.start();
440    }
441
442    /** Aborts any current stack scrolls */
443    void abortBoundScrollAnimation() {
444        if (mScrollAnimator != null) {
445            mScrollAnimator.cancel();
446        }
447    }
448
449    /** Aborts the scroller and any current fling */
450    void abortScroller() {
451        if (!mScroller.isFinished()) {
452            // Abort the scroller
453            mScroller.abortAnimation();
454            // And disable hw layers on the stack
455            decHwLayersRefCount("flingScroll");
456        }
457    }
458
459    /** Bounds the current scroll if necessary */
460    public boolean boundScroll() {
461        int curScroll = getStackScroll();
462        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
463        if (newScroll != curScroll) {
464            setStackScroll(newScroll);
465            return true;
466        }
467        return false;
468    }
469
470    /**
471     * Bounds the current scroll if necessary, but does not synchronize the stack view with the
472     * model.
473     */
474    public boolean boundScrollRaw() {
475        int curScroll = getStackScroll();
476        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
477        if (newScroll != curScroll) {
478            setStackScrollRaw(newScroll);
479            return true;
480        }
481        return false;
482    }
483
484
485    /** Returns the amount that the scroll is out of bounds */
486    int getScrollAmountOutOfBounds(int scroll) {
487        if (scroll < mMinScroll) {
488            return mMinScroll - scroll;
489        } else if (scroll > mMaxScroll) {
490            return scroll - mMaxScroll;
491        }
492        return 0;
493    }
494
495    /** Returns whether the specified scroll is out of bounds */
496    boolean isScrollOutOfBounds() {
497        return getScrollAmountOutOfBounds(getStackScroll()) != 0;
498    }
499
500    /** Returns whether the task view is in the stack bounds or not */
501    boolean isTaskInStackBounds(TaskView tv) {
502        Rect r = new Rect();
503        tv.getHitRect(r);
504        return r.bottom <= mRect.bottom;
505    }
506
507    /** Updates the min and max virtual scroll bounds */
508    void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
509        // Compute the min and max scroll values
510        int numTasks = Math.max(1, mStack.getTaskCount());
511        int taskHeight = mTaskRect.height();
512        int stackHeight = mStackRectSansPeek.height();
513        int maxScrollHeight = taskHeight + (int) ((numTasks - 1) *
514                Constants.Values.TaskStackView.StackOverlapPct * taskHeight);
515
516        if (numTasks <= 1) {
517            // If there is only one task, then center the task in the stack rect (sans peek)
518            mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2;
519        } else {
520            mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
521            mMaxScroll = maxScrollHeight - stackHeight;
522        }
523
524        // Debug logging
525        if (Constants.Log.UI.MeasureAndLayout) {
526            Console.log("  [TaskStack|minScroll] " + mMinScroll);
527            Console.log("  [TaskStack|maxScroll] " + mMaxScroll);
528        }
529
530        if (boundScrollToNewMinMax) {
531            boundScroll();
532        }
533    }
534
535    /** Focuses the task at the specified index in the stack */
536    void focusTask(int taskIndex, boolean scrollToNewPosition) {
537        if (Console.Enabled) {
538            Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "" + taskIndex);
539        }
540        if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
541            mFocusedTaskIndex = taskIndex;
542
543            // Focus the view if possible, otherwise, focus the view after we scroll into position
544            Task t = mStack.getTasks().get(taskIndex);
545            TaskView tv = getChildViewForTask(t);
546            Runnable postScrollRunnable = null;
547            if (tv != null) {
548                tv.setFocusedTask();
549                if (Console.Enabled) {
550                    Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "Requesting focus");
551                }
552            } else {
553                postScrollRunnable = new Runnable() {
554                    @Override
555                    public void run() {
556                        Task t = mStack.getTasks().get(mFocusedTaskIndex);
557                        TaskView tv = getChildViewForTask(t);
558                        if (tv != null) {
559                            tv.setFocusedTask();
560                            if (Console.Enabled) {
561                                Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]",
562                                        "Requesting focus after scroll animation");
563                            }
564                        }
565                    }
566                };
567            }
568
569            if (scrollToNewPosition) {
570                // Scroll the view into position
571                int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll,
572                        getStackScrollForTaskIndex(taskIndex)));
573
574                animateScroll(getStackScroll(), newScroll, postScrollRunnable);
575            } else {
576                if (postScrollRunnable != null) {
577                    postScrollRunnable.run();
578                }
579            }
580        }
581    }
582
583    /** Focuses the next task in the stack */
584    void focusNextTask(boolean forward) {
585        if (Console.Enabled) {
586            Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusNextTask]", "" +
587                    mFocusedTaskIndex);
588        }
589
590        // Find the next index to focus
591        int numTasks = mStack.getTaskCount();
592        if (mFocusedTaskIndex < 0) {
593            mFocusedTaskIndex = numTasks - 1;
594        }
595        if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) {
596            mFocusedTaskIndex = Math.max(0, Math.min(numTasks - 1,
597                    mFocusedTaskIndex + (forward ? -1 : 1)));
598        }
599        focusTask(mFocusedTaskIndex, true);
600    }
601
602    /** Enables the hw layers and increments the hw layer requirement ref count */
603    void addHwLayersRefCount(String reason) {
604        if (Console.Enabled) {
605            int refCount = mHwLayersTrigger.getCount();
606            Console.log(Constants.Log.UI.HwLayers,
607                    "[TaskStackView|addHwLayersRefCount] refCount: " +
608                            refCount + "->" + (refCount + 1) + " " + reason);
609        }
610        mHwLayersTrigger.increment();
611    }
612
613    /** Decrements the hw layer requirement ref count and disables the hw layers when we don't
614        need them anymore. */
615    void decHwLayersRefCount(String reason) {
616        if (Console.Enabled) {
617            int refCount = mHwLayersTrigger.getCount();
618            Console.log(Constants.Log.UI.HwLayers,
619                    "[TaskStackView|decHwLayersRefCount] refCount: " +
620                            refCount + "->" + (refCount - 1) + " " + reason);
621        }
622        mHwLayersTrigger.decrement();
623    }
624
625    @Override
626    public void computeScroll() {
627        if (mScroller.computeScrollOffset()) {
628            setStackScroll(mScroller.getCurrY());
629            invalidate(mStackRect);
630
631            // If we just finished scrolling, then disable the hw layers
632            if (mScroller.isFinished()) {
633                decHwLayersRefCount("finishedFlingScroll");
634            }
635        }
636    }
637
638    @Override
639    public boolean onInterceptTouchEvent(MotionEvent ev) {
640        return mTouchHandler.onInterceptTouchEvent(ev);
641    }
642
643    @Override
644    public boolean onTouchEvent(MotionEvent ev) {
645        return mTouchHandler.onTouchEvent(ev);
646    }
647
648    @Override
649    public void dispatchDraw(Canvas canvas) {
650        if (Console.Enabled) {
651            Console.log(Constants.Log.UI.Draw, "[TaskStackView|dispatchDraw]", "",
652                    Console.AnsiPurple);
653        }
654        synchronizeStackViewsWithModel();
655        super.dispatchDraw(canvas);
656    }
657
658    @Override
659    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
660        if (Constants.DebugFlags.App.EnableTaskStackClipping) {
661            TaskView tv = (TaskView) child;
662            TaskView nextTv = null;
663            TaskView tmpTv = null;
664            if (tv.shouldClipViewInStack()) {
665                int curIndex = indexOfChild(tv);
666
667                // Find the next view to clip against
668                while (nextTv == null && curIndex < getChildCount()) {
669                    tmpTv = (TaskView) getChildAt(++curIndex);
670                    if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
671                        nextTv = tmpTv;
672                    }
673                }
674
675                // Clip against the next view (if we aren't animating its alpha)
676                if (nextTv != null) {
677                    Rect curRect = tv.getClippingRect(mTmpRect);
678                    Rect nextRect = nextTv.getClippingRect(mTmpRect2);
679                    // The hit rects are relative to the task view, which needs to be offset by
680                    // the system bar height
681                    curRect.offset(0, mConfig.systemInsets.top);
682                    nextRect.offset(0, mConfig.systemInsets.top);
683                    // Compute the clip region
684                    Region clipRegion = new Region();
685                    clipRegion.op(curRect, Region.Op.UNION);
686                    clipRegion.op(nextRect, Region.Op.DIFFERENCE);
687                    // Clip the canvas
688                    int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
689                    canvas.clipRegion(clipRegion);
690                    boolean invalidate = super.drawChild(canvas, child, drawingTime);
691                    canvas.restoreToCount(saveCount);
692                    return invalidate;
693                }
694            }
695        }
696        return super.drawChild(canvas, child, drawingTime);
697    }
698
699    /** Computes the stack and task rects */
700    public void computeRects(int width, int height, int insetLeft, int insetBottom) {
701        // Note: We let the stack view be the full height because we want the cards to go under the
702        //       navigation bar if possible.  However, the stack rects which we use to calculate
703        //       max scroll, etc. need to take the nav bar into account
704
705        // Compute the stack rects
706        mRect.set(0, 0, width, height);
707        mStackRect.set(mRect);
708        mStackRect.left += insetLeft;
709        mStackRect.bottom -= insetBottom;
710
711        int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
712        int heightPadding = mConfig.taskStackTopPaddingPx;
713        if (Constants.DebugFlags.App.EnableSearchLayout) {
714            mStackRect.top += heightPadding;
715            mStackRect.left += widthPadding;
716            mStackRect.right -= widthPadding;
717            mStackRect.bottom -= heightPadding;
718        } else {
719            mStackRect.inset(widthPadding, heightPadding);
720        }
721        mStackRectSansPeek.set(mStackRect);
722        mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
723
724        // Compute the task rect
725        int size = mStackRect.width();
726        int left = mStackRect.left + (mStackRect.width() - size) / 2;
727        mTaskRect.set(left, mStackRectSansPeek.top,
728                left + size, mStackRectSansPeek.top + size);
729
730        // Update the scroll bounds
731        updateMinMaxScroll(false);
732    }
733
734    /**
735     * This is called with the size of the space not including the top or right insets, or the
736     * search bar height in portrait (but including the search bar width in landscape, since we want
737     * to draw under it.
738     */
739    @Override
740    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
741        int width = MeasureSpec.getSize(widthMeasureSpec);
742        int height = MeasureSpec.getSize(heightMeasureSpec);
743        if (Console.Enabled) {
744            Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|measure]",
745                    "width: " + width + " height: " + height +
746                            " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen);
747        }
748
749        // Compute our stack/task rects
750        Rect taskStackBounds = new Rect();
751        mConfig.getTaskStackBounds(width, height, taskStackBounds);
752        computeRects(width, height, taskStackBounds.left, mConfig.systemInsets.bottom);
753
754        // Debug logging
755        if (Constants.Log.UI.MeasureAndLayout) {
756            Console.log("  [TaskStack|fullRect] " + mRect);
757            Console.log("  [TaskStack|stackRect] " + mStackRect);
758            Console.log("  [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
759            Console.log("  [TaskStack|taskRect] " + mTaskRect);
760        }
761
762        // If this is the first layout, then scroll to the front of the stack and synchronize the
763        // stack views immediately
764        if (mAwaitingFirstLayout) {
765            setStackScrollToInitialState();
766            requestSynchronizeStackViewsWithModel();
767            synchronizeStackViewsWithModel();
768        }
769
770        // Measure each of the children
771        int childCount = getChildCount();
772        for (int i = 0; i < childCount; i++) {
773            TaskView t = (TaskView) getChildAt(i);
774            t.measure(MeasureSpec.makeMeasureSpec(mTaskRect.width(), MeasureSpec.EXACTLY),
775                    MeasureSpec.makeMeasureSpec(mTaskRect.height(), MeasureSpec.EXACTLY));
776        }
777
778        setMeasuredDimension(width, height);
779    }
780
781    /**
782     * This is called with the size of the space not including the top or right insets, or the
783     * search bar height in portrait (but including the search bar width in landscape, since we want
784     * to draw under it.
785     */
786    @Override
787    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
788        if (Console.Enabled) {
789            Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|layout]",
790                    "" + new Rect(left, top, right, bottom), Console.AnsiGreen);
791        }
792
793        // Debug logging
794        if (Constants.Log.UI.MeasureAndLayout) {
795            Console.log("  [TaskStack|fullRect] " + mRect);
796            Console.log("  [TaskStack|stackRect] " + mStackRect);
797            Console.log("  [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
798            Console.log("  [TaskStack|taskRect] " + mTaskRect);
799        }
800
801        // Layout each of the children
802        int childCount = getChildCount();
803        for (int i = 0; i < childCount; i++) {
804            TaskView t = (TaskView) getChildAt(i);
805            t.layout(mTaskRect.left, mStackRectSansPeek.top,
806                    mTaskRect.right, mStackRectSansPeek.top + mTaskRect.height());
807        }
808
809        if (mAwaitingFirstLayout) {
810            // Mark that we have completely the first layout
811            mAwaitingFirstLayout = false;
812
813            // Start dozing
814            mDozeTrigger.startDozing();
815
816            // Prepare the first view for its enter animation
817            int offsetTopAlign = -mTaskRect.top;
818            int offscreenY = mRect.bottom - (mTaskRect.top - mRect.top);
819            for (int i = childCount - 1; i >= 0; i--) {
820                TaskView tv = (TaskView) getChildAt(i);
821                tv.prepareAnimateEnterRecents((i == (getChildCount() - 1)), offsetTopAlign,
822                        offscreenY, mTaskRect);
823            }
824
825            // If the enter animation started already and we haven't completed a layout yet, do the
826            // enter animation now
827            if (mStartEnterAnimationRequestedAfterLayout) {
828                startOnEnterAnimation(mStartEnterAnimationContext);
829                mStartEnterAnimationRequestedAfterLayout = false;
830                mStartEnterAnimationContext = null;
831            }
832
833            // Update the focused task index to be the next item to the top task
834            if (mConfig.launchedWithAltTab) {
835                focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
836            }
837        }
838    }
839
840    /** Requests this task stacks to start it's enter-recents animation */
841    public void startOnEnterAnimation(ViewAnimation.TaskViewEnterContext ctx) {
842        // If we are still waiting to layout, then just defer until then
843        if (mAwaitingFirstLayout) {
844            mStartEnterAnimationRequestedAfterLayout = true;
845            mStartEnterAnimationContext = ctx;
846            return;
847        }
848
849        // Animate all the task views into view
850        ctx.taskRect = mTaskRect;
851        ctx.stackRectSansPeek = mStackRectSansPeek;
852        int childCount = getChildCount();
853        for (int i = childCount - 1; i >= 0; i--) {
854            TaskView tv = (TaskView) getChildAt(i);
855            TaskViewTransform transform = getStackTransform(mStack.indexOfTask(tv.getTask()),
856                    getStackScroll());
857            ctx.stackViewIndex = i;
858            ctx.stackViewCount = childCount;
859            ctx.isFrontMost = (i == (getChildCount() - 1));
860            ctx.transform = transform;
861            tv.animateOnEnterRecents(ctx);
862        }
863    }
864
865    /** Requests this task stacks to start it's exit-recents animation. */
866    public void startOnExitAnimation(ViewAnimation.TaskViewExitContext ctx) {
867        // Animate all the task views into view
868        ctx.offscreenTranslationY = mRect.bottom - (mTaskRect.top - mRect.top);
869        int childCount = getChildCount();
870        for (int i = 0; i < childCount; i++) {
871            TaskView tv = (TaskView) getChildAt(i);
872            tv.animateOnExitRecents(ctx);
873        }
874    }
875
876    @Override
877    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
878        super.onScrollChanged(l, t, oldl, oldt);
879        requestSynchronizeStackViewsWithModel();
880    }
881
882    public boolean isTransformedTouchPointInView(float x, float y, View child) {
883        return isTransformedTouchPointInView(x, y, child, null);
884    }
885
886    /** Pokes the dozer on user interaction. */
887    void onUserInteraction() {
888        // If the dozer is not running, then either we have not yet laid out, or it has already
889        // fallen asleep, so just let it rest.
890        if (mDozeTrigger.isDozing()) {
891            mDozeTrigger.poke();
892        }
893    }
894
895    /**** TaskStackCallbacks Implementation ****/
896
897    @Override
898    public void onStackTaskAdded(TaskStack stack, Task t) {
899        requestSynchronizeStackViewsWithModel();
900    }
901
902    @Override
903    public void onStackTaskRemoved(TaskStack stack, Task t) {
904        // Remove the view associated with this task, we can't rely on updateTransforms
905        // to work here because the task is no longer in the list
906        int childCount = getChildCount();
907        for (int i = childCount - 1; i >= 0; i--) {
908            TaskView tv = (TaskView) getChildAt(i);
909            if (tv.getTask() == t) {
910                mViewPool.returnViewToPool(tv);
911                break;
912            }
913        }
914
915        // Update the min/max scroll and animate other task views into their new positions
916        updateMinMaxScroll(true);
917        int movement = (int) (Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height());
918        requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
919
920        // If there are no remaining tasks, then either unfilter the current stack, or just close
921        // the activity if there are no filtered stacks
922        if (mStack.getTaskCount() == 0) {
923            boolean shouldFinishActivity = true;
924            if (mStack.hasFilteredTasks()) {
925                mStack.unfilterTasks();
926                shouldFinishActivity = (mStack.getTaskCount() == 0);
927            }
928            if (shouldFinishActivity) {
929                Activity activity = (Activity) getContext();
930                activity.finish();
931            }
932        }
933    }
934
935    /**
936     * Creates the animations for all the children views that need to be removed or to move views
937     * to their un/filtered position when we are un/filtering a stack, and returns the duration
938     * for these animations.
939     */
940    int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks,
941                        ArrayList<TaskViewTransform> curTaskTransforms,
942                        ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms,
943                        HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut,
944                        ArrayList<TaskView> childrenToRemoveOut) {
945        // Animate all of the existing views out of view (if they are not in the visible range in
946        // the new stack) or to their final positions in the new stack
947        int movement = 0;
948        int childCount = getChildCount();
949        for (int i = 0; i < childCount; i++) {
950            TaskView tv = (TaskView) getChildAt(i);
951            Task task = tv.getTask();
952            int taskIndex = tasks.indexOf(task);
953            TaskViewTransform toTransform;
954
955            // If the view is no longer visible, then we should just animate it out
956            boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible;
957            if (willBeInvisible) {
958                if (taskIndex < 0) {
959                    toTransform = curTaskTransforms.get(curTasks.indexOf(task));
960                } else {
961                    toTransform = new TaskViewTransform(taskTransforms.get(taskIndex));
962                }
963                tv.prepareTaskTransformForFilterTaskVisible(toTransform);
964                childrenToRemoveOut.add(tv);
965            } else {
966                toTransform = taskTransforms.get(taskIndex);
967                // Use the movement of the visible views to calculate the duration of the animation
968                movement = Math.max(movement, Math.abs(toTransform.translationY -
969                        (int) tv.getTranslationY()));
970            }
971            childViewTransformsOut.put(tv, new Pair(0, toTransform));
972        }
973        return Utilities.calculateTranslationAnimationDuration(movement,
974                mConfig.filteringCurrentViewsMinAnimDuration);
975    }
976
977    /**
978     * Creates the animations for all the children views that need to be animated in when we are
979     * un/filtering a stack, and returns the duration for these animations.
980     */
981    int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks,
982                         ArrayList<TaskViewTransform> taskTransforms,
983                         HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut) {
984        int offset = 0;
985        int movement = 0;
986        int taskCount = tasks.size();
987        for (int i = taskCount - 1; i >= 0; i--) {
988            Task task = tasks.get(i);
989            TaskViewTransform toTransform = taskTransforms.get(i);
990            if (toTransform.visible) {
991                TaskView tv = getChildViewForTask(task);
992                if (tv == null) {
993                    // For views that are not already visible, animate them in
994                    tv = mViewPool.pickUpViewFromPool(task, task);
995
996                    // Compose a new transform to fade and slide the new task in
997                    TaskViewTransform fromTransform = new TaskViewTransform(toTransform);
998                    tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
999                    tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0);
1000
1001                    int startDelay = offset *
1002                            Constants.Values.TaskStackView.FilterStartDelay;
1003                    childViewTransformsOut.put(tv, new Pair(startDelay, toTransform));
1004
1005                    // Use the movement of the new views to calculate the duration of the animation
1006                    movement = Math.max(movement,
1007                            Math.abs(toTransform.translationY - fromTransform.translationY));
1008                    offset++;
1009                }
1010            }
1011        }
1012        return Utilities.calculateTranslationAnimationDuration(movement,
1013                mConfig.filteringNewViewsMinAnimDuration);
1014    }
1015
1016    /** Orchestrates the animations of the current child views and any new views. */
1017    void doFilteringAnimation(ArrayList<Task> curTasks,
1018                              ArrayList<TaskViewTransform> curTaskTransforms,
1019                              final ArrayList<Task> tasks,
1020                              final ArrayList<TaskViewTransform> taskTransforms) {
1021        // Calculate the transforms to animate out all the existing views if they are not in the
1022        // new visible range (or to their final positions in the stack if they are)
1023        final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
1024        final HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransforms =
1025                new HashMap<TaskView, Pair<Integer, TaskViewTransform>>();
1026        int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks,
1027                taskTransforms, childViewTransforms, childrenToRemove);
1028
1029        // If all the current views are in the visible range of the new stack, then don't wait for
1030        // views to animate out and animate all the new views into their place
1031        final boolean unifyNewViewAnimation = childrenToRemove.isEmpty();
1032        if (unifyNewViewAnimation) {
1033            int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms,
1034                    childViewTransforms);
1035            duration = Math.max(duration, inDuration);
1036        }
1037
1038        // Animate all the views to their final transforms
1039        for (final TaskView tv : childViewTransforms.keySet()) {
1040            Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv);
1041            tv.animate().cancel();
1042            tv.animate()
1043                    .setStartDelay(t.first)
1044                    .withEndAction(new Runnable() {
1045                        @Override
1046                        public void run() {
1047                            childViewTransforms.remove(tv);
1048                            if (childViewTransforms.isEmpty()) {
1049                                // Return all the removed children to the view pool
1050                                for (TaskView tv : childrenToRemove) {
1051                                    mViewPool.returnViewToPool(tv);
1052                                }
1053
1054                                if (!unifyNewViewAnimation) {
1055                                    // For views that are not already visible, animate them in
1056                                    childViewTransforms.clear();
1057                                    int duration = getEnterTransformsForFilterAnimation(tasks,
1058                                            taskTransforms, childViewTransforms);
1059                                    for (final TaskView tv : childViewTransforms.keySet()) {
1060                                        Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv);
1061                                        tv.animate().setStartDelay(t.first);
1062                                        tv.updateViewPropertiesToTaskTransform(null, t.second, duration);
1063                                    }
1064                                }
1065                            }
1066                        }
1067                    });
1068            tv.updateViewPropertiesToTaskTransform(null, t.second, duration);
1069        }
1070    }
1071
1072    @Override
1073    public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
1074                                Task filteredTask) {
1075        // Stash the scroll and filtered task for us to restore to when we unfilter
1076        mStashedScroll = getStackScroll();
1077
1078        // Calculate the current task transforms
1079        ArrayList<TaskViewTransform> curTaskTransforms =
1080                getStackTransforms(curTasks, getStackScroll(), null, true);
1081
1082        // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
1083        updateMinMaxScroll(false);
1084        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
1085        setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
1086        boundScrollRaw();
1087
1088        // Compute the transforms of the items in the new stack after setting the new scroll
1089        final ArrayList<Task> tasks = mStack.getTasks();
1090        final ArrayList<TaskViewTransform> taskTransforms =
1091                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
1092
1093        // Animate
1094        doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
1095    }
1096
1097    @Override
1098    public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
1099        // Calculate the current task transforms
1100        final ArrayList<TaskViewTransform> curTaskTransforms =
1101                getStackTransforms(curTasks, getStackScroll(), null, true);
1102
1103        // Restore the stashed scroll
1104        updateMinMaxScroll(false);
1105        setStackScrollRaw(mStashedScroll);
1106        boundScrollRaw();
1107
1108        // Compute the transforms of the items in the new stack after restoring the stashed scroll
1109        final ArrayList<Task> tasks = mStack.getTasks();
1110        final ArrayList<TaskViewTransform> taskTransforms =
1111                getStackTransforms(tasks, getStackScroll(), null, true);
1112
1113        // Animate
1114        doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
1115
1116        // Clear the saved vars
1117        mStashedScroll = 0;
1118    }
1119
1120    /**** ViewPoolConsumer Implementation ****/
1121
1122    @Override
1123    public TaskView createView(Context context) {
1124        if (Console.Enabled) {
1125            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]");
1126        }
1127        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
1128    }
1129
1130    @Override
1131    public void prepareViewToEnterPool(TaskView tv) {
1132        Task task = tv.getTask();
1133        tv.resetViewProperties();
1134        if (Console.Enabled) {
1135            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]",
1136                    tv.getTask() + " tv: " + tv);
1137        }
1138
1139        // Report that this tasks's data is no longer being used
1140        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
1141        loader.unloadTaskData(task);
1142
1143        // Detach the view from the hierarchy
1144        detachViewFromParent(tv);
1145
1146        // Disable hw layers on this view
1147        tv.disableHwLayers();
1148    }
1149
1150    @Override
1151    public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) {
1152        if (Console.Enabled) {
1153            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]",
1154                    "isNewView: " + isNewView);
1155        }
1156
1157        // Setup and attach the view to the window
1158        Task task = prepareData;
1159        // We try and rebind the task (this MUST be done before the task filled)
1160        tv.onTaskBound(task);
1161        // Request that this tasks's data be filled
1162        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
1163        loader.loadTaskData(task);
1164
1165        // Find the index where this task should be placed in the children
1166        int insertIndex = -1;
1167        int childCount = getChildCount();
1168        for (int i = 0; i < childCount; i++) {
1169            Task tvTask = ((TaskView) getChildAt(i)).getTask();
1170            if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) {
1171                insertIndex = i;
1172                break;
1173            }
1174        }
1175
1176        // Sanity check, the task view should always be clipping against the stack at this point,
1177        // but just in case, re-enable it here
1178        tv.setClipViewInStack(true);
1179
1180        // If the doze trigger has already fired, then update the state for this task view
1181        if (mDozeTrigger.hasTriggered()) {
1182            tv.setOnNoUserInteraction();
1183        }
1184
1185        // Add/attach the view to the hierarchy
1186        if (Console.Enabled) {
1187            Console.log(Constants.Log.ViewPool.PoolCallbacks, "  [TaskStackView|insertIndex]",
1188                    "" + insertIndex);
1189        }
1190        if (isNewView) {
1191            addView(tv, insertIndex);
1192
1193            // Set the callbacks and listeners for this new view
1194            tv.setOnClickListener(this);
1195            tv.setCallbacks(this);
1196        } else {
1197            attachViewToParent(tv, insertIndex, tv.getLayoutParams());
1198        }
1199
1200        // Enable hw layers on this view if hw layers are enabled on the stack
1201        if (mHwLayersTrigger.getCount() > 0) {
1202            tv.enableHwLayers();
1203        }
1204    }
1205
1206    @Override
1207    public boolean hasPreferredData(TaskView tv, Task preferredData) {
1208        return (tv.getTask() == preferredData);
1209    }
1210
1211    /**** TaskViewCallbacks Implementation ****/
1212
1213    @Override
1214    public void onTaskIconClicked(TaskView tv) {
1215        if (Console.Enabled) {
1216            Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Icon]",
1217                    tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(),
1218                    Console.AnsiCyan);
1219        }
1220        if (Constants.DebugFlags.App.EnableTaskFiltering) {
1221            if (mStack.hasFilteredTasks()) {
1222                mStack.unfilterTasks();
1223            } else {
1224                mStack.filterTasks(tv.getTask());
1225            }
1226        }
1227    }
1228
1229    @Override
1230    public void onTaskAppInfoClicked(TaskView tv) {
1231        if (mCb != null) {
1232            mCb.onTaskAppInfoLaunched(tv.getTask());
1233        }
1234    }
1235
1236    @Override
1237    public void onTaskFocused(TaskView tv) {
1238        // Do nothing
1239    }
1240
1241    @Override
1242    public void onTaskDismissed(TaskView tv) {
1243        Task task = tv.getTask();
1244        // Remove the task from the view
1245        mStack.removeTask(task);
1246        // Notify the callback that we've removed the task and it can clean up after it
1247        mCb.onTaskRemoved(task);
1248    }
1249
1250    /**** View.OnClickListener Implementation ****/
1251
1252    @Override
1253    public void onClick(View v) {
1254        TaskView tv = (TaskView) v;
1255        Task task = tv.getTask();
1256        if (Console.Enabled) {
1257            Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
1258                    task + " cb: " + mCb);
1259        }
1260
1261        if (mCb != null) {
1262            mCb.onTaskLaunched(this, tv, mStack, task);
1263        }
1264    }
1265
1266    /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
1267
1268    @Override
1269    public void onComponentRemoved(Set<ComponentName> cns) {
1270        // For other tasks, just remove them directly if they no longer exist
1271        ArrayList<Task> tasks = mStack.getTasks();
1272        for (int i = tasks.size() - 1; i >= 0; i--) {
1273            final Task t = tasks.get(i);
1274            if (cns.contains(t.key.baseIntent.getComponent())) {
1275                TaskView tv = getChildViewForTask(t);
1276                if (tv != null) {
1277                    // For visible children, defer removing the task until after the animation
1278                    tv.animateRemoval(new Runnable() {
1279                        @Override
1280                        public void run() {
1281                            mStack.removeTask(t);
1282                        }
1283                    });
1284                } else {
1285                    // Otherwise, remove the task from the stack immediately
1286                    mStack.removeTask(t);
1287                }
1288            }
1289        }
1290    }
1291}
1292
1293/* Handles touch events */
1294class TaskStackViewTouchHandler implements SwipeHelper.Callback {
1295    static int INACTIVE_POINTER_ID = -1;
1296
1297    TaskStackView mSv;
1298    VelocityTracker mVelocityTracker;
1299
1300    boolean mIsScrolling;
1301
1302    int mInitialMotionX, mInitialMotionY;
1303    int mLastMotionX, mLastMotionY;
1304    int mActivePointerId = INACTIVE_POINTER_ID;
1305    TaskView mActiveTaskView = null;
1306
1307    int mTotalScrollMotion;
1308    int mMinimumVelocity;
1309    int mMaximumVelocity;
1310    // The scroll touch slop is used to calculate when we start scrolling
1311    int mScrollTouchSlop;
1312    // The page touch slop is used to calculate when we start swiping
1313    float mPagingTouchSlop;
1314
1315    SwipeHelper mSwipeHelper;
1316    boolean mInterceptedBySwipeHelper;
1317
1318    public TaskStackViewTouchHandler(Context context, TaskStackView sv) {
1319        ViewConfiguration configuration = ViewConfiguration.get(context);
1320        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
1321        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
1322        mScrollTouchSlop = configuration.getScaledTouchSlop();
1323        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
1324        mSv = sv;
1325
1326
1327        float densityScale = context.getResources().getDisplayMetrics().density;
1328        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop);
1329        mSwipeHelper.setMinAlpha(1f);
1330    }
1331
1332    /** Velocity tracker helpers */
1333    void initOrResetVelocityTracker() {
1334        if (mVelocityTracker == null) {
1335            mVelocityTracker = VelocityTracker.obtain();
1336        } else {
1337            mVelocityTracker.clear();
1338        }
1339    }
1340    void initVelocityTrackerIfNotExists() {
1341        if (mVelocityTracker == null) {
1342            mVelocityTracker = VelocityTracker.obtain();
1343        }
1344    }
1345    void recycleVelocityTracker() {
1346        if (mVelocityTracker != null) {
1347            mVelocityTracker.recycle();
1348            mVelocityTracker = null;
1349        }
1350    }
1351
1352    /** Returns the view at the specified coordinates */
1353    TaskView findViewAtPoint(int x, int y) {
1354        int childCount = mSv.getChildCount();
1355        for (int i = childCount - 1; i >= 0; i--) {
1356            TaskView tv = (TaskView) mSv.getChildAt(i);
1357            if (tv.getVisibility() == View.VISIBLE) {
1358                if (mSv.isTransformedTouchPointInView(x, y, tv)) {
1359                    return tv;
1360                }
1361            }
1362        }
1363        return null;
1364    }
1365
1366    /** Touch preprocessing for handling below */
1367    public boolean onInterceptTouchEvent(MotionEvent ev) {
1368        if (Console.Enabled) {
1369            Console.log(Constants.Log.UI.TouchEvents,
1370                    "[TaskStackViewTouchHandler|interceptTouchEvent]",
1371                    Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
1372        }
1373
1374        // Return early if we have no children
1375        boolean hasChildren = (mSv.getChildCount() > 0);
1376        if (!hasChildren) {
1377            return false;
1378        }
1379
1380        // Pass through to swipe helper if we are swiping
1381        mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
1382        if (mInterceptedBySwipeHelper) {
1383            return true;
1384        }
1385
1386        boolean wasScrolling = !mSv.mScroller.isFinished() ||
1387                (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning());
1388        int action = ev.getAction();
1389        switch (action & MotionEvent.ACTION_MASK) {
1390            case MotionEvent.ACTION_DOWN: {
1391                // Save the touch down info
1392                mInitialMotionX = mLastMotionX = (int) ev.getX();
1393                mInitialMotionY = mLastMotionY = (int) ev.getY();
1394                mActivePointerId = ev.getPointerId(0);
1395                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
1396                // Stop the current scroll if it is still flinging
1397                mSv.abortScroller();
1398                mSv.abortBoundScrollAnimation();
1399                // Initialize the velocity tracker
1400                initOrResetVelocityTracker();
1401                mVelocityTracker.addMovement(ev);
1402                // Check if the scroller is finished yet
1403                mIsScrolling = !mSv.mScroller.isFinished();
1404                break;
1405            }
1406            case MotionEvent.ACTION_MOVE: {
1407                if (mActivePointerId == INACTIVE_POINTER_ID) break;
1408
1409                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1410                int y = (int) ev.getY(activePointerIndex);
1411                int x = (int) ev.getX(activePointerIndex);
1412                if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
1413                    // Save the touch move info
1414                    mIsScrolling = true;
1415                    // Initialize the velocity tracker if necessary
1416                    initVelocityTrackerIfNotExists();
1417                    mVelocityTracker.addMovement(ev);
1418                    // Disallow parents from intercepting touch events
1419                    final ViewParent parent = mSv.getParent();
1420                    if (parent != null) {
1421                        parent.requestDisallowInterceptTouchEvent(true);
1422                    }
1423                    // Enable HW layers
1424                    mSv.addHwLayersRefCount("stackScroll");
1425                }
1426
1427                mLastMotionX = x;
1428                mLastMotionY = y;
1429                break;
1430            }
1431            case MotionEvent.ACTION_CANCEL:
1432            case MotionEvent.ACTION_UP: {
1433                // Animate the scroll back if we've cancelled
1434                mSv.animateBoundScroll();
1435                // Disable HW layers
1436                if (mIsScrolling) {
1437                    mSv.decHwLayersRefCount("stackScroll");
1438                }
1439                // Reset the drag state and the velocity tracker
1440                mIsScrolling = false;
1441                mActivePointerId = INACTIVE_POINTER_ID;
1442                mActiveTaskView = null;
1443                mTotalScrollMotion = 0;
1444                recycleVelocityTracker();
1445                break;
1446            }
1447        }
1448
1449        return wasScrolling || mIsScrolling;
1450    }
1451
1452    /** Handles touch events once we have intercepted them */
1453    public boolean onTouchEvent(MotionEvent ev) {
1454        if (Console.Enabled) {
1455            Console.log(Constants.Log.UI.TouchEvents,
1456                    "[TaskStackViewTouchHandler|touchEvent]",
1457                    Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
1458        }
1459
1460        // Short circuit if we have no children
1461        boolean hasChildren = (mSv.getChildCount() > 0);
1462        if (!hasChildren) {
1463            return false;
1464        }
1465
1466        // Pass through to swipe helper if we are swiping
1467        if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
1468            return true;
1469        }
1470
1471        // Update the velocity tracker
1472        initVelocityTrackerIfNotExists();
1473        mVelocityTracker.addMovement(ev);
1474
1475        int action = ev.getAction();
1476        switch (action & MotionEvent.ACTION_MASK) {
1477            case MotionEvent.ACTION_DOWN: {
1478                // Save the touch down info
1479                mInitialMotionX = mLastMotionX = (int) ev.getX();
1480                mInitialMotionY = mLastMotionY = (int) ev.getY();
1481                mActivePointerId = ev.getPointerId(0);
1482                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
1483                // Stop the current scroll if it is still flinging
1484                mSv.abortScroller();
1485                mSv.abortBoundScrollAnimation();
1486                // Initialize the velocity tracker
1487                initOrResetVelocityTracker();
1488                mVelocityTracker.addMovement(ev);
1489                // Disallow parents from intercepting touch events
1490                final ViewParent parent = mSv.getParent();
1491                if (parent != null) {
1492                    parent.requestDisallowInterceptTouchEvent(true);
1493                }
1494                break;
1495            }
1496            case MotionEvent.ACTION_POINTER_DOWN: {
1497                final int index = ev.getActionIndex();
1498                mActivePointerId = ev.getPointerId(index);
1499                mLastMotionX = (int) ev.getX(index);
1500                mLastMotionY = (int) ev.getY(index);
1501                break;
1502            }
1503            case MotionEvent.ACTION_MOVE: {
1504                if (mActivePointerId == INACTIVE_POINTER_ID) break;
1505
1506                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1507                int x = (int) ev.getX(activePointerIndex);
1508                int y = (int) ev.getY(activePointerIndex);
1509                int yTotal = Math.abs(y - mInitialMotionY);
1510                int deltaY = mLastMotionY - y;
1511                if (!mIsScrolling) {
1512                    if (yTotal > mScrollTouchSlop) {
1513                        mIsScrolling = true;
1514                        // Initialize the velocity tracker
1515                        initOrResetVelocityTracker();
1516                        mVelocityTracker.addMovement(ev);
1517                        // Disallow parents from intercepting touch events
1518                        final ViewParent parent = mSv.getParent();
1519                        if (parent != null) {
1520                            parent.requestDisallowInterceptTouchEvent(true);
1521                        }
1522                        // Enable HW layers
1523                        mSv.addHwLayersRefCount("stackScroll");
1524                    }
1525                }
1526                if (mIsScrolling) {
1527                    int curStackScroll = mSv.getStackScroll();
1528                    int overScrollAmount = mSv.getScrollAmountOutOfBounds(curStackScroll + deltaY);
1529                    if (overScrollAmount != 0) {
1530                        // Bound the overscroll to a fixed amount, and inversely scale the y-movement
1531                        // relative to how close we are to the max overscroll
1532                        float maxOverScroll = mSv.mTaskRect.height() / 3f;
1533                        deltaY = Math.round(deltaY * (1f - (Math.min(maxOverScroll, overScrollAmount)
1534                                / maxOverScroll)));
1535                    }
1536                    mSv.setStackScroll(curStackScroll + deltaY);
1537                    if (mSv.isScrollOutOfBounds()) {
1538                        mVelocityTracker.clear();
1539                    }
1540                }
1541                mLastMotionX = x;
1542                mLastMotionY = y;
1543                mTotalScrollMotion += Math.abs(deltaY);
1544                break;
1545            }
1546            case MotionEvent.ACTION_UP: {
1547                final VelocityTracker velocityTracker = mVelocityTracker;
1548                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1549                int velocity = (int) velocityTracker.getYVelocity(mActivePointerId);
1550
1551                if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
1552                    // Enable HW layers on the stack
1553                    mSv.addHwLayersRefCount("flingScroll");
1554                    // XXX: Make this animation a function of the velocity AND distance
1555                    int overscrollRange = (int) (Math.min(1f,
1556                            Math.abs((float) velocity / mMaximumVelocity)) *
1557                            Constants.Values.TaskStackView.TaskStackOverscrollRange);
1558
1559                    if (Console.Enabled) {
1560                        Console.log(Constants.Log.UI.TouchEvents,
1561                                "[TaskStackViewTouchHandler|fling]",
1562                                "scroll: " + mSv.getStackScroll() + " velocity: " + velocity +
1563                                        " maxVelocity: " + mMaximumVelocity +
1564                                        " overscrollRange: " + overscrollRange,
1565                                Console.AnsiGreen);
1566                    }
1567
1568                    // Fling scroll
1569                    mSv.mScroller.fling(0, mSv.getStackScroll(),
1570                            0, -velocity,
1571                            0, 0,
1572                            mSv.mMinScroll, mSv.mMaxScroll,
1573                            0, overscrollRange);
1574                    // Invalidate to kick off computeScroll
1575                    mSv.invalidate(mSv.mStackRect);
1576                } else if (mSv.isScrollOutOfBounds()) {
1577                    // Animate the scroll back into bounds
1578                    // XXX: Make this animation a function of the velocity OR distance
1579                    mSv.animateBoundScroll();
1580                }
1581
1582                if (mIsScrolling) {
1583                    // Disable HW layers
1584                    mSv.decHwLayersRefCount("stackScroll");
1585                }
1586                mActivePointerId = INACTIVE_POINTER_ID;
1587                mIsScrolling = false;
1588                mTotalScrollMotion = 0;
1589                recycleVelocityTracker();
1590                break;
1591            }
1592            case MotionEvent.ACTION_POINTER_UP: {
1593                int pointerIndex = ev.getActionIndex();
1594                int pointerId = ev.getPointerId(pointerIndex);
1595                if (pointerId == mActivePointerId) {
1596                    // Select a new active pointer id and reset the motion state
1597                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
1598                    mActivePointerId = ev.getPointerId(newPointerIndex);
1599                    mLastMotionX = (int) ev.getX(newPointerIndex);
1600                    mLastMotionY = (int) ev.getY(newPointerIndex);
1601                    mVelocityTracker.clear();
1602                }
1603                break;
1604            }
1605            case MotionEvent.ACTION_CANCEL: {
1606                if (mIsScrolling) {
1607                    // Disable HW layers
1608                    mSv.decHwLayersRefCount("stackScroll");
1609                }
1610                if (mSv.isScrollOutOfBounds()) {
1611                    // Animate the scroll back into bounds
1612                    // XXX: Make this animation a function of the velocity OR distance
1613                    mSv.animateBoundScroll();
1614                }
1615                mActivePointerId = INACTIVE_POINTER_ID;
1616                mIsScrolling = false;
1617                mTotalScrollMotion = 0;
1618                recycleVelocityTracker();
1619                break;
1620            }
1621        }
1622        return true;
1623    }
1624
1625    /**** SwipeHelper Implementation ****/
1626
1627    @Override
1628    public View getChildAtPosition(MotionEvent ev) {
1629        return findViewAtPoint((int) ev.getX(), (int) ev.getY());
1630    }
1631
1632    @Override
1633    public boolean canChildBeDismissed(View v) {
1634        return true;
1635    }
1636
1637    @Override
1638    public void onBeginDrag(View v) {
1639        // Enable HW layers
1640        mSv.addHwLayersRefCount("swipeBegin");
1641        // Disable clipping with the stack while we are swiping
1642        TaskView tv = (TaskView) v;
1643        tv.setClipViewInStack(false);
1644        // Disallow parents from intercepting touch events
1645        final ViewParent parent = mSv.getParent();
1646        if (parent != null) {
1647            parent.requestDisallowInterceptTouchEvent(true);
1648        }
1649    }
1650
1651    @Override
1652    public void onSwipeChanged(View v, float delta) {
1653        // Do nothing
1654    }
1655
1656    @Override
1657    public void onChildDismissed(View v) {
1658        TaskView tv = (TaskView) v;
1659        mSv.onTaskDismissed(tv);
1660
1661        // Re-enable clipping with the stack (we will reuse this view)
1662        tv.setClipViewInStack(true);
1663
1664        // Disable HW layers
1665        mSv.decHwLayersRefCount("swipeComplete");
1666    }
1667
1668    @Override
1669    public void onSnapBackCompleted(View v) {
1670        // Re-enable clipping with the stack
1671        TaskView tv = (TaskView) v;
1672        tv.setClipViewInStack(true);
1673    }
1674
1675    @Override
1676    public void onDragCancelled(View v) {
1677        // Disable HW layers
1678        mSv.decHwLayersRefCount("swipeCancelled");
1679    }
1680}
1681