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