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