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