TaskStackView.java revision ffa2ec664479bff6b4b61d4c349d9db2cb37ca16
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.content.ComponentName;
24import android.content.Context;
25import android.graphics.Canvas;
26import android.graphics.Rect;
27import android.graphics.Region;
28import android.view.LayoutInflater;
29import android.view.MotionEvent;
30import android.view.View;
31import android.widget.FrameLayout;
32import android.widget.OverScroller;
33import com.android.systemui.R;
34import com.android.systemui.recents.Constants;
35import com.android.systemui.recents.RecentsConfiguration;
36import com.android.systemui.recents.misc.Console;
37import com.android.systemui.recents.misc.DozeTrigger;
38import com.android.systemui.recents.misc.ReferenceCountedTrigger;
39import com.android.systemui.recents.misc.Utilities;
40import com.android.systemui.recents.model.RecentsPackageMonitor;
41import com.android.systemui.recents.model.RecentsTaskLoader;
42import com.android.systemui.recents.model.Task;
43import com.android.systemui.recents.model.TaskStack;
44
45import java.util.ArrayList;
46import java.util.HashMap;
47import java.util.Set;
48
49
50/* The visual representation of a task stack view */
51public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
52        TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
53        View.OnClickListener, RecentsPackageMonitor.PackageCallbacks {
54
55    /** The TaskView callbacks */
56    interface TaskStackViewCallbacks {
57        public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
58        public void onTaskViewAppInfoClicked(Task t);
59        public void onTaskViewDismissed(Task t);
60        public void onAllTaskViewsDismissed();
61        public void onTaskStackFilterTriggered();
62        public void onTaskStackUnfilterTriggered();
63    }
64
65    RecentsConfiguration mConfig;
66
67    TaskStack mStack;
68    TaskStackViewLayoutAlgorithm mStackAlgorithm;
69    TaskStackViewFilterAlgorithm mFilterAlgorithm;
70    TaskStackViewTouchHandler mTouchHandler;
71    TaskStackViewCallbacks mCb;
72    ViewPool<TaskView, Task> mViewPool;
73    ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
74    DozeTrigger mUIDozeTrigger;
75
76    // The virtual stack scroll that we use for the card layout
77    int mStackScroll;
78    int mMinScroll;
79    int mMaxScroll;
80    int mStashedScroll;
81    int mFocusedTaskIndex = -1;
82    OverScroller mScroller;
83    ObjectAnimator mScrollAnimator;
84
85    // Optimizations
86    ReferenceCountedTrigger mHwLayersTrigger;
87    int mStackViewsAnimationDuration;
88    boolean mStackViewsDirty = true;
89    boolean mAwaitingFirstLayout = true;
90    boolean mStartEnterAnimationRequestedAfterLayout;
91    ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
92    int[] mTmpVisibleRange = new int[2];
93    Rect mTmpRect = new Rect();
94    Rect mTmpRect2 = new Rect();
95    LayoutInflater mInflater;
96
97    Runnable mReturnAllViewsToPoolRunnable = new Runnable() {
98        @Override
99        public void run() {
100            int childCount = getChildCount();
101            for (int i = childCount - 1; i >= 0; i--) {
102                mViewPool.returnViewToPool((TaskView) getChildAt(i));
103            }
104        }
105    };
106
107    public TaskStackView(Context context, TaskStack stack) {
108        super(context);
109        mConfig = RecentsConfiguration.getInstance();
110        mStack = stack;
111        mStack.setCallbacks(this);
112        mScroller = new OverScroller(context);
113        mTouchHandler = new TaskStackViewTouchHandler(context, this);
114        mViewPool = new ViewPool<TaskView, Task>(context, this);
115        mInflater = LayoutInflater.from(context);
116        mStackAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig);
117        mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool);
118        mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {
119            @Override
120            public void run() {
121                // Show the task bar dismiss buttons
122                int childCount = getChildCount();
123                for (int i = 0; i < childCount; i++) {
124                    TaskView tv = (TaskView) getChildAt(i);
125                    tv.startNoUserInteractionAnimation();
126                }
127            }
128        });
129        mHwLayersTrigger = new ReferenceCountedTrigger(getContext(), new Runnable() {
130            @Override
131            public void run() {
132                // Enable hw layers on each of the children
133                int childCount = getChildCount();
134                for (int i = 0; i < childCount; i++) {
135                    TaskView tv = (TaskView) getChildAt(i);
136                    tv.enableHwLayers();
137                }
138            }
139        }, new Runnable() {
140            @Override
141            public void run() {
142                // Disable hw layers on each of the children
143                int childCount = getChildCount();
144                for (int i = 0; i < childCount; i++) {
145                    TaskView tv = (TaskView) getChildAt(i);
146                    tv.disableHwLayers();
147                }
148            }
149        }, new Runnable() {
150            @Override
151            public void run() {
152                new Throwable("Invalid hw layers ref count").printStackTrace();
153                Console.logError(getContext(), "Invalid HW layers ref count");
154            }
155        });
156    }
157
158    /** Sets the callbacks */
159    void setCallbacks(TaskStackViewCallbacks cb) {
160        mCb = cb;
161    }
162
163    /** Requests that the views be synchronized with the model */
164    void requestSynchronizeStackViewsWithModel() {
165        requestSynchronizeStackViewsWithModel(0);
166    }
167    void requestSynchronizeStackViewsWithModel(int duration) {
168        if (Console.Enabled) {
169            Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
170                    "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow);
171        }
172        if (!mStackViewsDirty) {
173            invalidate(mStackAlgorithm.mStackRect);
174        }
175        if (mAwaitingFirstLayout) {
176            // Skip the animation if we are awaiting first layout
177            mStackViewsAnimationDuration = 0;
178        } else {
179            mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
180        }
181        mStackViewsDirty = true;
182    }
183
184    /** Returns a mapping of child view to Task. */
185    HashMap<Task, TaskView> getTaskChildViewMap() {
186        HashMap<Task, TaskView> taskViewMap = new HashMap<Task, TaskView>();
187        int childCount = getChildCount();
188        for (int i = 0; i < childCount; i++) {
189            TaskView tv = (TaskView) getChildAt(i);
190            taskViewMap.put(tv.getTask(), tv);
191        }
192        return taskViewMap;
193    }
194
195    /** Finds the child view given a specific task. */
196    TaskView getChildViewForTask(Task t) {
197        int childCount = getChildCount();
198        for (int i = 0; i < childCount; i++) {
199            TaskView tv = (TaskView) getChildAt(i);
200            if (tv.getTask() == t) {
201                return tv;
202            }
203        }
204        return null;
205    }
206
207    /** Returns the stack algorithm for this task stack. */
208    public TaskStackViewLayoutAlgorithm getStackAlgorithm() {
209        return mStackAlgorithm;
210    }
211
212    /**
213     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
214     */
215    private void updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
216                                       ArrayList<Task> tasks,
217                                       int stackScroll,
218                                       int[] visibleRangeOut,
219                                       boolean boundTranslationsToRect) {
220        // XXX: We should be intelligent about where to look for the visible stack range using the
221        //      current stack scroll.
222
223        int taskTransformCount = taskTransforms.size();
224        int taskCount = tasks.size();
225        int frontMostVisibleIndex = -1;
226        int backMostVisibleIndex = -1;
227
228        // We can reuse the task transforms where possible to reduce object allocation
229        if (taskTransformCount < taskCount) {
230            // If there are less transforms than tasks, then add as many transforms as necessary
231            for (int i = taskTransformCount; i < taskCount; i++) {
232                taskTransforms.add(new TaskViewTransform());
233            }
234        } else if (taskTransformCount > taskCount) {
235            // If there are more transforms than tasks, then just subset the transform list
236            taskTransforms.subList(0, taskCount);
237        }
238
239        // Update the stack transforms
240        TaskStack.GroupTaskIndex groupTaskIndex = new TaskStack.GroupTaskIndex();
241        for (int i = taskCount - 1; i >= 0; i--) {
242            mStack.getGroupIndexForTask(tasks.get(i), groupTaskIndex);
243            TaskViewTransform transform = mStackAlgorithm.getStackTransform(groupTaskIndex.groupIndex,
244                    groupTaskIndex.taskIndex, stackScroll, taskTransforms.get(i));
245            if (transform.visible) {
246                if (frontMostVisibleIndex < 0) {
247                    frontMostVisibleIndex = i;
248                }
249                backMostVisibleIndex = i;
250            } else {
251                if (backMostVisibleIndex != -1) {
252                    // We've reached the end of the visible range, so going down the rest of the
253                    // stack, we can just reset the transforms accordingly
254                    while (i >= 0) {
255                        taskTransforms.get(i).reset();
256                        i--;
257                    }
258                    break;
259                }
260            }
261
262            if (boundTranslationsToRect) {
263                transform.translationY = Math.min(transform.translationY,
264                        mStackAlgorithm.mRect.bottom);
265            }
266        }
267        if (visibleRangeOut != null) {
268            visibleRangeOut[0] = frontMostVisibleIndex;
269            visibleRangeOut[1] = backMostVisibleIndex;
270            if (Console.Enabled) {
271                Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
272                        "[TaskStackView|updateStackTransforms]",
273                        "Back: " + backMostVisibleIndex + " Front: " + frontMostVisibleIndex);
274            }
275        }
276    }
277
278    /**
279     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This
280     * call is less optimal than calling updateStackTransforms directly.
281     */
282    private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
283                                                            int stackScroll,
284                                                            int[] visibleRangeOut,
285                                                            boolean boundTranslationsToRect) {
286        ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
287        updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut,
288                boundTranslationsToRect);
289        return taskTransforms;
290    }
291
292    /** Synchronizes the views with the model */
293    void synchronizeStackViewsWithModel() {
294        if (Console.Enabled) {
295            Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
296                    "[TaskStackView|synchronizeViewsWithModel]",
297                    "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow);
298        }
299        if (mStackViewsDirty) {
300            // Get all the task transforms
301            ArrayList<Task> tasks = mStack.getTasks();
302            int stackScroll = getStackScroll();
303            int[] visibleRange = mTmpVisibleRange;
304            updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false);
305            TaskViewTransform tmpTransform = new TaskViewTransform();
306            TaskStack.GroupTaskIndex gti = new TaskStack.GroupTaskIndex();
307
308            // Return all the invisible children to the pool
309            HashMap<Task, TaskView> taskChildViewMap = getTaskChildViewMap();
310            int childCount = getChildCount();
311            for (int i = childCount - 1; i >= 0; i--) {
312                TaskView tv = (TaskView) getChildAt(i);
313                Task task = tv.getTask();
314                int taskIndex = mStack.indexOfTask(task);
315                if (taskIndex < visibleRange[1] || taskIndex > visibleRange[0]) {
316                    taskChildViewMap.remove(task);
317                    mViewPool.returnViewToPool(tv);
318                }
319            }
320
321            // Pick up all the newly visible children and update all the existing children
322            boolean isValidVisibleRange = visibleRange[0] != -1 && visibleRange[1] != -1;
323            for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {
324                Task task = tasks.get(i);
325                TaskViewTransform transform = mCurrentTaskTransforms.get(i);
326                TaskView tv = taskChildViewMap.get(task);
327                int taskIndex = mStack.indexOfTask(task);
328
329                if (tv == null) {
330                    tv = mViewPool.pickUpViewFromPool(task, task);
331                    if (mStackViewsAnimationDuration > 0) {
332                        // For items in the list, put them in start animating them from the
333                        // approriate ends of the list where they are expected to appear
334                        Task fromTask = (transform.t < 0) ?
335                                tasks.get(visibleRange[1]) :
336                                tasks.get(visibleRange[0]);
337                        mStack.getGroupIndexForTask(fromTask, gti);
338                        tmpTransform = mStackAlgorithm.getStackTransform(
339                                (transform.t < 0) ? gti.groupIndex - 1 : gti.groupIndex + 1,
340                                (transform.t < 0) ? gti.taskIndex - 1 : gti.taskIndex + 1,
341                                stackScroll, tmpTransform);
342                        tv.updateViewPropertiesToTaskTransform(tmpTransform, 0);
343                    }
344                }
345
346                // Update and animate the task into place
347                tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
348                        mStackViewsAnimationDuration);
349            }
350
351            if (Console.Enabled) {
352                Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
353                        "  [TaskStackView|viewChildren]", "" + getChildCount());
354            }
355
356            mStackViewsAnimationDuration = 0;
357            mStackViewsDirty = false;
358        }
359    }
360
361    /** Sets the current stack scroll */
362    public void setStackScroll(int value) {
363        mStackScroll = value;
364        mUIDozeTrigger.poke();
365        requestSynchronizeStackViewsWithModel();
366    }
367    /** Sets the current stack scroll without synchronizing the stack view with the model */
368    public void setStackScrollRaw(int value) {
369        mStackScroll = value;
370        mUIDozeTrigger.poke();
371    }
372    /** Sets the current stack scroll to the initial state when you first enter recents */
373    public void setStackScrollToInitialState() {
374        setStackScroll(getInitialStackScroll());
375    }
376    /** Computes the initial stack scroll for the stack. */
377    int getInitialStackScroll() {
378        if (mStack.getGroupingCount() > 2) {
379            return mMaxScroll - mStackAlgorithm.mTaskRect.height() / 2;
380        }
381        return mMaxScroll;
382    }
383
384    /** Gets the current stack scroll */
385    public int getStackScroll() {
386        return mStackScroll;
387    }
388
389    /** Animates the stack scroll into bounds */
390    ObjectAnimator animateBoundScroll() {
391        int curScroll = getStackScroll();
392        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
393        if (newScroll != curScroll) {
394            // Enable hw layers on the stack
395            addHwLayersRefCount("animateBoundScroll");
396
397            // Start a new scroll animation
398            animateScroll(curScroll, newScroll, new Runnable() {
399                @Override
400                public void run() {
401                    // Disable hw layers on the stack
402                    decHwLayersRefCount("animateBoundScroll");
403                }
404            });
405        }
406        return mScrollAnimator;
407    }
408
409    /** Animates the stack scroll */
410    void animateScroll(int curScroll, int newScroll, final Runnable postRunnable) {
411        // Abort any current animations
412        abortScroller();
413        abortBoundScrollAnimation();
414
415        mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
416        mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
417                curScroll, 250));
418        mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
419        mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
420            @Override
421            public void onAnimationUpdate(ValueAnimator animation) {
422                setStackScroll((Integer) animation.getAnimatedValue());
423            }
424        });
425        mScrollAnimator.addListener(new AnimatorListenerAdapter() {
426            @Override
427            public void onAnimationEnd(Animator animation) {
428                if (postRunnable != null) {
429                    postRunnable.run();
430                }
431                mScrollAnimator.removeAllListeners();
432            }
433        });
434        mScrollAnimator.start();
435    }
436
437    /** Aborts any current stack scrolls */
438    void abortBoundScrollAnimation() {
439        if (mScrollAnimator != null) {
440            mScrollAnimator.cancel();
441        }
442    }
443
444    /** Aborts the scroller and any current fling */
445    void abortScroller() {
446        if (!mScroller.isFinished()) {
447            // Abort the scroller
448            mScroller.abortAnimation();
449            // And disable hw layers on the stack
450            decHwLayersRefCount("flingScroll");
451        }
452    }
453
454    /** Bounds the current scroll if necessary */
455    public boolean boundScroll() {
456        int curScroll = getStackScroll();
457        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
458        if (newScroll != curScroll) {
459            setStackScroll(newScroll);
460            return true;
461        }
462        return false;
463    }
464
465    /**
466     * Bounds the current scroll if necessary, but does not synchronize the stack view with the
467     * model.
468     */
469    public boolean boundScrollRaw() {
470        int curScroll = getStackScroll();
471        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
472        if (newScroll != curScroll) {
473            setStackScrollRaw(newScroll);
474            return true;
475        }
476        return false;
477    }
478
479
480    /** Returns the amount that the scroll is out of bounds */
481    int getScrollAmountOutOfBounds(int scroll) {
482        if (scroll < mMinScroll) {
483            return mMinScroll - scroll;
484        } else if (scroll > mMaxScroll) {
485            return scroll - mMaxScroll;
486        }
487        return 0;
488    }
489
490    /** Returns whether the specified scroll is out of bounds */
491    boolean isScrollOutOfBounds() {
492        return getScrollAmountOutOfBounds(getStackScroll()) != 0;
493    }
494
495    /** Updates the min and max virtual scroll bounds */
496    void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
497        // Compute the min and max scroll values
498        mStackAlgorithm.computeMinMaxScroll(mStack.getGroupingCount());
499        mMinScroll = mStackAlgorithm.mMinScroll;
500        mMaxScroll = mStackAlgorithm.mMaxScroll;
501
502        // Debug logging
503        if (Constants.Log.UI.MeasureAndLayout) {
504            Console.log("  [TaskStack|minScroll] " + mMinScroll);
505            Console.log("  [TaskStack|maxScroll] " + mMaxScroll);
506        }
507
508        if (boundScrollToNewMinMax) {
509            boundScroll();
510        }
511    }
512
513    /** Animates a task view in this stack as it launches. */
514    public void animateOnLaunchingTask(TaskView tv, final Runnable r) {
515        // Hide each of the task bar dismiss buttons
516        int childCount = getChildCount();
517        for (int i = 0; i < childCount; i++) {
518            TaskView t = (TaskView) getChildAt(i);
519            if (t == tv) {
520                t.startLaunchTaskAnimation(r, true);
521            } else {
522                t.startLaunchTaskAnimation(null, false);
523            }
524        }
525    }
526
527    /** Focuses the task at the specified index in the stack */
528    void focusTask(int taskIndex, boolean scrollToNewPosition) {
529        if (Console.Enabled) {
530            Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "" + taskIndex);
531        }
532        if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
533            mFocusedTaskIndex = taskIndex;
534
535            // Focus the view if possible, otherwise, focus the view after we scroll into position
536            Task t = mStack.getTasks().get(taskIndex);
537            TaskView tv = getChildViewForTask(t);
538            Runnable postScrollRunnable = null;
539            if (tv != null) {
540                tv.setFocusedTask();
541                if (Console.Enabled) {
542                    Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "Requesting focus");
543                }
544            } else {
545                postScrollRunnable = new Runnable() {
546                    @Override
547                    public void run() {
548                        Task t = mStack.getTasks().get(mFocusedTaskIndex);
549                        TaskView tv = getChildViewForTask(t);
550                        if (tv != null) {
551                            tv.setFocusedTask();
552                            if (Console.Enabled) {
553                                Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]",
554                                        "Requesting focus after scroll animation");
555                            }
556                        }
557                    }
558                };
559            }
560
561            if (scrollToNewPosition) {
562                // Scroll the view into position
563                int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll,
564                        mStackAlgorithm.getStackScrollForTaskIndex(taskIndex)));
565
566                animateScroll(getStackScroll(), newScroll, postScrollRunnable);
567            } else {
568                if (postScrollRunnable != null) {
569                    postScrollRunnable.run();
570                }
571            }
572        }
573    }
574
575    /** Focuses the next task in the stack */
576    void focusNextTask(boolean forward) {
577        if (Console.Enabled) {
578            Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusNextTask]", "" +
579                    mFocusedTaskIndex);
580        }
581
582        // Find the next index to focus
583        int numTasks = mStack.getTaskCount();
584        if (mFocusedTaskIndex < 0) {
585            mFocusedTaskIndex = numTasks - 1;
586        }
587        if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) {
588            mFocusedTaskIndex = Math.max(0, Math.min(numTasks - 1,
589                    mFocusedTaskIndex + (forward ? -1 : 1)));
590        }
591        focusTask(mFocusedTaskIndex, true);
592    }
593
594    /** Enables the hw layers and increments the hw layer requirement ref count */
595    void addHwLayersRefCount(String reason) {
596        if (Console.Enabled) {
597            int refCount = mHwLayersTrigger.getCount();
598            Console.log(Constants.Log.UI.HwLayers,
599                    "[TaskStackView|addHwLayersRefCount] refCount: " +
600                            refCount + "->" + (refCount + 1) + " " + reason);
601        }
602        mHwLayersTrigger.increment();
603    }
604
605    /** Decrements the hw layer requirement ref count and disables the hw layers when we don't
606        need them anymore. */
607    void decHwLayersRefCount(String reason) {
608        if (Console.Enabled) {
609            int refCount = mHwLayersTrigger.getCount();
610            Console.log(Constants.Log.UI.HwLayers,
611                    "[TaskStackView|decHwLayersRefCount] refCount: " +
612                            refCount + "->" + (refCount - 1) + " " + reason);
613        }
614        mHwLayersTrigger.decrement();
615    }
616
617    @Override
618    public boolean onInterceptTouchEvent(MotionEvent ev) {
619        return mTouchHandler.onInterceptTouchEvent(ev);
620    }
621
622    @Override
623    public boolean onTouchEvent(MotionEvent ev) {
624        return mTouchHandler.onTouchEvent(ev);
625    }
626
627    @Override
628    public void computeScroll() {
629        if (mScroller.computeScrollOffset()) {
630            setStackScroll(mScroller.getCurrY());
631            invalidate(mStackAlgorithm.mStackRect);
632
633            // If we just finished scrolling, then disable the hw layers
634            if (mScroller.isFinished()) {
635                decHwLayersRefCount("finishedFlingScroll");
636            }
637        }
638    }
639
640    @Override
641    public void dispatchDraw(Canvas canvas) {
642        if (Console.Enabled) {
643            Console.log(Constants.Log.UI.Draw, "[TaskStackView|dispatchDraw]", "",
644                    Console.AnsiPurple);
645        }
646        synchronizeStackViewsWithModel();
647        super.dispatchDraw(canvas);
648    }
649
650    @Override
651    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
652        if (Constants.DebugFlags.App.EnableTaskStackClipping) {
653            TaskView tv = (TaskView) child;
654            TaskView nextTv = null;
655            TaskView tmpTv = null;
656            if (tv.shouldClipViewInStack()) {
657                int curIndex = indexOfChild(tv);
658
659                // Find the next view to clip against
660                while (nextTv == null && curIndex < getChildCount()) {
661                    tmpTv = (TaskView) getChildAt(++curIndex);
662                    if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
663                        nextTv = tmpTv;
664                    }
665                }
666
667                // Clip against the next view (if we aren't animating its alpha)
668                if (nextTv != null) {
669                    Rect curRect = tv.getClippingRect(mTmpRect);
670                    Rect nextRect = nextTv.getClippingRect(mTmpRect2);
671                    // The hit rects are relative to the task view, which needs to be offset by
672                    // the system bar height
673                    curRect.offset(0, mConfig.systemInsets.top);
674                    nextRect.offset(0, mConfig.systemInsets.top);
675                    // Compute the clip region
676                    Region clipRegion = new Region();
677                    clipRegion.op(curRect, Region.Op.UNION);
678                    clipRegion.op(nextRect, Region.Op.DIFFERENCE);
679                    // Clip the canvas
680                    int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
681                    canvas.clipRegion(clipRegion);
682                    boolean invalidate = super.drawChild(canvas, child, drawingTime);
683                    canvas.restoreToCount(saveCount);
684                    return invalidate;
685                }
686            }
687        }
688        return super.drawChild(canvas, child, drawingTime);
689    }
690
691    /** Computes the stack and task rects */
692    public void computeRects(int width, int height, int insetLeft, int insetBottom) {
693        // Compute the rects in the stack algorithm
694        mStackAlgorithm.computeRects(width, height, insetLeft, insetBottom);
695
696        // Update the scroll bounds
697        updateMinMaxScroll(false);
698    }
699
700    /**
701     * This is called with the size of the space not including the top or right insets, or the
702     * search bar height in portrait (but including the search bar width in landscape, since we want
703     * to draw under it.
704     */
705    @Override
706    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
707        int width = MeasureSpec.getSize(widthMeasureSpec);
708        int height = MeasureSpec.getSize(heightMeasureSpec);
709        if (Console.Enabled) {
710            Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|measure]",
711                    "width: " + width + " height: " + height +
712                            " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen);
713        }
714
715        // Compute our stack/task rects
716        Rect taskStackBounds = new Rect();
717        mConfig.getTaskStackBounds(width, height, taskStackBounds);
718        computeRects(width, height, taskStackBounds.left, mConfig.systemInsets.bottom);
719
720        // Debug logging
721        if (Constants.Log.UI.MeasureAndLayout) {
722            Console.log("  [TaskStack|fullRect] " + mStackAlgorithm.mRect);
723            Console.log("  [TaskStack|stackRect] " + mStackAlgorithm.mStackRect);
724            Console.log("  [TaskStack|stackRectSansPeek] " + mStackAlgorithm.mStackRectSansPeek);
725            Console.log("  [TaskStack|taskRect] " + mStackAlgorithm.mTaskRect);
726        }
727
728        // If this is the first layout, then scroll to the front of the stack and synchronize the
729        // stack views immediately
730        if (mAwaitingFirstLayout) {
731            setStackScrollToInitialState();
732            requestSynchronizeStackViewsWithModel();
733            synchronizeStackViewsWithModel();
734        }
735
736        // Measure each of the children
737        int childCount = getChildCount();
738        for (int i = 0; i < childCount; i++) {
739            TaskView t = (TaskView) getChildAt(i);
740            t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
741                    MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height(), MeasureSpec.EXACTLY));
742        }
743
744        setMeasuredDimension(width, height);
745    }
746
747    /**
748     * This is called with the size of the space not including the top or right insets, or the
749     * search bar height in portrait (but including the search bar width in landscape, since we want
750     * to draw under it.
751     */
752    @Override
753    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
754        if (Console.Enabled) {
755            Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|layout]",
756                    "" + new Rect(left, top, right, bottom), Console.AnsiGreen);
757        }
758
759        // Debug logging
760        if (Constants.Log.UI.MeasureAndLayout) {
761            Console.log("  [TaskStack|fullRect] " + mStackAlgorithm.mRect);
762            Console.log("  [TaskStack|stackRect] " + mStackAlgorithm.mStackRect);
763            Console.log("  [TaskStack|stackRectSansPeek] " + mStackAlgorithm.mStackRectSansPeek);
764            Console.log("  [TaskStack|taskRect] " + mStackAlgorithm.mTaskRect);
765        }
766
767        // Layout each of the children
768        int childCount = getChildCount();
769        for (int i = 0; i < childCount; i++) {
770            TaskView t = (TaskView) getChildAt(i);
771            t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
772                    mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
773                    mStackAlgorithm.mTaskRect.height());
774        }
775
776        if (mAwaitingFirstLayout) {
777            // Mark that we have completely the first layout
778            mAwaitingFirstLayout = false;
779
780            // Prepare the first view for its enter animation
781            int offsetTopAlign = -mStackAlgorithm.mTaskRect.top;
782            int offscreenY = mStackAlgorithm.mRect.bottom -
783                    (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
784            for (int i = childCount - 1; i >= 0; i--) {
785                TaskView tv = (TaskView) getChildAt(i);
786                tv.prepareEnterRecentsAnimation((i == (getChildCount() - 1)), offsetTopAlign,
787                        offscreenY, mStackAlgorithm.mTaskRect);
788            }
789
790            // If the enter animation started already and we haven't completed a layout yet, do the
791            // enter animation now
792            if (mStartEnterAnimationRequestedAfterLayout) {
793                startEnterRecentsAnimation(mStartEnterAnimationContext);
794                mStartEnterAnimationRequestedAfterLayout = false;
795                mStartEnterAnimationContext = null;
796            }
797
798            // Update the focused task index to be the next item to the top task
799            if (mConfig.launchedWithAltTab) {
800                // When alt-tabbing, we focus the next previous task
801                focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
802            }
803        }
804    }
805
806    /** Requests this task stacks to start it's enter-recents animation */
807    public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
808        // If we are still waiting to layout, then just defer until then
809        if (mAwaitingFirstLayout) {
810            mStartEnterAnimationRequestedAfterLayout = true;
811            mStartEnterAnimationContext = ctx;
812            return;
813        }
814
815        if (mStack.getTaskCount() > 0) {
816            // Animate all the task views into view
817            TaskViewTransform transform = new TaskViewTransform();
818            TaskStack.GroupTaskIndex groupTaskIndex = new TaskStack.GroupTaskIndex();
819            mStack.getGroupIndexForTask(mStack.getFrontMostTask(), groupTaskIndex);
820            mStackAlgorithm.getStackTransform(groupTaskIndex.groupIndex, groupTaskIndex.taskIndex,
821                    getInitialStackScroll(), transform);
822            ctx.taskRect = transform.rect;
823            ctx.stackRectSansPeek = mStackAlgorithm.mStackRectSansPeek;
824            int childCount = getChildCount();
825            for (int i = childCount - 1; i >= 0; i--) {
826                TaskView tv = (TaskView) getChildAt(i);
827                ctx.stackViewIndex = i;
828                ctx.stackViewCount = childCount;
829                ctx.isFrontMost = (i == (getChildCount() - 1));
830                ctx.transform = new TaskViewTransform();
831                mStack.getGroupIndexForTask(tv.getTask(), groupTaskIndex);
832                mStackAlgorithm.getStackTransform(groupTaskIndex.groupIndex, groupTaskIndex.taskIndex,
833                        getStackScroll(), ctx.transform);
834                tv.startEnterRecentsAnimation(ctx);
835            }
836
837            // Add a runnable to the post animation ref counter to clear all the views
838            ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
839                @Override
840                public void run() {
841                    // Start dozing
842                    mUIDozeTrigger.startDozing();
843                }
844            });
845        }
846    }
847
848    /** Requests this task stacks to start it's exit-recents animation. */
849    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
850        // Animate all the task views into view
851        ctx.offscreenTranslationY = mStackAlgorithm.mRect.bottom -
852                (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
853        int childCount = getChildCount();
854        for (int i = 0; i < childCount; i++) {
855            TaskView tv = (TaskView) getChildAt(i);
856            tv.startExitToHomeAnimation(ctx);
857        }
858
859        // Add a runnable to the post animation ref counter to clear all the views
860        ctx.postAnimationTrigger.addLastDecrementRunnable(mReturnAllViewsToPoolRunnable);
861    }
862
863    @Override
864    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
865        super.onScrollChanged(l, t, oldl, oldt);
866        requestSynchronizeStackViewsWithModel();
867    }
868
869    public boolean isTransformedTouchPointInView(float x, float y, View child) {
870        return isTransformedTouchPointInView(x, y, child, null);
871    }
872
873    /** Pokes the dozer on user interaction. */
874    void onUserInteraction() {
875        // Poke the doze trigger if it is dozing
876        mUIDozeTrigger.poke();
877    }
878
879    /** Disables handling touch on this task view. */
880    void setTouchOnTaskView(TaskView tv, boolean enabled) {
881        tv.setOnClickListener(enabled ? this : null);
882    }
883
884    /**** TaskStackCallbacks Implementation ****/
885
886    @Override
887    public void onStackTaskAdded(TaskStack stack, Task t) {
888        requestSynchronizeStackViewsWithModel();
889    }
890
891    @Override
892    public void onStackTaskRemoved(TaskStack stack, Task t) {
893        // Remove the view associated with this task, we can't rely on updateTransforms
894        // to work here because the task is no longer in the list
895        TaskView tv = getChildViewForTask(t);
896        if (tv != null) {
897            mViewPool.returnViewToPool(tv);
898        }
899
900        // Notify the callback that we've removed the task and it can clean up after it
901        mCb.onTaskViewDismissed(t);
902
903        // Update the min/max scroll and animate other task views into their new positions
904        updateMinMaxScroll(true);
905        int movement = (int) mStackAlgorithm.getTaskOverlapHeight();
906        requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
907
908        // If there are no remaining tasks, then either unfilter the current stack, or just close
909        // the activity if there are no filtered stacks
910        if (mStack.getTaskCount() == 0) {
911            boolean shouldFinishActivity = true;
912            if (mStack.hasFilteredTasks()) {
913                mStack.unfilterTasks();
914                shouldFinishActivity = (mStack.getTaskCount() == 0);
915            }
916            if (shouldFinishActivity) {
917                mCb.onAllTaskViewsDismissed();
918            }
919        }
920    }
921
922    @Override
923    public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
924                                Task filteredTask) {
925        // Stash the scroll and filtered task for us to restore to when we unfilter
926        mStashedScroll = getStackScroll();
927
928        // Calculate the current task transforms
929        ArrayList<TaskViewTransform> curTaskTransforms =
930                getStackTransforms(curTasks, getStackScroll(), null, true);
931
932        // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
933        updateMinMaxScroll(false);
934        float overlapHeight = mStackAlgorithm.getTaskOverlapHeight();
935        setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
936        boundScrollRaw();
937
938        // Compute the transforms of the items in the new stack after setting the new scroll
939        final ArrayList<Task> tasks = mStack.getTasks();
940        final ArrayList<TaskViewTransform> taskTransforms =
941                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
942
943        // Animate
944        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
945
946        // Notify any callbacks
947        mCb.onTaskStackFilterTriggered();
948    }
949
950    @Override
951    public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
952        // Calculate the current task transforms
953        final ArrayList<TaskViewTransform> curTaskTransforms =
954                getStackTransforms(curTasks, getStackScroll(), null, true);
955
956        // Restore the stashed scroll
957        updateMinMaxScroll(false);
958        setStackScrollRaw(mStashedScroll);
959        boundScrollRaw();
960
961        // Compute the transforms of the items in the new stack after restoring the stashed scroll
962        final ArrayList<Task> tasks = mStack.getTasks();
963        final ArrayList<TaskViewTransform> taskTransforms =
964                getStackTransforms(tasks, getStackScroll(), null, true);
965
966        // Animate
967        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
968
969        // Clear the saved vars
970        mStashedScroll = 0;
971
972        // Notify any callbacks
973        mCb.onTaskStackUnfilterTriggered();
974    }
975
976    /**** ViewPoolConsumer Implementation ****/
977
978    @Override
979    public TaskView createView(Context context) {
980        if (Console.Enabled) {
981            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]");
982        }
983        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
984    }
985
986    @Override
987    public void prepareViewToEnterPool(TaskView tv) {
988        Task task = tv.getTask();
989        if (Console.Enabled) {
990            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]",
991                    tv.getTask() + " tv: " + tv);
992        }
993
994        // Report that this tasks's data is no longer being used
995        RecentsTaskLoader.getInstance().unloadTaskData(task);
996
997        // Detach the view from the hierarchy
998        detachViewFromParent(tv);
999
1000        // Disable HW layers
1001        tv.disableHwLayers();
1002
1003        // Reset the view properties
1004        tv.resetViewProperties();
1005    }
1006
1007    @Override
1008    public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
1009        if (Console.Enabled) {
1010            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]",
1011                    "isNewView: " + isNewView);
1012        }
1013
1014        // Rebind the task and request that this task's data be filled into the TaskView
1015        tv.onTaskBound(task);
1016        RecentsTaskLoader.getInstance().loadTaskData(task);
1017
1018        // Sanity check, the task view should always be clipping against the stack at this point,
1019        // but just in case, re-enable it here
1020        tv.setClipViewInStack(true);
1021
1022        // If the doze trigger has already fired, then update the state for this task view
1023        if (mUIDozeTrigger.hasTriggered()) {
1024            tv.setNoUserInteractionState();
1025        }
1026
1027        // Find the index where this task should be placed in the stack
1028        int insertIndex = -1;
1029        int taskIndex = mStack.indexOfTask(task);
1030        if (taskIndex != -1) {
1031            int childCount = getChildCount();
1032            for (int i = 0; i < childCount; i++) {
1033                Task tvTask = ((TaskView) getChildAt(i)).getTask();
1034                if (taskIndex < mStack.indexOfTask(tvTask)) {
1035                    insertIndex = i;
1036                    break;
1037                }
1038            }
1039        }
1040
1041        // Add/attach the view to the hierarchy
1042        if (Console.Enabled) {
1043            Console.log(Constants.Log.ViewPool.PoolCallbacks, "  [TaskStackView|insertIndex]",
1044                    "" + insertIndex);
1045        }
1046        if (isNewView) {
1047            addView(tv, insertIndex);
1048
1049            // Set the callbacks and listeners for this new view
1050            setTouchOnTaskView(tv, true);
1051            tv.setCallbacks(this);
1052        } else {
1053            attachViewToParent(tv, insertIndex, tv.getLayoutParams());
1054        }
1055
1056        // Enable hw layers on this view if hw layers are enabled on the stack
1057        if (mHwLayersTrigger.getCount() > 0) {
1058            tv.enableHwLayers();
1059        }
1060    }
1061
1062    @Override
1063    public boolean hasPreferredData(TaskView tv, Task preferredData) {
1064        return (tv.getTask() == preferredData);
1065    }
1066
1067    /**** TaskViewCallbacks Implementation ****/
1068
1069    @Override
1070    public void onTaskViewAppIconClicked(TaskView tv) {
1071        if (Console.Enabled) {
1072            Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Icon]",
1073                    tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(),
1074                    Console.AnsiCyan);
1075        }
1076        if (Constants.DebugFlags.App.EnableTaskFiltering) {
1077            if (mStack.hasFilteredTasks()) {
1078                mStack.unfilterTasks();
1079            } else {
1080                mStack.filterTasks(tv.getTask());
1081            }
1082        }
1083    }
1084
1085    @Override
1086    public void onTaskViewAppInfoClicked(TaskView tv) {
1087        if (mCb != null) {
1088            mCb.onTaskViewAppInfoClicked(tv.getTask());
1089        }
1090    }
1091
1092    @Override
1093    public void onTaskViewDismissed(TaskView tv) {
1094        Task task = tv.getTask();
1095        // Remove the task from the view
1096        mStack.removeTask(task);
1097    }
1098
1099    /**** View.OnClickListener Implementation ****/
1100
1101    @Override
1102    public void onClick(View v) {
1103        TaskView tv = (TaskView) v;
1104        Task task = tv.getTask();
1105        if (Console.Enabled) {
1106            Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
1107                    task + " cb: " + mCb);
1108        }
1109
1110        // Cancel any doze triggers
1111        mUIDozeTrigger.stopDozing();
1112
1113        if (mCb != null) {
1114            mCb.onTaskViewClicked(this, tv, mStack, task);
1115        }
1116    }
1117
1118    /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
1119
1120    @Override
1121    public void onComponentRemoved(Set<ComponentName> cns) {
1122        // For other tasks, just remove them directly if they no longer exist
1123        ArrayList<Task> tasks = mStack.getTasks();
1124        for (int i = tasks.size() - 1; i >= 0; i--) {
1125            final Task t = tasks.get(i);
1126            if (cns.contains(t.key.baseIntent.getComponent())) {
1127                TaskView tv = getChildViewForTask(t);
1128                if (tv != null) {
1129                    // For visible children, defer removing the task until after the animation
1130                    tv.startDeleteTaskAnimation(new Runnable() {
1131                        @Override
1132                        public void run() {
1133                            mStack.removeTask(t);
1134                        }
1135                    });
1136                } else {
1137                    // Otherwise, remove the task from the stack immediately
1138                    mStack.removeTask(t);
1139                }
1140            }
1141        }
1142    }
1143}