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