TaskStackView.java revision ebfc6981828b0699eef85c58b23a61f2cac41af3
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, mConfig, 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.getStackScrollForTask(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 ONLY used from AlternateRecentsComponent to update the dummy stack view for purposes
464     * of getting the task rect to animate to.
465     */
466    public void updateMinMaxScrollForStack(TaskStack stack, boolean launchedWithAltTab,
467            boolean launchedFromHome) {
468        mStack = stack;
469        updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome);
470    }
471
472    /**
473     * This is called with the full window width and height to allow stack view children to
474     * perform the full screen transition down.
475     */
476    @Override
477    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
478        int width = MeasureSpec.getSize(widthMeasureSpec);
479        int height = MeasureSpec.getSize(heightMeasureSpec);
480
481        // Compute our stack/task rects
482        Rect taskStackBounds = new Rect(mTaskStackBounds);
483        taskStackBounds.bottom -= mConfig.systemInsets.bottom;
484        computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab,
485                mConfig.launchedFromHome);
486
487        // If this is the first layout, then scroll to the front of the stack and synchronize the
488        // stack views immediately to load all the views
489        if (mAwaitingFirstLayout) {
490            mStackScroller.setStackScrollToInitialState();
491            requestSynchronizeStackViewsWithModel();
492            synchronizeStackViewsWithModel();
493        }
494
495        // Measure each of the TaskViews
496        int childCount = getChildCount();
497        for (int i = 0; i < childCount; i++) {
498            TaskView tv = (TaskView) getChildAt(i);
499            if (tv.isFullScreenView()) {
500                tv.measure(widthMeasureSpec, heightMeasureSpec);
501            } else {
502                tv.measure(
503                    MeasureSpec.makeMeasureSpec(mLayoutAlgorithm.mTaskRect.width(),
504                            MeasureSpec.EXACTLY),
505                    MeasureSpec.makeMeasureSpec(mLayoutAlgorithm.mTaskRect.height() +
506                            tv.getMaxFooterHeight(), MeasureSpec.EXACTLY));
507            }
508        }
509
510        setMeasuredDimension(width, height);
511    }
512
513    /**
514     * This is called with the size of the space not including the top or right insets, or the
515     * search bar height in portrait (but including the search bar width in landscape, since we want
516     * to draw under it.
517     */
518    @Override
519    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
520        // Layout each of the children
521        int childCount = getChildCount();
522        for (int i = 0; i < childCount; i++) {
523            TaskView tv = (TaskView) getChildAt(i);
524            if (tv.isFullScreenView()) {
525                tv.layout(left, top, left + tv.getMeasuredWidth(), top + tv.getMeasuredHeight());
526            } else {
527                tv.layout(mLayoutAlgorithm.mTaskRect.left, mLayoutAlgorithm.mTaskRect.top,
528                        mLayoutAlgorithm.mTaskRect.right, mLayoutAlgorithm.mTaskRect.bottom +
529                                tv.getMaxFooterHeight());
530            }
531        }
532
533        if (mAwaitingFirstLayout) {
534            mAwaitingFirstLayout = false;
535            onFirstLayout();
536        }
537    }
538
539    /** Handler for the first layout. */
540    void onFirstLayout() {
541        int offscreenY = mLayoutAlgorithm.mViewRect.bottom -
542                (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);
543
544        // Find the launch target task
545        Task launchTargetTask = null;
546        int childCount = getChildCount();
547        for (int i = childCount - 1; i >= 0; i--) {
548            TaskView tv = (TaskView) getChildAt(i);
549            Task task = tv.getTask();
550            if (task.isLaunchTarget) {
551                launchTargetTask = task;
552                break;
553            }
554        }
555
556        // Prepare the first view for its enter animation
557        for (int i = childCount - 1; i >= 0; i--) {
558            TaskView tv = (TaskView) getChildAt(i);
559            Task task = tv.getTask();
560            boolean occludesLaunchTarget = (launchTargetTask != null) &&
561                    launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
562            tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY);
563        }
564
565        // If the enter animation started already and we haven't completed a layout yet, do the
566        // enter animation now
567        if (mStartEnterAnimationRequestedAfterLayout) {
568            startEnterRecentsAnimation(mStartEnterAnimationContext);
569            mStartEnterAnimationRequestedAfterLayout = false;
570            mStartEnterAnimationContext = null;
571        }
572
573        // When Alt-Tabbing, we scroll to and focus the previous task
574        if (mConfig.launchedWithAltTab) {
575            if (mConfig.launchedFromHome) {
576                focusTask(Math.max(0, mStack.getTaskCount() - 1), false);
577            } else {
578                focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
579            }
580        }
581    }
582
583    /** Requests this task stacks to start it's enter-recents animation */
584    public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
585        // If we are still waiting to layout, then just defer until then
586        if (mAwaitingFirstLayout) {
587            mStartEnterAnimationRequestedAfterLayout = true;
588            mStartEnterAnimationContext = ctx;
589            return;
590        }
591
592        if (mStack.getTaskCount() > 0) {
593            // Find the launch target task
594            Task launchTargetTask = null;
595            int childCount = getChildCount();
596            for (int i = childCount - 1; i >= 0; i--) {
597                TaskView tv = (TaskView) getChildAt(i);
598                Task task = tv.getTask();
599                if (task.isLaunchTarget) {
600                    launchTargetTask = task;
601                    break;
602                }
603            }
604
605            // Animate all the task views into view
606            for (int i = childCount - 1; i >= 0; i--) {
607                TaskView tv = (TaskView) getChildAt(i);
608                Task task = tv.getTask();
609                ctx.currentTaskTransform = new TaskViewTransform();
610                ctx.currentStackViewIndex = i;
611                ctx.currentStackViewCount = childCount;
612                ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect;
613                ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) &&
614                        launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
615                mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null);
616                tv.startEnterRecentsAnimation(ctx);
617            }
618
619            // Add a runnable to the post animation ref counter to clear all the views
620            ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
621                @Override
622                public void run() {
623                    mStartEnterAnimationCompleted = true;
624                    // Start dozing
625                    mUIDozeTrigger.startDozing();
626                }
627            });
628        }
629    }
630
631    /** Requests this task stacks to start it's exit-recents animation. */
632    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
633        // Stop any scrolling
634        mStackScroller.stopScroller();
635        mStackScroller.stopBoundScrollAnimation();
636        // Animate all the task views out of view
637        ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom -
638                (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);
639        int childCount = getChildCount();
640        for (int i = 0; i < childCount; i++) {
641            TaskView tv = (TaskView) getChildAt(i);
642            tv.startExitToHomeAnimation(ctx);
643        }
644
645        // Add a runnable to the post animation ref counter to clear all the views
646        ctx.postAnimationTrigger.addLastDecrementRunnable(mReturnAllViewsToPoolRunnable);
647    }
648
649    /** Animates a task view in this stack as it launches. */
650    public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) {
651        Task launchTargetTask = tv.getTask();
652        int childCount = getChildCount();
653        for (int i = 0; i < childCount; i++) {
654            TaskView t = (TaskView) getChildAt(i);
655            if (t == tv) {
656                t.startLaunchTaskAnimation(r, true, true, lockToTask);
657            } else {
658                boolean occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(t.getTask(),
659                        launchTargetTask);
660                t.startLaunchTaskAnimation(null, false, occludesLaunchTarget, lockToTask);
661            }
662        }
663    }
664
665    public boolean isTransformedTouchPointInView(float x, float y, View child) {
666        return isTransformedTouchPointInView(x, y, child, null);
667    }
668
669    /** Pokes the dozer on user interaction. */
670    void onUserInteraction() {
671        // Poke the doze trigger if it is dozing
672        mUIDozeTrigger.poke();
673    }
674
675    /**** TaskStackCallbacks Implementation ****/
676
677    @Override
678    public void onStackTaskAdded(TaskStack stack, Task t) {
679        requestSynchronizeStackViewsWithModel();
680    }
681
682    @Override
683    public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) {
684        // Remove the view associated with this task, we can't rely on updateTransforms
685        // to work here because the task is no longer in the list
686        TaskView tv = getChildViewForTask(removedTask);
687        if (tv != null) {
688            mViewPool.returnViewToPool(tv);
689        }
690
691        // Notify the callback that we've removed the task and it can clean up after it
692        mCb.onTaskViewDismissed(removedTask);
693
694        // Get the stack scroll of the task to anchor to (since we are removing something, the front
695        // most task will be our anchor task)
696        Task anchorTask = null;
697        float prevAnchorTaskScroll = 0;
698        boolean pullStackForward = stack.getTaskCount() > 0;
699        if (pullStackForward) {
700            anchorTask = mStack.getFrontMostTask();
701            prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
702        }
703
704        // Update the min/max scroll and animate other task views into their new positions
705        updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome);
706
707        // Offset the stack by as much as the anchor task would otherwise move back
708        if (pullStackForward) {
709            float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
710            mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
711                    - prevAnchorTaskScroll));
712            mStackScroller.boundScroll();
713        }
714
715        // Animate all the tasks into place
716        requestSynchronizeStackViewsWithModel(200);
717
718        // Update the new front most task
719        if (newFrontMostTask != null) {
720            TaskView frontTv = getChildViewForTask(newFrontMostTask);
721            if (frontTv != null) {
722                frontTv.onTaskBound(newFrontMostTask);
723            }
724        }
725
726        // If there are no remaining tasks, then either unfilter the current stack, or just close
727        // the activity if there are no filtered stacks
728        if (mStack.getTaskCount() == 0) {
729            boolean shouldFinishActivity = true;
730            if (mStack.hasFilteredTasks()) {
731                mStack.unfilterTasks();
732                shouldFinishActivity = (mStack.getTaskCount() == 0);
733            }
734            if (shouldFinishActivity) {
735                mCb.onAllTaskViewsDismissed();
736            }
737        }
738    }
739
740    @Override
741    public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
742                                Task filteredTask) {
743        /*
744        // Stash the scroll and filtered task for us to restore to when we unfilter
745        mStashedScroll = getStackScroll();
746
747        // Calculate the current task transforms
748        ArrayList<TaskViewTransform> curTaskTransforms =
749                getStackTransforms(curTasks, getStackScroll(), null, true);
750
751        // Update the task offsets
752        mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
753
754        // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
755        updateMinMaxScroll(false);
756        float overlapHeight = mLayoutAlgorithm.getTaskOverlapHeight();
757        setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
758        boundScrollRaw();
759
760        // Compute the transforms of the items in the new stack after setting the new scroll
761        final ArrayList<Task> tasks = mStack.getTasks();
762        final ArrayList<TaskViewTransform> taskTransforms =
763                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
764
765        // Animate
766        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
767
768        // Notify any callbacks
769        mCb.onTaskStackFilterTriggered();
770        */
771    }
772
773    @Override
774    public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
775        /*
776        // Calculate the current task transforms
777        final ArrayList<TaskViewTransform> curTaskTransforms =
778                getStackTransforms(curTasks, getStackScroll(), null, true);
779
780        // Update the task offsets
781        mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
782
783        // Restore the stashed scroll
784        updateMinMaxScroll(false);
785        setStackScrollRaw(mStashedScroll);
786        boundScrollRaw();
787
788        // Compute the transforms of the items in the new stack after restoring the stashed scroll
789        final ArrayList<Task> tasks = mStack.getTasks();
790        final ArrayList<TaskViewTransform> taskTransforms =
791                getStackTransforms(tasks, getStackScroll(), null, true);
792
793        // Animate
794        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
795
796        // Clear the saved vars
797        mStashedScroll = 0;
798
799        // Notify any callbacks
800        mCb.onTaskStackUnfilterTriggered();
801        */
802    }
803
804    /**** ViewPoolConsumer Implementation ****/
805
806    @Override
807    public TaskView createView(Context context) {
808        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
809    }
810
811    @Override
812    public void prepareViewToEnterPool(TaskView tv) {
813        Task task = tv.getTask();
814
815        // Report that this tasks's data is no longer being used
816        RecentsTaskLoader.getInstance().unloadTaskData(task);
817
818        // Detach the view from the hierarchy
819        detachViewFromParent(tv);
820
821        // Reset the view properties
822        tv.resetViewProperties();
823    }
824
825    @Override
826    public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
827        // Rebind the task and request that this task's data be filled into the TaskView
828        tv.onTaskBound(task);
829
830        // Mark the launch task as fullscreen
831        if (Constants.DebugFlags.App.EnableScreenshotAppTransition && mAwaitingFirstLayout) {
832            if (task.isLaunchTarget) {
833                tv.setIsFullScreen(true);
834            }
835        }
836
837        // Load the task data
838        RecentsTaskLoader.getInstance().loadTaskData(task);
839
840        // Sanity check, the task view should always be clipping against the stack at this point,
841        // but just in case, re-enable it here
842        tv.setClipViewInStack(true);
843
844        // If the doze trigger has already fired, then update the state for this task view
845        if (mUIDozeTrigger.hasTriggered()) {
846            tv.setNoUserInteractionState();
847        }
848
849        // If we've finished the start animation, then ensure we always enable the focus animations
850        if (mStartEnterAnimationCompleted) {
851            tv.enableFocusAnimations();
852        }
853
854        // Find the index where this task should be placed in the stack
855        int insertIndex = -1;
856        int taskIndex = mStack.indexOfTask(task);
857        if (taskIndex != -1) {
858            int childCount = getChildCount();
859            for (int i = 0; i < childCount; i++) {
860                Task tvTask = ((TaskView) getChildAt(i)).getTask();
861                if (taskIndex < mStack.indexOfTask(tvTask)) {
862                    insertIndex = i;
863                    break;
864                }
865            }
866        }
867
868        // Add/attach the view to the hierarchy
869        if (isNewView) {
870            addView(tv, insertIndex);
871
872            // Set the callbacks and listeners for this new view
873            tv.setTouchEnabled(true);
874            tv.setCallbacks(this);
875        } else {
876            attachViewToParent(tv, insertIndex, tv.getLayoutParams());
877        }
878    }
879
880    @Override
881    public boolean hasPreferredData(TaskView tv, Task preferredData) {
882        return (tv.getTask() == preferredData);
883    }
884
885    /**** TaskViewCallbacks Implementation ****/
886
887    @Override
888    public void onTaskViewAppIconClicked(TaskView tv) {
889        if (Constants.DebugFlags.App.EnableTaskFiltering) {
890            if (mStack.hasFilteredTasks()) {
891                mStack.unfilterTasks();
892            } else {
893                mStack.filterTasks(tv.getTask());
894            }
895        }
896    }
897
898    @Override
899    public void onTaskViewAppInfoClicked(TaskView tv) {
900        if (mCb != null) {
901            mCb.onTaskViewAppInfoClicked(tv.getTask());
902        }
903    }
904
905    @Override
906    public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {
907        // Cancel any doze triggers
908        mUIDozeTrigger.stopDozing();
909
910        if (mCb != null) {
911            mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask);
912        }
913    }
914
915    @Override
916    public void onTaskViewDismissed(TaskView tv) {
917        Task task = tv.getTask();
918        int taskIndex = mStack.indexOfTask(task);
919        boolean taskWasFocused = tv.isFocusedTask();
920        // Announce for accessibility
921        tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed,
922                tv.getTask().activityLabel));
923        // Remove the task from the view
924        mStack.removeTask(task);
925        // If the dismissed task was focused, then we should focus the next task in front
926        if (taskWasFocused) {
927            ArrayList<Task> tasks = mStack.getTasks();
928            int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex);
929            if (nextTaskIndex >= 0) {
930                Task nextTask = tasks.get(nextTaskIndex);
931                TaskView nextTv = getChildViewForTask(nextTask);
932                nextTv.setFocusedTask();
933            }
934        }
935    }
936
937    @Override
938    public void onTaskViewClipStateChanged(TaskView tv) {
939        invalidate();
940    }
941
942    @Override
943    public void onTaskViewFullScreenTransitionCompleted() {
944        requestSynchronizeStackViewsWithModel();
945    }
946
947    @Override
948    public void onTaskViewFocusChanged(TaskView tv, boolean focused) {
949        if (focused) {
950            mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
951        }
952    }
953
954    /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
955
956    @Override
957    public void onScrollChanged(float p) {
958        mUIDozeTrigger.poke();
959        requestSynchronizeStackViewsWithModel();
960        postInvalidateOnAnimation();
961    }
962
963    /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
964
965    @Override
966    public void onComponentRemoved(HashSet<ComponentName> cns) {
967        // For other tasks, just remove them directly if they no longer exist
968        ArrayList<Task> tasks = mStack.getTasks();
969        for (int i = tasks.size() - 1; i >= 0; i--) {
970            final Task t = tasks.get(i);
971            if (cns.contains(t.key.baseIntent.getComponent())) {
972                TaskView tv = getChildViewForTask(t);
973                if (tv != null) {
974                    // For visible children, defer removing the task until after the animation
975                    tv.startDeleteTaskAnimation(new Runnable() {
976                        @Override
977                        public void run() {
978                            mStack.removeTask(t);
979                        }
980                    });
981                } else {
982                    // Otherwise, remove the task from the stack immediately
983                    mStack.removeTask(t);
984                }
985            }
986        }
987    }
988}
989