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