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