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