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