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