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