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