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