• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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;
18 
19 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
20 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
21 import static android.app.ActivityManager.StackId.isHomeOrRecentsStack;
22 import static android.view.View.MeasureSpec;
23 
24 import android.app.ActivityManager;
25 import android.app.ActivityManager.TaskSnapshot;
26 import android.app.ActivityOptions;
27 import android.app.ActivityOptions.OnAnimationStartedListener;
28 import android.content.ActivityNotFoundException;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.res.Resources;
32 import android.graphics.GraphicBuffer;
33 import android.graphics.Rect;
34 import android.graphics.RectF;
35 import android.graphics.drawable.Drawable;
36 import android.os.Handler;
37 import android.os.SystemClock;
38 import android.util.ArraySet;
39 import android.util.Log;
40 import android.util.MutableBoolean;
41 import android.util.Pair;
42 import android.view.AppTransitionAnimationSpec;
43 import android.view.LayoutInflater;
44 import android.view.ViewConfiguration;
45 import android.view.WindowManager;
46 
47 import android.widget.Toast;
48 
49 import com.google.android.collect.Lists;
50 
51 import com.android.internal.logging.MetricsLogger;
52 import com.android.internal.policy.DockedDividerUtils;
53 import com.android.systemui.R;
54 import com.android.systemui.SystemUIApplication;
55 import com.android.systemui.recents.events.EventBus;
56 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
57 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
58 import com.android.systemui.recents.events.activity.HideRecentsEvent;
59 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
60 import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent;
61 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
62 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
63 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
64 import com.android.systemui.recents.events.component.ActivityPinnedEvent;
65 import com.android.systemui.recents.events.component.ActivityUnpinnedEvent;
66 import com.android.systemui.recents.events.component.HidePipMenuEvent;
67 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
68 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
69 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
70 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
71 import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
72 import com.android.systemui.recents.misc.DozeTrigger;
73 import com.android.systemui.recents.misc.ForegroundThread;
74 import com.android.systemui.recents.misc.SystemServicesProxy;
75 import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
76 import com.android.systemui.recents.model.RecentsTaskLoadPlan;
77 import com.android.systemui.recents.model.RecentsTaskLoader;
78 import com.android.systemui.recents.model.Task;
79 import com.android.systemui.recents.model.Task.TaskKey;
80 import com.android.systemui.recents.model.TaskGrouping;
81 import com.android.systemui.recents.model.TaskStack;
82 import com.android.systemui.recents.model.ThumbnailData;
83 import com.android.systemui.recents.views.RecentsTransitionHelper;
84 import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
85 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
86 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
87 import com.android.systemui.recents.views.TaskStackView;
88 import com.android.systemui.recents.views.TaskStackViewScroller;
89 import com.android.systemui.recents.views.TaskViewHeader;
90 import com.android.systemui.recents.views.TaskViewTransform;
91 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
92 import com.android.systemui.stackdivider.DividerView;
93 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
94 import com.android.systemui.statusbar.phone.StatusBar;
95 
96 import java.util.ArrayList;
97 
98 /**
99  * An implementation of the Recents component for the current user.  For secondary users, this can
100  * be called remotely from the system user.
101  */
102 public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener {
103 
104     private final static String TAG = "RecentsImpl";
105 
106     // The minimum amount of time between each recents button press that we will handle
107     private final static int MIN_TOGGLE_DELAY_MS = 350;
108 
109     // The duration within which the user releasing the alt tab (from when they pressed alt tab)
110     // that the fast alt-tab animation will run.  If the user's alt-tab takes longer than this
111     // duration, then we will toggle recents after this duration.
112     private final static int FAST_ALT_TAB_DELAY_MS = 225;
113 
114     private final static ArraySet<TaskKey> EMPTY_SET = new ArraySet<>();
115 
116     public final static String RECENTS_PACKAGE = "com.android.systemui";
117     public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
118 
119     /**
120      * An implementation of TaskStackListener, that allows us to listen for changes to the system
121      * task stacks and update recents accordingly.
122      */
123     class TaskStackListenerImpl extends TaskStackListener {
124 
125         @Override
onTaskStackChangedBackground()126         public void onTaskStackChangedBackground() {
127             // Check this is for the right user
128             if (!checkCurrentUserId(mContext, false /* debug */)) {
129                 return;
130             }
131 
132             // Preloads the next task
133             RecentsConfiguration config = Recents.getConfiguration();
134             if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
135                 Rect windowRect = getWindowRect(null /* windowRectOverride */);
136                 if (windowRect.isEmpty()) {
137                     return;
138                 }
139 
140                 // Load the next task only if we aren't svelte
141                 SystemServicesProxy ssp = Recents.getSystemServices();
142                 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
143                 RecentsTaskLoader loader = Recents.getTaskLoader();
144                 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
145                 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
146                 TaskStack stack = plan.getTaskStack();
147                 RecentsActivityLaunchState launchState = new RecentsActivityLaunchState();
148                 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
149 
150                 synchronized (mBackgroundLayoutAlgorithm) {
151                     // This callback is made when a new activity is launched and the old one is
152                     // paused so ignore the current activity and try and preload the thumbnail for
153                     // the previous one.
154                     updateDummyStackViewLayout(mBackgroundLayoutAlgorithm, stack, windowRect);
155 
156                     // Launched from app is always the worst case (in terms of how many
157                     // thumbnails/tasks visible)
158                     launchState.launchedFromApp = true;
159                     mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState);
160                     VisibilityReport visibilityReport =
161                             mBackgroundLayoutAlgorithm.computeStackVisibilityReport(
162                                     stack.getStackTasks());
163 
164                     launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
165                     launchOpts.numVisibleTasks = visibilityReport.numVisibleTasks;
166                     launchOpts.numVisibleTaskThumbnails = visibilityReport.numVisibleThumbnails;
167                     launchOpts.onlyLoadForCache = true;
168                     launchOpts.onlyLoadPausedActivities = true;
169                     launchOpts.loadThumbnails = true;
170                 }
171                 loader.loadTasks(mContext, plan, launchOpts);
172             }
173         }
174 
175         @Override
onActivityPinned(String packageName, int userId, int taskId)176         public void onActivityPinned(String packageName, int userId, int taskId) {
177             // Check this is for the right user
178             if (!checkCurrentUserId(mContext, false /* debug */)) {
179                 return;
180             }
181 
182             // This time needs to be fetched the same way the last active time is fetched in
183             // {@link TaskRecord#touchActiveTime}
184             Recents.getConfiguration().getLaunchState().launchedFromPipApp = true;
185             Recents.getConfiguration().getLaunchState().launchedWithNextPipApp = false;
186             EventBus.getDefault().send(new ActivityPinnedEvent(taskId));
187             consumeInstanceLoadPlan();
188             sLastPipTime = System.currentTimeMillis();
189         }
190 
191         @Override
onActivityUnpinned()192         public void onActivityUnpinned() {
193             // Check this is for the right user
194             if (!checkCurrentUserId(mContext, false /* debug */)) {
195                 return;
196             }
197 
198             EventBus.getDefault().send(new ActivityUnpinnedEvent());
199             sLastPipTime = -1;
200         }
201 
202         @Override
onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)203         public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
204             // Check this is for the right user
205             if (!checkCurrentUserId(mContext, false /* debug */)) {
206                 return;
207             }
208 
209             EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId,
210                     ThumbnailData.createFromTaskSnapshot(snapshot)));
211         }
212     }
213 
214     protected static RecentsTaskLoadPlan sInstanceLoadPlan;
215     // Stores the last pinned task time
216     protected static long sLastPipTime = -1;
217     // Stores whether we are waiting for a transition to/from recents to start. During this time,
218     // we disallow the user from manually toggling recents until the transition has started.
219     private static boolean mWaitingForTransitionStart = false;
220     // Stores whether or not the user toggled while we were waiting for a transition to/from
221     // recents. In this case, we defer the toggle state until then and apply it immediately after.
222     private static boolean mToggleFollowingTransitionStart = true;
223 
224     private ActivityOptions.OnAnimationStartedListener mResetToggleFlagListener =
225             new OnAnimationStartedListener() {
226                 @Override
227                 public void onAnimationStarted() {
228                     setWaitingForTransitionStart(false);
229                 }
230             };
231 
232     protected Context mContext;
233     protected Handler mHandler;
234     TaskStackListenerImpl mTaskStackListener;
235     boolean mDraggingInRecents;
236     boolean mLaunchedWhileDocking;
237 
238     // Task launching
239     Rect mTmpBounds = new Rect();
240     TaskViewTransform mTmpTransform = new TaskViewTransform();
241     int mTaskBarHeight;
242 
243     // Header (for transition)
244     TaskViewHeader mHeaderBar;
245     final Object mHeaderBarLock = new Object();
246     private TaskStackView mDummyStackView;
247     private TaskStackLayoutAlgorithm mBackgroundLayoutAlgorithm;
248 
249     // Variables to keep track of if we need to start recents after binding
250     protected boolean mTriggeredFromAltTab;
251     protected long mLastToggleTime;
252     DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
253         @Override
254         public void run() {
255             // When this fires, then the user has not released alt-tab for at least
256             // FAST_ALT_TAB_DELAY_MS milliseconds
257             showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
258                     false /* reloadTasks */, false /* fromHome */,
259                     DividerView.INVALID_RECENTS_GROW_TARGET);
260         }
261     });
262 
RecentsImpl(Context context)263     public RecentsImpl(Context context) {
264         mContext = context;
265         mHandler = new Handler();
266         mBackgroundLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, null);
267 
268         // Initialize the static foreground thread
269         ForegroundThread.get();
270 
271         // Register the task stack listener
272         mTaskStackListener = new TaskStackListenerImpl();
273         SystemServicesProxy ssp = Recents.getSystemServices();
274         ssp.registerTaskStackListener(mTaskStackListener);
275 
276         // Initialize the static configuration resources
277         mDummyStackView = new TaskStackView(mContext);
278         reloadResources();
279     }
280 
onBootCompleted()281     public void onBootCompleted() {
282         // When we start, preload the data associated with the previous recent tasks.
283         // We can use a new plan since the caches will be the same.
284         RecentsTaskLoader loader = Recents.getTaskLoader();
285         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
286         loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
287         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
288         launchOpts.numVisibleTasks = loader.getIconCacheSize();
289         launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
290         launchOpts.onlyLoadForCache = true;
291         loader.loadTasks(mContext, plan, launchOpts);
292     }
293 
onConfigurationChanged()294     public void onConfigurationChanged() {
295         reloadResources();
296         mDummyStackView.reloadOnConfigurationChange();
297         synchronized (mBackgroundLayoutAlgorithm) {
298             mBackgroundLayoutAlgorithm.reloadOnConfigurationChange(mContext);
299         }
300     }
301 
302     /**
303      * This is only called from the system user's Recents.  Secondary users will instead proxy their
304      * visibility change events through to the system user via
305      * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
306      */
onVisibilityChanged(Context context, boolean visible)307     public void onVisibilityChanged(Context context, boolean visible) {
308         Recents.getSystemServices().setRecentsVisibility(visible);
309     }
310 
311     /**
312      * This is only called from the system user's Recents.  Secondary users will instead proxy their
313      * visibility change events through to the system user via
314      * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
315      */
onStartScreenPinning(Context context, int taskId)316     public void onStartScreenPinning(Context context, int taskId) {
317         SystemUIApplication app = (SystemUIApplication) context;
318         StatusBar statusBar = app.getComponent(StatusBar.class);
319         if (statusBar != null) {
320             statusBar.showScreenPinningRequest(taskId, false);
321         }
322     }
323 
showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, boolean animate, boolean launchedWhileDockingTask, boolean fromHome, int growTarget)324     public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
325             boolean animate, boolean launchedWhileDockingTask, boolean fromHome,
326             int growTarget) {
327         mTriggeredFromAltTab = triggeredFromAltTab;
328         mDraggingInRecents = draggingInRecents;
329         mLaunchedWhileDocking = launchedWhileDockingTask;
330         if (mFastAltTabTrigger.isAsleep()) {
331             // Fast alt-tab duration has elapsed, fall through to showing Recents and reset
332             mFastAltTabTrigger.stopDozing();
333         } else if (mFastAltTabTrigger.isDozing()) {
334             // Fast alt-tab duration has not elapsed.  If this is triggered by a different
335             // showRecents() call, then ignore that call for now.
336             // TODO: We can not handle quick tabs that happen between the initial showRecents() call
337             //       that started the activity and the activity starting up.  The severity of this
338             //       is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though.
339             if (!triggeredFromAltTab) {
340                 return;
341             }
342             mFastAltTabTrigger.stopDozing();
343         } else if (triggeredFromAltTab) {
344             // The fast alt-tab detector is not yet running, so start the trigger and wait for the
345             // hideRecents() call, or for the fast alt-tab duration to elapse
346             mFastAltTabTrigger.startDozing();
347             return;
348         }
349 
350         try {
351             // Check if the top task is in the home stack, and start the recents activity
352             SystemServicesProxy ssp = Recents.getSystemServices();
353             boolean forceVisible = launchedWhileDockingTask || draggingInRecents;
354             MutableBoolean isHomeStackVisible = new MutableBoolean(forceVisible);
355             if (forceVisible || !ssp.isRecentsActivityVisible(isHomeStackVisible)) {
356                 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
357                 startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate,
358                         growTarget);
359             }
360         } catch (ActivityNotFoundException e) {
361             Log.e(TAG, "Failed to launch RecentsActivity", e);
362         }
363     }
364 
hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey)365     public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
366         if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
367             // The user has released alt-tab before the trigger has run, so just show the next
368             // task immediately
369             showNextTask();
370 
371             // Cancel the fast alt-tab trigger
372             mFastAltTabTrigger.stopDozing();
373             return;
374         }
375 
376         // Defer to the activity to handle hiding recents, if it handles it, then it must still
377         // be visible
378         EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
379                 triggeredFromHomeKey));
380     }
381 
toggleRecents(int growTarget)382     public void toggleRecents(int growTarget) {
383         // Skip preloading if the task is locked
384         SystemServicesProxy ssp = Recents.getSystemServices();
385         if (ssp.isScreenPinningActive()) {
386             return;
387         }
388 
389         // Skip this toggle if we are already waiting to trigger recents via alt-tab
390         if (mFastAltTabTrigger.isDozing()) {
391             return;
392         }
393 
394         if (mWaitingForTransitionStart) {
395             mToggleFollowingTransitionStart = true;
396             return;
397         }
398 
399         mDraggingInRecents = false;
400         mLaunchedWhileDocking = false;
401         mTriggeredFromAltTab = false;
402 
403         try {
404             MutableBoolean isHomeStackVisible = new MutableBoolean(true);
405             long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
406 
407             if (ssp.isRecentsActivityVisible(isHomeStackVisible)) {
408                 RecentsDebugFlags debugFlags = Recents.getDebugFlags();
409                 RecentsConfiguration config = Recents.getConfiguration();
410                 RecentsActivityLaunchState launchState = config.getLaunchState();
411                 if (!launchState.launchedWithAltTab) {
412                     // Has the user tapped quickly?
413                     boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
414                     if (Recents.getConfiguration().isGridEnabled) {
415                         if (isQuickTap) {
416                             EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
417                         } else {
418                             EventBus.getDefault().post(new LaunchMostRecentTaskRequestEvent());
419                         }
420                     } else {
421                         if (!debugFlags.isPagingEnabled() || isQuickTap) {
422                             // Launch the next focused task
423                             EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
424                         } else {
425                             // Notify recents to move onto the next task
426                             EventBus.getDefault().post(new IterateRecentsEvent());
427                         }
428                     }
429                 } else {
430                     // If the user has toggled it too quickly, then just eat up the event here (it's
431                     // better than showing a janky screenshot).
432                     // NOTE: Ideally, the screenshot mechanism would take the window transform into
433                     // account
434                     if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
435                         return;
436                     }
437 
438                     EventBus.getDefault().post(new ToggleRecentsEvent());
439                     mLastToggleTime = SystemClock.elapsedRealtime();
440                 }
441                 return;
442             } else {
443                 // If the user has toggled it too quickly, then just eat up the event here (it's
444                 // better than showing a janky screenshot).
445                 // NOTE: Ideally, the screenshot mechanism would take the window transform into
446                 // account
447                 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
448                     return;
449                 }
450 
451                 // Otherwise, start the recents activity
452                 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
453                 startRecentsActivity(runningTask, isHomeStackVisible.value, true /* animate */,
454                         growTarget);
455 
456                 // Only close the other system windows if we are actually showing recents
457                 ssp.sendCloseSystemWindows(StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
458                 mLastToggleTime = SystemClock.elapsedRealtime();
459             }
460         } catch (ActivityNotFoundException e) {
461             Log.e(TAG, "Failed to launch RecentsActivity", e);
462         }
463     }
464 
465     public void preloadRecents() {
466         // Skip preloading if the task is locked
467         SystemServicesProxy ssp = Recents.getSystemServices();
468         if (ssp.isScreenPinningActive()) {
469             return;
470         }
471 
472         // Preload only the raw task list into a new load plan (which will be consumed by the
473         // RecentsActivity) only if there is a task to animate to.  Post this to ensure that we
474         // don't block the touch feedback on the nav bar button which triggers this.
475         mHandler.post(() -> {
476             MutableBoolean isHomeStackVisible = new MutableBoolean(true);
477             if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
478                 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
479                 if (runningTask == null) {
480                     return;
481                 }
482 
483                 RecentsTaskLoader loader = Recents.getTaskLoader();
484                 sInstanceLoadPlan = loader.createLoadPlan(mContext);
485                 loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
486                 TaskStack stack = sInstanceLoadPlan.getTaskStack();
487                 if (stack.getTaskCount() > 0) {
488                     // Only preload the icon (but not the thumbnail since it may not have been taken
489                     // for the pausing activity)
490                     preloadIcon(runningTask.id);
491 
492                     // At this point, we don't know anything about the stack state.  So only
493                     // calculate the dimensions of the thumbnail that we need for the transition
494                     // into Recents, but do not draw it until we construct the activity options when
495                     // we start Recents
496                     updateHeaderBarLayout(stack, null /* window rect override*/);
497                 }
498             }
499         });
500     }
501 
502     public void cancelPreloadingRecents() {
503         // Do nothing
504     }
505 
506     public void onDraggingInRecents(float distanceFromTop) {
507         EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
508     }
509 
510     public void onDraggingInRecentsEnded(float velocity) {
511         EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
512     }
513 
514     public void onShowCurrentUserToast(int msgResId, int msgLength) {
515         Toast.makeText(mContext, msgResId, msgLength).show();
516     }
517 
518     /**
519      * Transitions to the next recent task in the stack.
520      */
521     public void showNextTask() {
522         SystemServicesProxy ssp = Recents.getSystemServices();
523         RecentsTaskLoader loader = Recents.getTaskLoader();
524         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
525         loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
526         TaskStack focusedStack = plan.getTaskStack();
527 
528         // Return early if there are no tasks in the focused stack
529         if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
530 
531         // Return early if there is no running task
532         ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
533         if (runningTask == null) return;
534 
535         // Find the task in the recents list
536         boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId);
537         ArrayList<Task> tasks = focusedStack.getStackTasks();
538         Task toTask = null;
539         ActivityOptions launchOpts = null;
540         int taskCount = tasks.size();
541         for (int i = taskCount - 1; i >= 1; i--) {
542             Task task = tasks.get(i);
543             if (isRunningTaskInHomeStack) {
544                 toTask = tasks.get(i - 1);
545                 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
546                         R.anim.recents_launch_next_affiliated_task_target,
547                         R.anim.recents_fast_toggle_app_home_exit);
548                 break;
549             } else if (task.key.id == runningTask.id) {
550                 toTask = tasks.get(i - 1);
551                 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
552                         R.anim.recents_launch_prev_affiliated_task_target,
553                         R.anim.recents_launch_prev_affiliated_task_source);
554                 break;
555             }
556         }
557 
558         // Return early if there is no next task
559         if (toTask == null) {
560             ssp.startInPlaceAnimationOnFrontMostApplication(
561                     ActivityOptions.makeCustomInPlaceAnimation(mContext,
562                             R.anim.recents_launch_prev_affiliated_task_bounce));
563             return;
564         }
565 
566         // Launch the task
567         ssp.startActivityFromRecents(
568                 mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
569                 null /* resultListener */);
570     }
571 
572     /**
573      * Transitions to the next affiliated task.
574      */
showRelativeAffiliatedTask(boolean showNextTask)575     public void showRelativeAffiliatedTask(boolean showNextTask) {
576         SystemServicesProxy ssp = Recents.getSystemServices();
577         RecentsTaskLoader loader = Recents.getTaskLoader();
578         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
579         loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
580         TaskStack focusedStack = plan.getTaskStack();
581 
582         // Return early if there are no tasks in the focused stack
583         if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
584 
585         // Return early if there is no running task (can't determine affiliated tasks in this case)
586         ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
587         if (runningTask == null) return;
588         // Return early if the running task is in the home/recents stack (optimization)
589         if (isHomeOrRecentsStack(runningTask.stackId)) return;
590 
591         // Find the task in the recents list
592         ArrayList<Task> tasks = focusedStack.getStackTasks();
593         Task toTask = null;
594         ActivityOptions launchOpts = null;
595         int taskCount = tasks.size();
596         int numAffiliatedTasks = 0;
597         for (int i = 0; i < taskCount; i++) {
598             Task task = tasks.get(i);
599             if (task.key.id == runningTask.id) {
600                 TaskGrouping group = task.group;
601                 Task.TaskKey toTaskKey;
602                 if (showNextTask) {
603                     toTaskKey = group.getNextTaskInGroup(task);
604                     launchOpts = ActivityOptions.makeCustomAnimation(mContext,
605                             R.anim.recents_launch_next_affiliated_task_target,
606                             R.anim.recents_launch_next_affiliated_task_source);
607                 } else {
608                     toTaskKey = group.getPrevTaskInGroup(task);
609                     launchOpts = ActivityOptions.makeCustomAnimation(mContext,
610                             R.anim.recents_launch_prev_affiliated_task_target,
611                             R.anim.recents_launch_prev_affiliated_task_source);
612                 }
613                 if (toTaskKey != null) {
614                     toTask = focusedStack.findTaskWithId(toTaskKey.id);
615                 }
616                 numAffiliatedTasks = group.getTaskCount();
617                 break;
618             }
619         }
620 
621         // Return early if there is no next task
622         if (toTask == null) {
623             if (numAffiliatedTasks > 1) {
624                 if (showNextTask) {
625                     ssp.startInPlaceAnimationOnFrontMostApplication(
626                             ActivityOptions.makeCustomInPlaceAnimation(mContext,
627                                     R.anim.recents_launch_next_affiliated_task_bounce));
628                 } else {
629                     ssp.startInPlaceAnimationOnFrontMostApplication(
630                             ActivityOptions.makeCustomInPlaceAnimation(mContext,
631                                     R.anim.recents_launch_prev_affiliated_task_bounce));
632                 }
633             }
634             return;
635         }
636 
637         // Keep track of actually launched affiliated tasks
638         MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
639 
640         // Launch the task
641         ssp.startActivityFromRecents(
642                 mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
643                 null /* resultListener */);
644     }
645 
showNextAffiliatedTask()646     public void showNextAffiliatedTask() {
647         // Keep track of when the affiliated task is triggered
648         MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
649         showRelativeAffiliatedTask(true);
650     }
651 
showPrevAffiliatedTask()652     public void showPrevAffiliatedTask() {
653         // Keep track of when the affiliated task is triggered
654         MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
655         showRelativeAffiliatedTask(false);
656     }
657 
dockTopTask(int topTaskId, int dragMode, int stackCreateMode, Rect initialBounds)658     public void dockTopTask(int topTaskId, int dragMode,
659             int stackCreateMode, Rect initialBounds) {
660         SystemServicesProxy ssp = Recents.getSystemServices();
661 
662         // Make sure we inform DividerView before we actually start the activity so we can change
663         // the resize mode already.
664         if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
665             EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
666             showRecents(
667                     false /* triggeredFromAltTab */,
668                     dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
669                     false /* animate */,
670                     true /* launchedWhileDockingTask*/,
671                     false /* fromHome */,
672                     DividerView.INVALID_RECENTS_GROW_TARGET);
673         }
674     }
675 
setWaitingForTransitionStart(boolean waitingForTransitionStart)676     public void setWaitingForTransitionStart(boolean waitingForTransitionStart) {
677         if (mWaitingForTransitionStart == waitingForTransitionStart) {
678             return;
679         }
680 
681         mWaitingForTransitionStart = waitingForTransitionStart;
682         if (!waitingForTransitionStart && mToggleFollowingTransitionStart) {
683             mHandler.post(() -> toggleRecents(DividerView.INVALID_RECENTS_GROW_TARGET));
684         }
685         mToggleFollowingTransitionStart = false;
686     }
687 
688     /**
689      * Returns the preloaded load plan and invalidates it.
690      */
consumeInstanceLoadPlan()691     public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
692         RecentsTaskLoadPlan plan = sInstanceLoadPlan;
693         sInstanceLoadPlan = null;
694         return plan;
695     }
696 
697     /**
698      * @return the time at which a task last entered picture-in-picture.
699      */
getLastPipTime()700     public static long getLastPipTime() {
701         return sLastPipTime;
702     }
703 
704     /**
705      * Clears the time at which a task last entered picture-in-picture.
706      */
clearLastPipTime()707     public static void clearLastPipTime() {
708         sLastPipTime = -1;
709     }
710 
711     /**
712      * Reloads all the resources for the current configuration.
713      */
reloadResources()714     private void reloadResources() {
715         Resources res = mContext.getResources();
716 
717         mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(mContext,
718                 R.dimen.recents_task_view_header_height,
719                 R.dimen.recents_task_view_header_height,
720                 R.dimen.recents_task_view_header_height,
721                 R.dimen.recents_task_view_header_height_tablet_land,
722                 R.dimen.recents_task_view_header_height,
723                 R.dimen.recents_task_view_header_height_tablet_land,
724                 R.dimen.recents_grid_task_view_header_height);
725 
726         LayoutInflater inflater = LayoutInflater.from(mContext);
727         mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
728                 null, false);
729         mHeaderBar.setLayoutDirection(res.getConfiguration().getLayoutDirection());
730     }
731 
updateDummyStackViewLayout(TaskStackLayoutAlgorithm stackLayout, TaskStack stack, Rect windowRect)732     private void updateDummyStackViewLayout(TaskStackLayoutAlgorithm stackLayout,
733             TaskStack stack, Rect windowRect) {
734         SystemServicesProxy ssp = Recents.getSystemServices();
735         Rect displayRect = ssp.getDisplayRect();
736         Rect systemInsets = new Rect();
737         ssp.getStableInsets(systemInsets);
738 
739         // When docked, the nav bar insets are consumed and the activity is measured without insets.
740         // However, the window bounds include the insets, so we need to subtract them here to make
741         // them identical.
742         if (ssp.hasDockedTask()) {
743             windowRect.bottom -= systemInsets.bottom;
744             systemInsets.bottom = 0;
745         }
746         calculateWindowStableInsets(systemInsets, windowRect, displayRect);
747         windowRect.offsetTo(0, 0);
748 
749         // Rebind the header bar and draw it for the transition
750         stackLayout.setSystemInsets(systemInsets);
751         if (stack != null) {
752             stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
753                     systemInsets.left, systemInsets.right, mTmpBounds);
754             stackLayout.reset();
755             stackLayout.initialize(displayRect, windowRect, mTmpBounds,
756                     TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
757         }
758     }
759 
getWindowRect(Rect windowRectOverride)760     private Rect getWindowRect(Rect windowRectOverride) {
761        return windowRectOverride != null
762                 ? new Rect(windowRectOverride)
763                 : Recents.getSystemServices().getWindowRect();
764     }
765 
766     /**
767      * Prepares the header bar layout for the next transition, if the task view bounds has changed
768      * since the last call, it will attempt to re-measure and layout the header bar to the new size.
769      *
770      * @param stack the stack to initialize the stack layout with
771      * @param windowRectOverride the rectangle to use when calculating the stack state which can
772      *                           be different from the current window rect if recents is resizing
773      *                           while being launched
774      */
updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride)775     private void updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride) {
776         Rect windowRect = getWindowRect(windowRectOverride);
777         int taskViewWidth = 0;
778         boolean useGridLayout = mDummyStackView.useGridLayout();
779         updateDummyStackViewLayout(mDummyStackView.getStackAlgorithm(), stack, windowRect);
780         if (stack != null) {
781             TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
782             mDummyStackView.getStack().removeAllTasks(false /* notifyStackChanges */);
783             mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
784             // Get the width of a task view so that we know how wide to draw the header bar.
785             if (useGridLayout) {
786                 TaskGridLayoutAlgorithm gridLayout = mDummyStackView.getGridAlgorithm();
787                 gridLayout.initialize(windowRect);
788                 taskViewWidth = (int) gridLayout.getTransform(0 /* taskIndex */,
789                         stack.getTaskCount(), new TaskViewTransform(),
790                         stackLayout).rect.width();
791             } else {
792                 Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
793                 if (!taskViewBounds.isEmpty()) {
794                     taskViewWidth = taskViewBounds.width();
795                 }
796             }
797         }
798 
799         if (stack != null && taskViewWidth > 0) {
800             synchronized (mHeaderBarLock) {
801                 if (mHeaderBar.getMeasuredWidth() != taskViewWidth ||
802                         mHeaderBar.getMeasuredHeight() != mTaskBarHeight) {
803                     if (useGridLayout) {
804                         mHeaderBar.setShouldDarkenBackgroundColor(true);
805                         mHeaderBar.setNoUserInteractionState();
806                     }
807                     mHeaderBar.forceLayout();
808                     mHeaderBar.measure(
809                             MeasureSpec.makeMeasureSpec(taskViewWidth, MeasureSpec.EXACTLY),
810                             MeasureSpec.makeMeasureSpec(mTaskBarHeight, MeasureSpec.EXACTLY));
811                 }
812                 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
813             }
814         }
815     }
816 
817     /**
818      * Given the stable insets and the rect for our window, calculates the insets that affect our
819      * window.
820      */
calculateWindowStableInsets(Rect inOutInsets, Rect windowRect, Rect displayRect)821     private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect, Rect displayRect) {
822 
823         // Display rect without insets - available app space
824         Rect appRect = new Rect(displayRect);
825         appRect.inset(inOutInsets);
826 
827         // Our window intersected with available app space
828         Rect windowRectWithInsets = new Rect(windowRect);
829         windowRectWithInsets.intersect(appRect);
830         inOutInsets.left = windowRectWithInsets.left - windowRect.left;
831         inOutInsets.top = windowRectWithInsets.top - windowRect.top;
832         inOutInsets.right = windowRect.right - windowRectWithInsets.right;
833         inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
834     }
835 
836     /**
837      * Preloads the icon of a task.
838      */
preloadIcon(int runningTaskId)839     private void preloadIcon(int runningTaskId) {
840         // Ensure that we load the running task's icon
841         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
842         launchOpts.runningTaskId = runningTaskId;
843         launchOpts.loadThumbnails = false;
844         launchOpts.onlyLoadForCache = true;
845         Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
846     }
847 
848     /**
849      * Creates the activity options for a unknown state->recents transition.
850      */
getUnknownTransitionActivityOptions()851     protected ActivityOptions getUnknownTransitionActivityOptions() {
852         return ActivityOptions.makeCustomAnimation(mContext,
853                 R.anim.recents_from_unknown_enter,
854                 R.anim.recents_from_unknown_exit,
855                 mHandler, null);
856     }
857 
858     /**
859      * Creates the activity options for a home->recents transition.
860      */
getHomeTransitionActivityOptions()861     protected ActivityOptions getHomeTransitionActivityOptions() {
862         return ActivityOptions.makeCustomAnimation(mContext,
863                 R.anim.recents_from_launcher_enter,
864                 R.anim.recents_from_launcher_exit,
865                 mHandler, null);
866     }
867 
868     /**
869      * Creates the activity options for an app->recents transition.
870      */
871     private Pair<ActivityOptions, AppTransitionAnimationSpecsFuture>
getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask, Rect windowOverrideRect)872             getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask,
873                     Rect windowOverrideRect) {
874         final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
875         if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
876             ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
877             ArrayList<Task> tasks = mDummyStackView.getStack().getStackTasks();
878             TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
879             TaskStackViewScroller stackScroller = mDummyStackView.getScroller();
880 
881             mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */);
882             mDummyStackView.updateToInitialState();
883 
884             for (int i = tasks.size() - 1; i >= 0; i--) {
885                 Task task = tasks.get(i);
886                 if (task.isFreeformTask()) {
887                     mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
888                             stackScroller.getStackScroll(), mTmpTransform, null,
889                             windowOverrideRect);
890                     GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform);
891                     Rect toTaskRect = new Rect();
892                     mTmpTransform.rect.round(toTaskRect);
893                     specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
894                 }
895             }
896             AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
897             specs.toArray(specsArray);
898 
899             // For low end ram devices, wait for transition flag is reset when Recents entrance
900             // animation is complete instead of when the transition animation starts
901             return new Pair<>(ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
902                     specsArray, mHandler, isLowRamDevice ? null : mResetToggleFlagListener, this),
903                     null);
904         } else {
905             // Update the destination rect
906             Task toTask = new Task();
907             TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask,
908                     windowOverrideRect);
909 
910             RectF toTaskRect = toTransform.rect;
911             AppTransitionAnimationSpecsFuture future =
912                     new RecentsTransitionHelper(mContext).getAppTransitionFuture(
913                             () -> {
914                         Rect rect = new Rect();
915                         toTaskRect.round(rect);
916                         GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask,
917                                 toTransform);
918                         return Lists.newArrayList(new AppTransitionAnimationSpec(
919                                 toTask.key.id, thumbnail, rect));
920                     });
921 
922             // For low end ram devices, wait for transition flag is reset when Recents entrance
923             // animation is complete instead of when the transition animation starts
924             return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
925                     mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener,
926                     false /* scaleUp */), future);
927         }
928     }
929 
930     /**
931      * Returns the transition rect for the given task id.
932      */
getThumbnailTransitionTransform(TaskStackView stackView, Task runningTaskOut, Rect windowOverrideRect)933     private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
934             Task runningTaskOut, Rect windowOverrideRect) {
935         // Find the running task in the TaskStack
936         TaskStack stack = stackView.getStack();
937         Task launchTask = stack.getLaunchTarget();
938         if (launchTask != null) {
939             runningTaskOut.copyFrom(launchTask);
940         } else {
941             // If no task is specified or we can not find the task just use the front most one
942             launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
943             runningTaskOut.copyFrom(launchTask);
944         }
945 
946         // Get the transform for the running task
947         stackView.updateLayoutAlgorithm(true /* boundScroll */);
948         stackView.updateToInitialState();
949         stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
950                 stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect);
951         return mTmpTransform;
952     }
953 
954     /**
955      * Draws the header of a task used for the window animation into a bitmap.
956      */
drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform)957     private GraphicBuffer drawThumbnailTransitionBitmap(Task toTask,
958             TaskViewTransform toTransform) {
959         SystemServicesProxy ssp = Recents.getSystemServices();
960         int width = (int) toTransform.rect.width();
961         int height = (int) toTransform.rect.height();
962         if (toTransform != null && toTask.key != null && width > 0 && height > 0) {
963             synchronized (mHeaderBarLock) {
964                 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
965                 mHeaderBar.onTaskViewSizeChanged(width, height);
966                 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
967                     return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
968                             null, 1f, 0xFFff0000);
969                 } else {
970                     // Workaround for b/27815919, reset the callback so that we do not trigger an
971                     // invalidate on the header bar as a result of updating the icon
972                     Drawable icon = mHeaderBar.getIconView().getDrawable();
973                     if (icon != null) {
974                         icon.setCallback(null);
975                     }
976                     mHeaderBar.bindToTask(toTask, false /* touchExplorationEnabled */,
977                             disabledInSafeMode);
978                     mHeaderBar.onTaskDataLoaded();
979                     mHeaderBar.setDimAlpha(toTransform.dimAlpha);
980                     return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
981                             mHeaderBar, 1f, 0);
982                 }
983             }
984         }
985         return null;
986     }
987 
988     /**
989      * Shows the recents activity
990      */
startRecentsActivity(ActivityManager.RunningTaskInfo runningTask, boolean isHomeStackVisible, boolean animate, int growTarget)991     protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
992             boolean isHomeStackVisible, boolean animate, int growTarget) {
993         RecentsTaskLoader loader = Recents.getTaskLoader();
994         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
995         SystemServicesProxy ssp = Recents.getSystemServices();
996         boolean isBlacklisted = (runningTask != null)
997                 ? ssp.isBlackListedActivity(runningTask.baseActivity.getClassName())
998                 : false;
999 
1000         int runningTaskId = !mLaunchedWhileDocking && !isBlacklisted && (runningTask != null)
1001                 ? runningTask.id
1002                 : -1;
1003 
1004         // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
1005         // should always preload the tasks now. If we are dragging in recents, reload them as
1006         // the stacks might have changed.
1007         if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) {
1008             // Create a new load plan if preloadRecents() was never triggered
1009             sInstanceLoadPlan = loader.createLoadPlan(mContext);
1010         }
1011         if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
1012             loader.preloadTasks(sInstanceLoadPlan, runningTaskId, !isHomeStackVisible);
1013         }
1014 
1015         TaskStack stack = sInstanceLoadPlan.getTaskStack();
1016         boolean hasRecentTasks = stack.getTaskCount() > 0;
1017         boolean useThumbnailTransition = (runningTask != null) && !isHomeStackVisible &&
1018                 hasRecentTasks;
1019 
1020         // Update the launch state that we need in updateHeaderBarLayout()
1021         launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking;
1022         launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
1023         launchState.launchedFromBlacklistedApp = launchState.launchedFromApp && isBlacklisted;
1024         launchState.launchedFromPipApp = false;
1025         launchState.launchedWithNextPipApp =
1026                 stack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime());
1027         launchState.launchedViaDockGesture = mLaunchedWhileDocking;
1028         launchState.launchedViaDragGesture = mDraggingInRecents;
1029         launchState.launchedToTaskId = runningTaskId;
1030         launchState.launchedWithAltTab = mTriggeredFromAltTab;
1031 
1032         // Disable toggling of recents between starting the activity and it is visible and the app
1033         // has started its transition into recents.
1034         setWaitingForTransitionStart(useThumbnailTransition);
1035 
1036         // Preload the icon (this will be a null-op if we have preloaded the icon already in
1037         // preloadRecents())
1038         preloadIcon(runningTaskId);
1039 
1040         // Update the header bar if necessary
1041         Rect windowOverrideRect = getWindowRectOverride(growTarget);
1042         updateHeaderBarLayout(stack, windowOverrideRect);
1043 
1044         // Prepare the dummy stack for the transition
1045         TaskStackLayoutAlgorithm.VisibilityReport stackVr =
1046                 mDummyStackView.computeStackVisibilityReport();
1047 
1048         // Update the remaining launch state
1049         launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
1050         launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
1051 
1052         if (!animate) {
1053             startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1),
1054                     null /* future */);
1055             return;
1056         }
1057 
1058         Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> pair;
1059         if (isBlacklisted) {
1060             pair = new Pair<>(getUnknownTransitionActivityOptions(), null);
1061         } else if (useThumbnailTransition) {
1062             // Try starting with a thumbnail transition
1063             pair = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect);
1064         } else {
1065             // If there is no thumbnail transition, but is launching from home into recents, then
1066             // use a quick home transition
1067             pair = new Pair<>(hasRecentTasks
1068                     ? getHomeTransitionActivityOptions()
1069                     : getUnknownTransitionActivityOptions(), null);
1070         }
1071         startRecentsActivity(pair.first, pair.second);
1072         mLastToggleTime = SystemClock.elapsedRealtime();
1073     }
1074 
getWindowRectOverride(int growTarget)1075     private Rect getWindowRectOverride(int growTarget) {
1076         if (growTarget == DividerView.INVALID_RECENTS_GROW_TARGET) {
1077             return SystemServicesProxy.getInstance(mContext).getWindowRect();
1078         }
1079         Rect result = new Rect();
1080         Rect displayRect = Recents.getSystemServices().getDisplayRect();
1081         DockedDividerUtils.calculateBoundsForPosition(growTarget, WindowManager.DOCKED_BOTTOM,
1082                 result, displayRect.width(), displayRect.height(),
1083                 Recents.getSystemServices().getDockedDividerSize(mContext));
1084         return result;
1085     }
1086 
1087     /**
1088      * Starts the recents activity.
1089      */
startRecentsActivity(ActivityOptions opts, final AppTransitionAnimationSpecsFuture future)1090     private void startRecentsActivity(ActivityOptions opts,
1091             final AppTransitionAnimationSpecsFuture future) {
1092         Intent intent = new Intent();
1093         intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
1094         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1095                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
1096                 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
1097         HidePipMenuEvent hideMenuEvent = new HidePipMenuEvent();
1098         hideMenuEvent.addPostAnimationCallback(() -> {
1099             Recents.getSystemServices().startActivityAsUserAsync(intent, opts);
1100             EventBus.getDefault().send(new RecentsActivityStartingEvent());
1101             if (future != null) {
1102                 future.precacheSpecs();
1103             }
1104         });
1105         EventBus.getDefault().send(hideMenuEvent);
1106     }
1107 
1108     /**** OnAnimationFinishedListener Implementation ****/
1109 
1110     @Override
onAnimationFinished()1111     public void onAnimationFinished() {
1112         EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());
1113     }
1114 }
1115