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