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