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