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