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