TaskStackView.java revision fe03b40da4a513af759851dd399e39dfa90c3363
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.ValueAnimator;
20import android.content.ComponentName;
21import android.content.Context;
22import android.graphics.Matrix;
23import android.graphics.Rect;
24import android.view.LayoutInflater;
25import android.view.MotionEvent;
26import android.view.View;
27import android.view.accessibility.AccessibilityEvent;
28import android.widget.FrameLayout;
29import com.android.systemui.R;
30import com.android.systemui.recents.Constants;
31import com.android.systemui.recents.RecentsConfiguration;
32import com.android.systemui.recents.misc.DozeTrigger;
33import com.android.systemui.recents.misc.SystemServicesProxy;
34import com.android.systemui.recents.misc.Utilities;
35import com.android.systemui.recents.model.RecentsPackageMonitor;
36import com.android.systemui.recents.model.RecentsTaskLoader;
37import com.android.systemui.recents.model.Task;
38import com.android.systemui.recents.model.TaskStack;
39
40import java.util.ArrayList;
41import java.util.HashMap;
42import java.util.HashSet;
43import java.util.Iterator;
44
45
46/* The visual representation of a task stack view */
47public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
48        TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks,
49        ViewPool.ViewPoolConsumer<TaskView, Task>, RecentsPackageMonitor.PackageCallbacks {
50
51    /** The TaskView callbacks */
52    interface TaskStackViewCallbacks {
53        public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
54                                      boolean lockToTask);
55        public void onTaskViewAppInfoClicked(Task t);
56        public void onTaskViewDismissed(Task t);
57        public void onAllTaskViewsDismissed();
58        public void onTaskStackFilterTriggered();
59        public void onTaskStackUnfilterTriggered();
60    }
61
62    RecentsConfiguration mConfig;
63
64    TaskStack mStack;
65    TaskStackViewLayoutAlgorithm mLayoutAlgorithm;
66    TaskStackViewFilterAlgorithm mFilterAlgorithm;
67    TaskStackViewScroller mStackScroller;
68    TaskStackViewTouchHandler mTouchHandler;
69    TaskStackViewCallbacks mCb;
70    ViewPool<TaskView, Task> mViewPool;
71    ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
72    DozeTrigger mUIDozeTrigger;
73    DebugOverlayView mDebugOverlay;
74    Rect mTaskStackBounds = new Rect();
75    int mFocusedTaskIndex = -1;
76    int mPrevAccessibilityFocusedIndex = -1;
77
78    // Optimizations
79    int mStackViewsAnimationDuration;
80    boolean mStackViewsDirty = true;
81    boolean mStackViewsClipDirty = true;
82    boolean mAwaitingFirstLayout = true;
83    boolean mStartEnterAnimationRequestedAfterLayout;
84    boolean mStartEnterAnimationCompleted;
85    ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
86    int[] mTmpVisibleRange = new int[2];
87    float[] mTmpCoord = new float[2];
88    Matrix mTmpMatrix = new Matrix();
89    Rect mTmpRect = new Rect();
90    TaskViewTransform mTmpTransform = new TaskViewTransform();
91    HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>();
92    LayoutInflater mInflater;
93
94    // A convenience update listener to request updating clipping of tasks
95    ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
96            new ValueAnimator.AnimatorUpdateListener() {
97        @Override
98        public void onAnimationUpdate(ValueAnimator animation) {
99            requestUpdateStackViewsClip();
100        }
101    };
102
103    public TaskStackView(Context context, TaskStack stack) {
104        super(context);
105        // Set the stack first
106        setStack(stack);
107        mConfig = RecentsConfiguration.getInstance();
108        mViewPool = new ViewPool<TaskView, Task>(context, this);
109        mInflater = LayoutInflater.from(context);
110        mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig);
111        mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool);
112        mStackScroller = new TaskStackViewScroller(context, mConfig, mLayoutAlgorithm);
113        mStackScroller.setCallbacks(this);
114        mTouchHandler = new TaskStackViewTouchHandler(context, this, mConfig, mStackScroller);
115        mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {
116            @Override
117            public void run() {
118                // Show the task bar dismiss buttons
119                int childCount = getChildCount();
120                for (int i = 0; i < childCount; i++) {
121                    TaskView tv = (TaskView) getChildAt(i);
122                    tv.startNoUserInteractionAnimation();
123                }
124            }
125        });
126    }
127
128    /** Sets the callbacks */
129    void setCallbacks(TaskStackViewCallbacks cb) {
130        mCb = cb;
131    }
132
133    /** Sets the task stack */
134    void setStack(TaskStack stack) {
135        // Unset the old stack
136        if (mStack != null) {
137            mStack.setCallbacks(null);
138
139            // Return all existing views to the pool
140            reset();
141            // Layout again with the new stack
142            requestLayout();
143        }
144
145        // Set the new stack
146        mStack = stack;
147        if (mStack != null) {
148            mStack.setCallbacks(this);
149        }
150    }
151
152    /** Sets the debug overlay */
153    public void setDebugOverlay(DebugOverlayView overlay) {
154        mDebugOverlay = overlay;
155    }
156
157    /** Resets this TaskStackView for reuse. */
158    void reset() {
159        // Reset the focused task
160        resetFocusedTask();
161
162        // Return all the views to the pool
163        int childCount = getChildCount();
164        for (int i = childCount - 1; i >= 0; i--) {
165            TaskView tv = (TaskView) getChildAt(i);
166            mViewPool.returnViewToPool(tv);
167        }
168
169        // Mark each task view for relayout
170        if (mViewPool != null) {
171            Iterator<TaskView> iter = mViewPool.poolViewIterator();
172            if (iter != null) {
173                while (iter.hasNext()) {
174                    TaskView tv = iter.next();
175                    tv.reset();
176                }
177            }
178        }
179
180        // Reset the stack state
181        mStackViewsDirty = true;
182        mStackViewsClipDirty = true;
183        mAwaitingFirstLayout = true;
184        mPrevAccessibilityFocusedIndex = -1;
185        if (mUIDozeTrigger != null) {
186            mUIDozeTrigger.stopDozing();
187            mUIDozeTrigger.resetTrigger();
188        }
189    }
190
191    /** Requests that the views be synchronized with the model */
192    void requestSynchronizeStackViewsWithModel() {
193        requestSynchronizeStackViewsWithModel(0);
194    }
195    void requestSynchronizeStackViewsWithModel(int duration) {
196        if (!mStackViewsDirty) {
197            invalidate();
198            mStackViewsDirty = true;
199        }
200        if (mAwaitingFirstLayout) {
201            // Skip the animation if we are awaiting first layout
202            mStackViewsAnimationDuration = 0;
203        } else {
204            mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
205        }
206    }
207
208    /** Requests that the views clipping be updated. */
209    void requestUpdateStackViewsClip() {
210        if (!mStackViewsClipDirty) {
211            invalidate();
212            mStackViewsClipDirty = true;
213        }
214    }
215
216    /** Finds the child view given a specific task. */
217    public TaskView getChildViewForTask(Task t) {
218        int childCount = getChildCount();
219        for (int i = 0; i < childCount; i++) {
220            TaskView tv = (TaskView) getChildAt(i);
221            if (tv.getTask() == t) {
222                return tv;
223            }
224        }
225        return null;
226    }
227
228    /** Returns the stack algorithm for this task stack. */
229    public TaskStackViewLayoutAlgorithm getStackAlgorithm() {
230        return mLayoutAlgorithm;
231    }
232
233    /**
234     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
235     */
236    private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
237                                       ArrayList<Task> tasks,
238                                       float stackScroll,
239                                       int[] visibleRangeOut,
240                                       boolean boundTranslationsToRect) {
241        // XXX: We should be intelligent about where to look for the visible stack range using the
242        //      current stack scroll.
243        // XXX: We should log extra cases like the ones below where we don't expect to hit very often
244        // XXX: Print out approximately how many indices we have to go through to find the first visible transform
245
246        int taskTransformCount = taskTransforms.size();
247        int taskCount = tasks.size();
248        int frontMostVisibleIndex = -1;
249        int backMostVisibleIndex = -1;
250
251        // We can reuse the task transforms where possible to reduce object allocation
252        if (taskTransformCount < taskCount) {
253            // If there are less transforms than tasks, then add as many transforms as necessary
254            for (int i = taskTransformCount; i < taskCount; i++) {
255                taskTransforms.add(new TaskViewTransform());
256            }
257        } else if (taskTransformCount > taskCount) {
258            // If there are more transforms than tasks, then just subset the transform list
259            taskTransforms.subList(0, taskCount);
260        }
261
262        // Update the stack transforms
263        TaskViewTransform prevTransform = null;
264        for (int i = taskCount - 1; i >= 0; i--) {
265            TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(tasks.get(i),
266                    stackScroll, taskTransforms.get(i), prevTransform);
267            if (transform.visible) {
268                if (frontMostVisibleIndex < 0) {
269                    frontMostVisibleIndex = i;
270                }
271                backMostVisibleIndex = i;
272            } else {
273                if (backMostVisibleIndex != -1) {
274                    // We've reached the end of the visible range, so going down the rest of the
275                    // stack, we can just reset the transforms accordingly
276                    while (i >= 0) {
277                        taskTransforms.get(i).reset();
278                        i--;
279                    }
280                    break;
281                }
282            }
283
284            if (boundTranslationsToRect) {
285                transform.translationY = Math.min(transform.translationY,
286                        mLayoutAlgorithm.mViewRect.bottom);
287            }
288            prevTransform = transform;
289        }
290        if (visibleRangeOut != null) {
291            visibleRangeOut[0] = frontMostVisibleIndex;
292            visibleRangeOut[1] = backMostVisibleIndex;
293        }
294        return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1;
295    }
296
297    /**
298     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This
299     * call is less optimal than calling updateStackTransforms directly.
300     */
301    private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
302                                                            float stackScroll,
303                                                            int[] visibleRangeOut,
304                                                            boolean boundTranslationsToRect) {
305        ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
306        updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut,
307                boundTranslationsToRect);
308        return taskTransforms;
309    }
310
311    /** Synchronizes the views with the model */
312    boolean synchronizeStackViewsWithModel() {
313        if (mStackViewsDirty) {
314            RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
315            SystemServicesProxy ssp = loader.getSystemServicesProxy();
316
317            // Get all the task transforms
318            ArrayList<Task> tasks = mStack.getTasks();
319            float stackScroll = mStackScroller.getStackScroll();
320            int[] visibleRange = mTmpVisibleRange;
321            boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
322                    stackScroll, visibleRange, false);
323            if (mDebugOverlay != null) {
324                mDebugOverlay.setText("vis[" + visibleRange[1] + "-" + visibleRange[0] + "]");
325            }
326
327            // Return all the invisible children to the pool
328            mTmpTaskViewMap.clear();
329            int childCount = getChildCount();
330            for (int i = childCount - 1; i >= 0; i--) {
331                TaskView tv = (TaskView) getChildAt(i);
332                Task task = tv.getTask();
333                int taskIndex = mStack.indexOfTask(task);
334                if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
335                    mTmpTaskViewMap.put(task, tv);
336                } else {
337                    mViewPool.returnViewToPool(tv);
338                }
339            }
340
341            // Pick up all the newly visible children and update all the existing children
342            for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {
343                Task task = tasks.get(i);
344                TaskViewTransform transform = mCurrentTaskTransforms.get(i);
345                TaskView tv = mTmpTaskViewMap.get(task);
346                int taskIndex = mStack.indexOfTask(task);
347
348                if (tv == null) {
349                    tv = mViewPool.pickUpViewFromPool(task, task);
350
351                    if (mStackViewsAnimationDuration > 0) {
352                        // For items in the list, put them in start animating them from the
353                        // approriate ends of the list where they are expected to appear
354                        if (Float.compare(transform.p, 0f) <= 0) {
355                            mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null);
356                        } else {
357                            mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null);
358                        }
359                        tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
360                    }
361                }
362
363                // Animate the task into place
364                tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
365                        mStackViewsAnimationDuration, mRequestUpdateClippingListener);
366
367                // Request accessibility focus on the next view if we removed the task
368                // that previously held accessibility focus
369                childCount = getChildCount();
370                if (childCount > 0 && ssp.isTouchExplorationEnabled()) {
371                    TaskView atv = (TaskView) getChildAt(childCount - 1);
372                    int indexOfTask = mStack.indexOfTask(atv.getTask());
373                    if (mPrevAccessibilityFocusedIndex != indexOfTask) {
374                        tv.requestAccessibilityFocus();
375                        mPrevAccessibilityFocusedIndex = indexOfTask;
376                    }
377                }
378            }
379
380            // Reset the request-synchronize params
381            mStackViewsAnimationDuration = 0;
382            mStackViewsDirty = false;
383            mStackViewsClipDirty = true;
384            return true;
385        }
386        return false;
387    }
388
389    /** Updates the clip for each of the task views. */
390    void clipTaskViews() {
391        // Update the clip on each task child
392        if (Constants.DebugFlags.App.EnableTaskStackClipping) {
393            int childCount = getChildCount();
394            for (int i = 0; i < childCount - 1; i++) {
395                TaskView tv = (TaskView) getChildAt(i);
396                TaskView nextTv = null;
397                TaskView tmpTv = null;
398                int clipBottom = 0;
399                if (tv.shouldClipViewInStack()) {
400                    // Find the next view to clip against
401                    int nextIndex = i;
402                    while (nextIndex < getChildCount()) {
403                        tmpTv = (TaskView) getChildAt(++nextIndex);
404                        if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
405                            nextTv = tmpTv;
406                            break;
407                        }
408                    }
409
410                    // Clip against the next view, this is just an approximation since we are
411                    // stacked and we can make assumptions about the visibility of the this
412                    // task relative to the ones in front of it.
413                    if (nextTv != null) {
414                        // Map the top edge of next task view into the local space of the current
415                        // task view to find the clip amount in local space
416                        mTmpCoord[0] = mTmpCoord[1] = 0;
417                        Utilities.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false);
418                        Utilities.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix);
419                        clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1]
420                                - nextTv.getPaddingTop() - 1);
421                    }
422                }
423                tv.getViewBounds().setClipBottom(clipBottom);
424            }
425            if (getChildCount() > 0) {
426                // The front most task should never be clipped
427                TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
428                tv.getViewBounds().setClipBottom(0);
429            }
430        }
431        mStackViewsClipDirty = false;
432    }
433
434    /** The stack insets to apply to the stack contents */
435    public void setStackInsetRect(Rect r) {
436        mTaskStackBounds.set(r);
437    }
438
439    /** Updates the min and max virtual scroll bounds */
440    void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab,
441            boolean launchedFromHome) {
442        if (mStack == null) return;
443
444        // Compute the min and max scroll values
445        mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks(), launchedWithAltTab, launchedFromHome);
446
447        // Debug logging
448        if (boundScrollToNewMinMax) {
449            mStackScroller.boundScroll();
450        }
451    }
452
453    /** Returns the scroller. */
454    public TaskStackViewScroller getScroller() {
455        return mStackScroller;
456    }
457
458    /** Focuses the task at the specified index in the stack */
459    void focusTask(int taskIndex, boolean scrollToNewPosition, final boolean animateFocusedState) {
460        // Return early if the task is already focused
461        if (taskIndex == mFocusedTaskIndex) return;
462
463        if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
464            mFocusedTaskIndex = taskIndex;
465
466            // Focus the view if possible, otherwise, focus the view after we scroll into position
467            Task t = mStack.getTasks().get(taskIndex);
468            TaskView tv = getChildViewForTask(t);
469            Runnable postScrollRunnable = null;
470            if (tv != null) {
471                tv.setFocusedTask(animateFocusedState);
472            } else {
473                postScrollRunnable = new Runnable() {
474                    @Override
475                    public void run() {
476                        Task t = mStack.getTasks().get(mFocusedTaskIndex);
477                        TaskView tv = getChildViewForTask(t);
478                        if (tv != null) {
479                            tv.setFocusedTask(animateFocusedState);
480                        }
481                    }
482                };
483            }
484
485            // Scroll the view into position (just center it in the curve)
486            if (scrollToNewPosition) {
487                float newScroll = mLayoutAlgorithm.getStackScrollForTask(t) - 0.5f;
488                newScroll = mStackScroller.getBoundedStackScroll(newScroll);
489                mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, postScrollRunnable);
490            } else {
491                if (postScrollRunnable != null) {
492                    postScrollRunnable.run();
493                }
494            }
495
496        }
497    }
498
499    /**
500     * Ensures that there is a task focused, if nothign is focused, then we will use the task
501     * at the center of the visible stack.
502     */
503    public boolean ensureFocusedTask() {
504        if (mFocusedTaskIndex < 0) {
505            // If there is no task focused, then find the task that is closes to the center
506            // of the screen and use that as the currently focused task
507            int x = mLayoutAlgorithm.mStackVisibleRect.centerX();
508            int y = mLayoutAlgorithm.mStackVisibleRect.centerY();
509            int childCount = getChildCount();
510            for (int i = childCount - 1; i >= 0; i--) {
511                TaskView tv = (TaskView) getChildAt(i);
512                tv.getHitRect(mTmpRect);
513                if (mTmpRect.contains(x, y)) {
514                    mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
515                    break;
516                }
517            }
518            // If we can't find the center task, then use the front most index
519            if (mFocusedTaskIndex < 0 && childCount > 0) {
520                mFocusedTaskIndex = childCount - 1;
521            }
522        }
523        return mFocusedTaskIndex >= 0;
524    }
525
526    /**
527     * Focuses the next task in the stack.
528     * @param animateFocusedState determines whether to actually draw the highlight along with
529     *                            the change in focus, as well as whether to scroll to fit the
530     *                            task into view.
531     */
532    public void focusNextTask(boolean forward, boolean animateFocusedState) {
533        // Find the next index to focus
534        int numTasks = mStack.getTaskCount();
535        if (numTasks == 0) return;
536
537        int direction = (forward ? -1 : 1);
538        int newIndex = mFocusedTaskIndex + direction;
539        if (newIndex >= 0 && newIndex <= (numTasks - 1)) {
540            newIndex = Math.max(0, Math.min(numTasks - 1, newIndex));
541            focusTask(newIndex, true, animateFocusedState);
542        }
543    }
544
545    /** Dismisses the focused task. */
546    public void dismissFocusedTask() {
547        // Return early if there is no focused task index
548        if (mFocusedTaskIndex < 0) return;
549
550        Task t = mStack.getTasks().get(mFocusedTaskIndex);
551        TaskView tv = getChildViewForTask(t);
552        tv.dismissTask();
553    }
554
555    /** Resets the focused task. */
556    void resetFocusedTask() {
557        if ((mStack != null) && (0 <= mFocusedTaskIndex) &&
558                (mFocusedTaskIndex < mStack.getTaskCount())) {
559            Task t = mStack.getTasks().get(mFocusedTaskIndex);
560            TaskView tv = getChildViewForTask(t);
561            tv.unsetFocusedTask();
562        }
563        mFocusedTaskIndex = -1;
564    }
565
566    @Override
567    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
568        super.onInitializeAccessibilityEvent(event);
569        if (mStack == null) return;
570
571        int childCount = getChildCount();
572        if (childCount > 0) {
573            TaskView backMostTask = (TaskView) getChildAt(0);
574            TaskView frontMostTask = (TaskView) getChildAt(childCount - 1);
575            event.setFromIndex(mStack.indexOfTask(backMostTask.getTask()));
576            event.setToIndex(mStack.indexOfTask(frontMostTask.getTask()));
577            event.setContentDescription(frontMostTask.getTask().activityLabel);
578        }
579        event.setItemCount(mStack.getTaskCount());
580        event.setScrollY(mStackScroller.mScroller.getCurrY());
581        event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP));
582    }
583
584    @Override
585    public boolean onInterceptTouchEvent(MotionEvent ev) {
586        return mTouchHandler.onInterceptTouchEvent(ev);
587    }
588
589    @Override
590    public boolean onTouchEvent(MotionEvent ev) {
591        return mTouchHandler.onTouchEvent(ev);
592    }
593
594    @Override
595    public boolean onGenericMotionEvent(MotionEvent ev) {
596        return mTouchHandler.onGenericMotionEvent(ev);
597    }
598
599    @Override
600    public void computeScroll() {
601        if (mStack == null) return;
602
603        mStackScroller.computeScroll();
604        // Synchronize the views
605        synchronizeStackViewsWithModel();
606        clipTaskViews();
607        // Notify accessibility
608        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
609    }
610
611    /** Computes the stack and task rects */
612    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds,
613            boolean launchedWithAltTab, boolean launchedFromHome) {
614        // Compute the rects in the stack algorithm
615        mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds);
616
617        // Update the scroll bounds
618        updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome);
619    }
620
621    /**
622     * This is ONLY used from AlternateRecentsComponent to update the dummy stack view for purposes
623     * of getting the task rect to animate to.
624     */
625    public void updateMinMaxScrollForStack(TaskStack stack, boolean launchedWithAltTab,
626            boolean launchedFromHome) {
627        mStack = stack;
628        updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome);
629    }
630
631    /**
632     * Computes the maximum number of visible tasks and thumbnails.  Requires that
633     * updateMinMaxScrollForStack() is called first.
634     */
635    public TaskStackViewLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
636        return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
637    }
638
639    /**
640     * This is called with the full window width and height to allow stack view children to
641     * perform the full screen transition down.
642     */
643    @Override
644    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
645        if (mStack == null) {
646            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
647            return;
648        }
649
650        int width = MeasureSpec.getSize(widthMeasureSpec);
651        int height = MeasureSpec.getSize(heightMeasureSpec);
652
653        // Compute our stack/task rects
654        Rect taskStackBounds = new Rect(mTaskStackBounds);
655        taskStackBounds.bottom -= mConfig.systemInsets.bottom;
656        computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab,
657                mConfig.launchedFromHome);
658
659        // If this is the first layout, then scroll to the front of the stack and synchronize the
660        // stack views immediately to load all the views
661        if (mAwaitingFirstLayout) {
662            mStackScroller.setStackScrollToInitialState();
663            requestSynchronizeStackViewsWithModel();
664            synchronizeStackViewsWithModel();
665        }
666
667        // Measure each of the TaskViews
668        int childCount = getChildCount();
669        for (int i = 0; i < childCount; i++) {
670            TaskView tv = (TaskView) getChildAt(i);
671            if (tv.getBackground() != null) {
672                tv.getBackground().getPadding(mTmpRect);
673            } else {
674                mTmpRect.setEmpty();
675            }
676            tv.measure(
677                MeasureSpec.makeMeasureSpec(
678                        mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
679                        MeasureSpec.EXACTLY),
680                MeasureSpec.makeMeasureSpec(
681                        mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
682                        MeasureSpec.EXACTLY));
683        }
684
685        setMeasuredDimension(width, height);
686    }
687
688    /**
689     * This is called with the size of the space not including the top or right insets, or the
690     * search bar height in portrait (but including the search bar width in landscape, since we want
691     * to draw under it.
692     */
693    @Override
694    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
695        if (mStack == null) {
696            super.onLayout(changed, left, top, right, bottom);
697            return;
698        }
699
700        // Layout each of the children
701        int childCount = getChildCount();
702        for (int i = 0; i < childCount; i++) {
703            TaskView tv = (TaskView) getChildAt(i);
704            if (tv.getBackground() != null) {
705                tv.getBackground().getPadding(mTmpRect);
706            } else {
707                mTmpRect.setEmpty();
708            }
709            tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left,
710                    mLayoutAlgorithm.mTaskRect.top - mTmpRect.top,
711                    mLayoutAlgorithm.mTaskRect.right + mTmpRect.right,
712                    mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom);
713        }
714
715        if (mAwaitingFirstLayout) {
716            mAwaitingFirstLayout = false;
717            onFirstLayout();
718        }
719    }
720
721    /** Handler for the first layout. */
722    void onFirstLayout() {
723        int offscreenY = mLayoutAlgorithm.mViewRect.bottom -
724                (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);
725
726        // Find the launch target task
727        Task launchTargetTask = null;
728        int childCount = getChildCount();
729        for (int i = childCount - 1; i >= 0; i--) {
730            TaskView tv = (TaskView) getChildAt(i);
731            Task task = tv.getTask();
732            if (task.isLaunchTarget) {
733                launchTargetTask = task;
734                break;
735            }
736        }
737
738        // Prepare the first view for its enter animation
739        for (int i = childCount - 1; i >= 0; i--) {
740            TaskView tv = (TaskView) getChildAt(i);
741            Task task = tv.getTask();
742            boolean occludesLaunchTarget = (launchTargetTask != null) &&
743                    launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
744            tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY);
745        }
746
747        // If the enter animation started already and we haven't completed a layout yet, do the
748        // enter animation now
749        if (mStartEnterAnimationRequestedAfterLayout) {
750            startEnterRecentsAnimation(mStartEnterAnimationContext);
751            mStartEnterAnimationRequestedAfterLayout = false;
752            mStartEnterAnimationContext = null;
753        }
754
755        // When Alt-Tabbing, we scroll to and focus the previous task
756        if (mConfig.launchedWithAltTab) {
757            if (mConfig.launchedFromHome) {
758                focusTask(Math.max(0, mStack.getTaskCount() - 1), false, true);
759            } else {
760                focusTask(Math.max(0, mStack.getTaskCount() - 2), false, true);
761            }
762        }
763    }
764
765    /** Requests this task stacks to start it's enter-recents animation */
766    public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
767        // If we are still waiting to layout, then just defer until then
768        if (mAwaitingFirstLayout) {
769            mStartEnterAnimationRequestedAfterLayout = true;
770            mStartEnterAnimationContext = ctx;
771            return;
772        }
773
774        if (mStack.getTaskCount() > 0) {
775            // Find the launch target task
776            Task launchTargetTask = null;
777            int childCount = getChildCount();
778            for (int i = childCount - 1; i >= 0; i--) {
779                TaskView tv = (TaskView) getChildAt(i);
780                Task task = tv.getTask();
781                if (task.isLaunchTarget) {
782                    launchTargetTask = task;
783                    break;
784                }
785            }
786
787            // Animate all the task views into view
788            for (int i = childCount - 1; i >= 0; i--) {
789                TaskView tv = (TaskView) getChildAt(i);
790                Task task = tv.getTask();
791                ctx.currentTaskTransform = new TaskViewTransform();
792                ctx.currentStackViewIndex = i;
793                ctx.currentStackViewCount = childCount;
794                ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect;
795                ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) &&
796                        launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
797                ctx.updateListener = mRequestUpdateClippingListener;
798                mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null);
799                tv.startEnterRecentsAnimation(ctx);
800            }
801
802            // Add a runnable to the post animation ref counter to clear all the views
803            ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
804                @Override
805                public void run() {
806                    mStartEnterAnimationCompleted = true;
807                    // Start dozing
808                    mUIDozeTrigger.startDozing();
809                    // Focus the first view if accessibility is enabled
810                    RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
811                    SystemServicesProxy ssp = loader.getSystemServicesProxy();
812                    int childCount = getChildCount();
813                    if (childCount > 0 && ssp.isTouchExplorationEnabled()) {
814                        TaskView tv = ((TaskView) getChildAt(childCount - 1));
815                        tv.requestAccessibilityFocus();
816                        mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask());
817                    }
818                }
819            });
820        }
821    }
822
823    /** Requests this task stacks to start it's exit-recents animation. */
824    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
825        // Stop any scrolling
826        mStackScroller.stopScroller();
827        mStackScroller.stopBoundScrollAnimation();
828        // Animate all the task views out of view
829        ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom -
830                (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);
831        int childCount = getChildCount();
832        for (int i = 0; i < childCount; i++) {
833            TaskView tv = (TaskView) getChildAt(i);
834            tv.startExitToHomeAnimation(ctx);
835        }
836    }
837
838    /** Animates a task view in this stack as it launches. */
839    public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) {
840        Task launchTargetTask = tv.getTask();
841        int childCount = getChildCount();
842        for (int i = 0; i < childCount; i++) {
843            TaskView t = (TaskView) getChildAt(i);
844            if (t == tv) {
845                t.setClipViewInStack(false);
846                t.startLaunchTaskAnimation(r, true, true, lockToTask);
847            } else {
848                boolean occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(t.getTask(),
849                        launchTargetTask);
850                t.startLaunchTaskAnimation(null, false, occludesLaunchTarget, lockToTask);
851            }
852        }
853    }
854
855    /** Final callback after Recents is finally hidden. */
856    void onRecentsHidden() {
857        setStack(null);
858    }
859
860    public boolean isTransformedTouchPointInView(float x, float y, View child) {
861        return isTransformedTouchPointInView(x, y, child, null);
862    }
863
864    /** Pokes the dozer on user interaction. */
865    void onUserInteraction() {
866        // Poke the doze trigger if it is dozing
867        mUIDozeTrigger.poke();
868    }
869
870    /**** TaskStackCallbacks Implementation ****/
871
872    @Override
873    public void onStackTaskAdded(TaskStack stack, Task t) {
874        requestSynchronizeStackViewsWithModel();
875    }
876
877    @Override
878    public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) {
879        // Remove the view associated with this task, we can't rely on updateTransforms
880        // to work here because the task is no longer in the list
881        TaskView tv = getChildViewForTask(removedTask);
882        if (tv != null) {
883            mViewPool.returnViewToPool(tv);
884        }
885
886        // Notify the callback that we've removed the task and it can clean up after it
887        mCb.onTaskViewDismissed(removedTask);
888
889        // Get the stack scroll of the task to anchor to (since we are removing something, the front
890        // most task will be our anchor task)
891        Task anchorTask = null;
892        float prevAnchorTaskScroll = 0;
893        boolean pullStackForward = stack.getTaskCount() > 0;
894        if (pullStackForward) {
895            anchorTask = mStack.getFrontMostTask();
896            prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
897        }
898
899        // Update the min/max scroll and animate other task views into their new positions
900        updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome);
901
902        // Offset the stack by as much as the anchor task would otherwise move back
903        if (pullStackForward) {
904            float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
905            mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
906                    - prevAnchorTaskScroll));
907            mStackScroller.boundScroll();
908        }
909
910        // Animate all the tasks into place
911        requestSynchronizeStackViewsWithModel(200);
912
913        // Update the new front most task
914        if (newFrontMostTask != null) {
915            TaskView frontTv = getChildViewForTask(newFrontMostTask);
916            if (frontTv != null) {
917                frontTv.onTaskBound(newFrontMostTask);
918                frontTv.fadeInActionButton(false);
919            }
920        }
921
922        // If there are no remaining tasks, then either unfilter the current stack, or just close
923        // the activity if there are no filtered stacks
924        if (mStack.getTaskCount() == 0) {
925            boolean shouldFinishActivity = true;
926            if (mStack.hasFilteredTasks()) {
927                mStack.unfilterTasks();
928                shouldFinishActivity = (mStack.getTaskCount() == 0);
929            }
930            if (shouldFinishActivity) {
931                mCb.onAllTaskViewsDismissed();
932            }
933        }
934    }
935
936    @Override
937    public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
938                                Task filteredTask) {
939        /*
940        // Stash the scroll and filtered task for us to restore to when we unfilter
941        mStashedScroll = getStackScroll();
942
943        // Calculate the current task transforms
944        ArrayList<TaskViewTransform> curTaskTransforms =
945                getStackTransforms(curTasks, getStackScroll(), null, true);
946
947        // Update the task offsets
948        mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
949
950        // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
951        updateMinMaxScroll(false);
952        float overlapHeight = mLayoutAlgorithm.getTaskOverlapHeight();
953        setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
954        boundScrollRaw();
955
956        // Compute the transforms of the items in the new stack after setting the new scroll
957        final ArrayList<Task> tasks = mStack.getTasks();
958        final ArrayList<TaskViewTransform> taskTransforms =
959                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
960
961        // Animate
962        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
963
964        // Notify any callbacks
965        mCb.onTaskStackFilterTriggered();
966        */
967    }
968
969    @Override
970    public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
971        /*
972        // Calculate the current task transforms
973        final ArrayList<TaskViewTransform> curTaskTransforms =
974                getStackTransforms(curTasks, getStackScroll(), null, true);
975
976        // Update the task offsets
977        mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
978
979        // Restore the stashed scroll
980        updateMinMaxScroll(false);
981        setStackScrollRaw(mStashedScroll);
982        boundScrollRaw();
983
984        // Compute the transforms of the items in the new stack after restoring the stashed scroll
985        final ArrayList<Task> tasks = mStack.getTasks();
986        final ArrayList<TaskViewTransform> taskTransforms =
987                getStackTransforms(tasks, getStackScroll(), null, true);
988
989        // Animate
990        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
991
992        // Clear the saved vars
993        mStashedScroll = 0;
994
995        // Notify any callbacks
996        mCb.onTaskStackUnfilterTriggered();
997        */
998    }
999
1000    /**** ViewPoolConsumer Implementation ****/
1001
1002    @Override
1003    public TaskView createView(Context context) {
1004        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
1005    }
1006
1007    @Override
1008    public void prepareViewToEnterPool(TaskView tv) {
1009        Task task = tv.getTask();
1010
1011        // Clear the accessibility focus for that view
1012        if (tv.isAccessibilityFocused()) {
1013            tv.clearAccessibilityFocus();
1014        }
1015
1016        // Report that this tasks's data is no longer being used
1017        RecentsTaskLoader.getInstance().unloadTaskData(task);
1018
1019        // Detach the view from the hierarchy
1020        detachViewFromParent(tv);
1021
1022        // Reset the view properties
1023        tv.resetViewProperties();
1024
1025        // Reset the clip state of the task view
1026        tv.setClipViewInStack(false);
1027    }
1028
1029    @Override
1030    public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
1031        if (mStack == null) return;
1032
1033        // It is possible for a view to be returned to the view pool before it is laid out,
1034        // which means that we will need to relayout the view when it is first used next.
1035        boolean requiresRelayout = tv.getWidth() <= 0 && !isNewView;
1036
1037        // Rebind the task and request that this task's data be filled into the TaskView
1038        tv.onTaskBound(task);
1039
1040        // Load the task data
1041        RecentsTaskLoader.getInstance().loadTaskData(task);
1042
1043        // If the doze trigger has already fired, then update the state for this task view
1044        if (mUIDozeTrigger.hasTriggered()) {
1045            tv.setNoUserInteractionState();
1046        }
1047
1048        // If we've finished the start animation, then ensure we always enable the focus animations
1049        if (mStartEnterAnimationCompleted) {
1050            tv.enableFocusAnimations();
1051        }
1052
1053        // Find the index where this task should be placed in the stack
1054        int insertIndex = -1;
1055        int taskIndex = mStack.indexOfTask(task);
1056        if (taskIndex != -1) {
1057            int childCount = getChildCount();
1058            for (int i = 0; i < childCount; i++) {
1059                Task tvTask = ((TaskView) getChildAt(i)).getTask();
1060                if (taskIndex < mStack.indexOfTask(tvTask)) {
1061                    insertIndex = i;
1062                    break;
1063                }
1064            }
1065        }
1066
1067        // Add/attach the view to the hierarchy
1068        if (isNewView) {
1069            addView(tv, insertIndex);
1070        } else {
1071            attachViewToParent(tv, insertIndex, tv.getLayoutParams());
1072            if (requiresRelayout) {
1073                tv.requestLayout();
1074            }
1075        }
1076
1077        // Set the new state for this view, including the callbacks and view clipping
1078        tv.setCallbacks(this);
1079        tv.setTouchEnabled(true);
1080        tv.setClipViewInStack(true);
1081    }
1082
1083    @Override
1084    public boolean hasPreferredData(TaskView tv, Task preferredData) {
1085        return (tv.getTask() == preferredData);
1086    }
1087
1088    /**** TaskViewCallbacks Implementation ****/
1089
1090    @Override
1091    public void onTaskViewAppIconClicked(TaskView tv) {
1092        if (Constants.DebugFlags.App.EnableTaskFiltering) {
1093            if (mStack.hasFilteredTasks()) {
1094                mStack.unfilterTasks();
1095            } else {
1096                mStack.filterTasks(tv.getTask());
1097            }
1098        }
1099    }
1100
1101    @Override
1102    public void onTaskViewAppInfoClicked(TaskView tv) {
1103        if (mCb != null) {
1104            mCb.onTaskViewAppInfoClicked(tv.getTask());
1105        }
1106    }
1107
1108    @Override
1109    public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {
1110        // Cancel any doze triggers
1111        mUIDozeTrigger.stopDozing();
1112
1113        if (mCb != null) {
1114            mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask);
1115        }
1116    }
1117
1118    @Override
1119    public void onTaskViewDismissed(TaskView tv) {
1120        Task task = tv.getTask();
1121        int taskIndex = mStack.indexOfTask(task);
1122        boolean taskWasFocused = tv.isFocusedTask();
1123        // Announce for accessibility
1124        tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed,
1125                tv.getTask().activityLabel));
1126        // Remove the task from the view
1127        mStack.removeTask(task);
1128        // If the dismissed task was focused, then we should focus the new task in the same index
1129        if (taskWasFocused) {
1130            ArrayList<Task> tasks = mStack.getTasks();
1131            int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex - 1);
1132            if (nextTaskIndex >= 0) {
1133                Task nextTask = tasks.get(nextTaskIndex);
1134                TaskView nextTv = getChildViewForTask(nextTask);
1135                if (nextTv != null) {
1136                    // Focus the next task, and only animate the visible state if we are launched
1137                    // from Alt-Tab
1138                    nextTv.setFocusedTask(mConfig.launchedWithAltTab);
1139                }
1140            }
1141        }
1142    }
1143
1144    @Override
1145    public void onTaskViewClipStateChanged(TaskView tv) {
1146        if (!mStackViewsDirty) {
1147            invalidate();
1148        }
1149    }
1150
1151    @Override
1152    public void onTaskViewFocusChanged(TaskView tv, boolean focused) {
1153        if (focused) {
1154            mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
1155        }
1156    }
1157
1158    /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
1159
1160    @Override
1161    public void onScrollChanged(float p) {
1162        mUIDozeTrigger.poke();
1163        requestSynchronizeStackViewsWithModel();
1164        postInvalidateOnAnimation();
1165    }
1166
1167    /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
1168
1169    @Override
1170    public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) {
1171        if (mStack == null) return;
1172
1173        // Compute which components need to be removed
1174        HashSet<ComponentName> removedComponents = monitor.computeComponentsRemoved(
1175                mStack.getTaskKeys(), packageName, userId);
1176
1177        // For other tasks, just remove them directly if they no longer exist
1178        ArrayList<Task> tasks = mStack.getTasks();
1179        for (int i = tasks.size() - 1; i >= 0; i--) {
1180            final Task t = tasks.get(i);
1181            if (removedComponents.contains(t.key.baseIntent.getComponent())) {
1182                TaskView tv = getChildViewForTask(t);
1183                if (tv != null) {
1184                    // For visible children, defer removing the task until after the animation
1185                    tv.startDeleteTaskAnimation(new Runnable() {
1186                        @Override
1187                        public void run() {
1188                            mStack.removeTask(t);
1189                        }
1190                    });
1191                } else {
1192                    // Otherwise, remove the task from the stack immediately
1193                    mStack.removeTask(t);
1194                }
1195            }
1196        }
1197    }
1198}
1199