1 /*
2  * Copyright 2021 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.quickstep.util;
18 
19 import static com.android.launcher3.Utilities.postAsyncCallback;
20 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP;
21 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM;
22 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTED_SECOND_APP;
23 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_COMPLETE;
24 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
25 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_INITIATED;
26 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
27 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
28 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
29 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_PENDINGINTENT;
30 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_TASK;
31 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SHORTCUT_TASK;
32 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SINGLE_INTENT_FULLSCREEN;
33 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SINGLE_SHORTCUT_FULLSCREEN;
34 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SINGLE_TASK_FULLSCREEN;
35 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_PENDINGINTENT;
36 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT;
37 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK;
38 import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
39 import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
40 
41 import android.animation.Animator;
42 import android.animation.AnimatorListenerAdapter;
43 import android.annotation.NonNull;
44 import android.annotation.UiThread;
45 import android.app.ActivityManager;
46 import android.app.ActivityOptions;
47 import android.app.ActivityThread;
48 import android.app.PendingIntent;
49 import android.content.Intent;
50 import android.content.pm.PackageManager;
51 import android.content.pm.ShortcutInfo;
52 import android.graphics.Rect;
53 import android.graphics.RectF;
54 import android.graphics.drawable.Drawable;
55 import android.os.Bundle;
56 import android.os.Handler;
57 import android.os.IBinder;
58 import android.os.RemoteException;
59 import android.os.SystemClock;
60 import android.os.UserHandle;
61 import android.util.Log;
62 import android.util.Pair;
63 import android.view.RemoteAnimationAdapter;
64 import android.view.RemoteAnimationTarget;
65 import android.view.SurfaceControl;
66 import android.window.IRemoteTransitionFinishedCallback;
67 import android.window.RemoteTransition;
68 import android.window.RemoteTransitionStub;
69 import android.window.TransitionInfo;
70 
71 import androidx.annotation.Nullable;
72 import androidx.annotation.VisibleForTesting;
73 
74 import com.android.internal.logging.InstanceId;
75 import com.android.launcher3.R;
76 import com.android.launcher3.anim.PendingAnimation;
77 import com.android.launcher3.apppairs.AppPairIcon;
78 import com.android.launcher3.config.FeatureFlags;
79 import com.android.launcher3.icons.IconProvider;
80 import com.android.launcher3.logging.StatsLogManager;
81 import com.android.launcher3.model.data.ItemInfo;
82 import com.android.launcher3.statehandlers.DepthController;
83 import com.android.launcher3.statemanager.StateManager;
84 import com.android.launcher3.testing.TestLogging;
85 import com.android.launcher3.testing.shared.TestProtocol;
86 import com.android.launcher3.uioverrides.QuickstepLauncher;
87 import com.android.launcher3.util.BackPressHandler;
88 import com.android.launcher3.util.ComponentKey;
89 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
90 import com.android.quickstep.OverviewComponentObserver;
91 import com.android.quickstep.RecentsAnimationCallbacks;
92 import com.android.quickstep.RecentsAnimationController;
93 import com.android.quickstep.RecentsAnimationTargets;
94 import com.android.quickstep.RecentsModel;
95 import com.android.quickstep.SplitSelectionListener;
96 import com.android.quickstep.SystemUiProxy;
97 import com.android.quickstep.TaskAnimationManager;
98 import com.android.quickstep.views.FloatingTaskView;
99 import com.android.quickstep.views.GroupedTaskView;
100 import com.android.quickstep.views.RecentsView;
101 import com.android.quickstep.views.RecentsViewContainer;
102 import com.android.quickstep.views.SplitInstructionsView;
103 import com.android.systemui.animation.RemoteAnimationRunnerCompat;
104 import com.android.systemui.shared.recents.model.Task;
105 import com.android.systemui.shared.system.ActivityManagerWrapper;
106 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
107 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
108 import com.android.wm.shell.splitscreen.ISplitSelectListener;
109 
110 import java.io.PrintWriter;
111 import java.util.ArrayList;
112 import java.util.Arrays;
113 import java.util.List;
114 import java.util.function.Consumer;
115 
116 /**
117  * Represent data needed for the transient state when user has selected one app for split screen
118  * and is in the process of either a) selecting a second app or b) exiting intention to invoke split
119  */
120 public class SplitSelectStateController {
121     private static final String TAG = "SplitSelectStateCtor";
122 
123     private RecentsViewContainer mContainer;
124     private final Handler mHandler;
125     private final RecentsModel mRecentTasksModel;
126     @Nullable
127     private Runnable mActivityBackCallback;
128     private final SplitAnimationController mSplitAnimationController;
129     private final AppPairsController mAppPairsController;
130     private final SplitSelectDataHolder mSplitSelectDataHolder;
131     private final StatsLogManager mStatsLogManager;
132     private final SystemUiProxy mSystemUiProxy;
133     private final StateManager mStateManager;
134     @Nullable
135     private SplitFromDesktopController mSplitFromDesktopController;
136     @Nullable
137     private DepthController mDepthController;
138     private boolean mRecentsAnimationRunning;
139     /** If {@code true}, animates the existing task view split placeholder view */
140     private boolean mAnimateCurrentTaskDismissal;
141     /**
142      * Acts as a subset of {@link #mAnimateCurrentTaskDismissal}, we can't be dismissing from a
143      * split pair task view without wanting to animate current task dismissal overall
144      */
145     private boolean mDismissingFromSplitPair;
146     /** If not null, this is the TaskView we want to launch from */
147     @Nullable
148     private GroupedTaskView mLaunchingTaskView;
149     /** If not null, this is the icon we want to launch from */
150     private AppPairIcon mLaunchingIconView;
151 
152     /** True when the first selected split app is being launched in fullscreen. */
153     private boolean mLaunchingFirstAppFullscreen;
154 
155     /**
156      * Should be a constant from {@link com.android.internal.jank.Cuj} or -1, does not need to be
157      * set for all launches.
158      */
159     private int mLaunchCuj = -1;
160 
161     private FloatingTaskView mFirstFloatingTaskView;
162     private SplitInstructionsView mSplitInstructionsView;
163 
164     private final List<SplitSelectionListener> mSplitSelectionListeners = new ArrayList<>();
165     /**
166      * Tracks metrics from when first app is selected to split launch or cancellation. This also
167      * gets passed over to shell when attempting to invoke split.
168      */
169     private Pair<InstanceId, com.android.launcher3.logging.InstanceId> mSessionInstanceIds;
170 
171     private final BackPressHandler mSplitBackHandler = new BackPressHandler() {
172         @Override
173         public boolean canHandleBack() {
174             return FeatureFlags.enableSplitContextually() && isSplitSelectActive();
175         }
176 
177         @Override
178         public void onBackInvoked() {
179             // When exiting from split selection, leave current context to go to
180             // homescreen as well
181             getSplitAnimationController().playPlaceholderDismissAnim(mContainer,
182                     LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
183             if (mActivityBackCallback != null) {
184                 mActivityBackCallback.run();
185             }
186         }
187     };
188 
SplitSelectStateController(RecentsViewContainer container, Handler handler, StateManager stateManager, DepthController depthController, StatsLogManager statsLogManager, SystemUiProxy systemUiProxy, RecentsModel recentsModel, Runnable activityBackCallback)189     public SplitSelectStateController(RecentsViewContainer container, Handler handler,
190             StateManager stateManager, DepthController depthController,
191             StatsLogManager statsLogManager, SystemUiProxy systemUiProxy, RecentsModel recentsModel,
192             Runnable activityBackCallback) {
193         mContainer = container;
194         mHandler = handler;
195         mStatsLogManager = statsLogManager;
196         mSystemUiProxy = systemUiProxy;
197         mStateManager = stateManager;
198         mDepthController = depthController;
199         mRecentTasksModel = recentsModel;
200         mActivityBackCallback = activityBackCallback;
201         mSplitAnimationController = new SplitAnimationController(this);
202         mAppPairsController = new AppPairsController(mContainer.asContext(), this, statsLogManager);
203         mSplitSelectDataHolder = new SplitSelectDataHolder(mContainer.asContext());
204     }
205 
onDestroy()206     public void onDestroy() {
207         mContainer = null;
208         mActivityBackCallback = null;
209         mAppPairsController.onDestroy();
210         mSplitSelectDataHolder.onDestroy();
211         if (mSplitFromDesktopController != null) {
212             mSplitFromDesktopController.onDestroy();
213         }
214     }
215 
216     /**
217      * @param alreadyRunningTask if set to {@link android.app.ActivityTaskManager#INVALID_TASK_ID}
218      *                           then @param intent will be used to launch the initial task
219      * @param intent will be ignored if @param alreadyRunningTask is set
220      */
setInitialTaskSelect(@ullable Intent intent, @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent, int alreadyRunningTask)221     public void setInitialTaskSelect(@Nullable Intent intent, @StagePosition int stagePosition,
222             @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent,
223             int alreadyRunningTask) {
224         mSplitSelectDataHolder.setInitialTaskSelect(intent, stagePosition, itemInfo, splitEvent,
225                 alreadyRunningTask);
226         createAndLogInstanceIdsForSession();
227     }
228 
229     /**
230      * To be called after first task selected from using a split shortcut from the fullscreen
231      * running app.
232      */
setInitialTaskSelect(ActivityManager.RunningTaskInfo info, @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent)233     public void setInitialTaskSelect(ActivityManager.RunningTaskInfo info,
234             @StagePosition int stagePosition, @NonNull ItemInfo itemInfo,
235             StatsLogManager.EventEnum splitEvent) {
236         mSplitSelectDataHolder.setInitialTaskSelect(info, stagePosition, itemInfo, splitEvent);
237         createAndLogInstanceIdsForSession();
238     }
239 
240     /**
241      * Given a list of task keys, searches through active Tasks in RecentsModel to find the last
242      * active instances of these tasks. Returns an empty array if there is no such running task.
243      *
244      * @param componentKeys The list of ComponentKeys to search for.
245      * @param callback The callback that will be executed on the list of found tasks.
246      * @param findExactPairMatch If {@code true}, only finds tasks that contain BOTH of the wanted
247      *                           tasks (i.e. searching for a running pair of tasks.)
248      */
findLastActiveTasksAndRunCallback(@ullable List<ComponentKey> componentKeys, boolean findExactPairMatch, Consumer<Task[]> callback)249     public void findLastActiveTasksAndRunCallback(@Nullable List<ComponentKey> componentKeys,
250             boolean findExactPairMatch, Consumer<Task[]> callback) {
251         mRecentTasksModel.getTasks(taskGroups -> {
252             if (componentKeys == null || componentKeys.isEmpty()) {
253                 callback.accept(new Task[]{});
254                 return;
255             }
256 
257             Task[] lastActiveTasks = new Task[componentKeys.size()];
258 
259             if (findExactPairMatch) {
260                 // Loop through tasks in reverse, since they are ordered with most-recent tasks last
261                 for (int i = taskGroups.size() - 1; i >= 0; i--) {
262                     GroupTask groupTask = taskGroups.get(i);
263                     if (isInstanceOfAppPair(
264                             groupTask, componentKeys.get(0), componentKeys.get(1))) {
265                         lastActiveTasks[0] = groupTask.task1;
266                         break;
267                     }
268                 }
269             } else {
270                 // For each key we are looking for, add to lastActiveTasks with the corresponding
271                 // Task (or do nothing if not found).
272                 for (int i = 0; i < componentKeys.size(); i++) {
273                     ComponentKey key = componentKeys.get(i);
274                     Task lastActiveTask = null;
275                     // Loop through tasks in reverse, since they are ordered with recent tasks last
276                     for (int j = taskGroups.size() - 1; j >= 0; j--) {
277                         GroupTask groupTask = taskGroups.get(j);
278                         Task task1 = groupTask.task1;
279                         // Don't add duplicate Tasks
280                         if (isInstanceOfComponent(task1, key)
281                                 && !Arrays.asList(lastActiveTasks).contains(task1)) {
282                             lastActiveTask = task1;
283                             break;
284                         }
285                         Task task2 = groupTask.task2;
286                         if (isInstanceOfComponent(task2, key)
287                                 && !Arrays.asList(lastActiveTasks).contains(task2)) {
288                             lastActiveTask = task2;
289                             break;
290                         }
291                     }
292 
293                     lastActiveTasks[i] = lastActiveTask;
294                 }
295             }
296 
297             callback.accept(lastActiveTasks);
298         });
299     }
300 
301     /**
302      * Checks if a given Task is the most recently-active Task of type componentName. Used for
303      * selecting already-running Tasks for splitscreen.
304      */
isInstanceOfComponent(@ullable Task task, @NonNull ComponentKey componentKey)305     public boolean isInstanceOfComponent(@Nullable Task task, @NonNull ComponentKey componentKey) {
306         // Exclude the task that is already staged
307         if (task == null || task.key.id == mSplitSelectDataHolder.getInitialTaskId()) {
308             return false;
309         }
310 
311         return task.key.baseIntent.getComponent().equals(componentKey.componentName)
312                 && task.key.userId == componentKey.user.getIdentifier();
313     }
314 
315     /**
316      * Checks if a given GroupTask is a pair of apps that matches two given ComponentKeys. We check
317      * both permutations because task order is not guaranteed in GroupTasks.
318      */
isInstanceOfAppPair(GroupTask groupTask, @NonNull ComponentKey componentKey1, @NonNull ComponentKey componentKey2)319     public boolean isInstanceOfAppPair(GroupTask groupTask, @NonNull ComponentKey componentKey1,
320             @NonNull ComponentKey componentKey2) {
321         return ((isInstanceOfComponent(groupTask.task1, componentKey1)
322                 && isInstanceOfComponent(groupTask.task2, componentKey2))
323                 ||
324                 (isInstanceOfComponent(groupTask.task1, componentKey2)
325                         && isInstanceOfComponent(groupTask.task2, componentKey1)));
326     }
327 
328     /**
329      * Listener will only get callbacks going forward from the point of registration. No
330      * methods will be fired upon registering.
331      */
registerSplitListener(@onNull SplitSelectionListener listener)332     public void registerSplitListener(@NonNull SplitSelectionListener listener) {
333         if (mSplitSelectionListeners.contains(listener)) {
334             return;
335         }
336         mSplitSelectionListeners.add(listener);
337     }
338 
unregisterSplitListener(@onNull SplitSelectionListener listener)339     public void unregisterSplitListener(@NonNull SplitSelectionListener listener) {
340         mSplitSelectionListeners.remove(listener);
341     }
342 
dispatchOnSplitSelectionExit()343     private void dispatchOnSplitSelectionExit() {
344         for (SplitSelectionListener listener : mSplitSelectionListeners) {
345             listener.onSplitSelectionExit(false);
346         }
347     }
348 
349     /**
350      * To be called when the both split tasks are ready to be launched. Call after launcher side
351      * animations are complete.
352      */
launchSplitTasks(@ersistentSnapPosition int snapPosition, @Nullable Consumer<Boolean> callback)353     public void launchSplitTasks(@PersistentSnapPosition int snapPosition,
354             @Nullable Consumer<Boolean> callback) {
355         launchTasks(callback, false /* freezeTaskList */, snapPosition, mSessionInstanceIds.first);
356 
357         mStatsLogManager.logger()
358                 .withItemInfo(mSplitSelectDataHolder.getSecondItemInfo())
359                 .withInstanceId(mSessionInstanceIds.second)
360                 .log(LAUNCHER_SPLIT_SELECTED_SECOND_APP);
361     }
362 
363     /**
364      * A version of {@link #launchTasks(Consumer, boolean, int, InstanceId)} with no success
365      * callback.
366      */
launchSplitTasks(@ersistentSnapPosition int snapPosition)367     public void launchSplitTasks(@PersistentSnapPosition int snapPosition) {
368         launchSplitTasks(snapPosition, /* callback */ null);
369     }
370 
371     /**
372      * A version of {@link #launchSplitTasks(int, Consumer)} that launches with default split ratio.
373      */
launchSplitTasks(@ullable Consumer<Boolean> callback)374     public void launchSplitTasks(@Nullable Consumer<Boolean> callback) {
375         launchSplitTasks(SNAP_TO_50_50, callback);
376     }
377 
378     /**
379      * A version of {@link #launchSplitTasks(int, Consumer)} that launches with a default split
380      * ratio and no callback.
381      */
launchSplitTasks()382     public void launchSplitTasks() {
383         launchSplitTasks(SNAP_TO_50_50, null);
384     }
385 
386     /**
387      * Use to log an event when user exists split selection when the second app **IS NOT** selected.
388      * This must be called before playing any exit animations since most animations will call
389      * {@link #resetState()} which removes {@link #mSessionInstanceIds}.
390      */
logExitReason(StatsLogManager.EventEnum splitExitEvent)391     public void logExitReason(StatsLogManager.EventEnum splitExitEvent) {
392         StatsLogManager.StatsLogger logger = mStatsLogManager.logger();
393         if (mSessionInstanceIds != null) {
394             logger.withInstanceId(mSessionInstanceIds.second);
395         } else {
396             Log.w(TAG, "Missing session instanceIds");
397         }
398         logger.log(splitExitEvent);
399     }
400 
401     /**
402      * To be called as soon as user selects the second task (even if animations aren't complete)
403      * @param task The second task that will be launched.
404      */
setSecondTask(Task task, ItemInfo itemInfo)405     public void setSecondTask(Task task, ItemInfo itemInfo) {
406         mSplitSelectDataHolder.setSecondTask(task.key.id, itemInfo);
407     }
408 
409     /**
410      * To be called as soon as user selects the second app (even if animations aren't complete)
411      * @param intent The second intent that will be launched.
412      * @param user The user of that intent.
413      */
setSecondTask(Intent intent, UserHandle user, ItemInfo itemInfo)414     public void setSecondTask(Intent intent, UserHandle user, ItemInfo itemInfo) {
415         mSplitSelectDataHolder.setSecondTask(intent, user, itemInfo);
416     }
417 
418     /**
419      * To be called as soon as user selects the second app (even if animations aren't complete)
420      * @param pendingIntent The second PendingIntent that will be launched.
421      */
setSecondTask(PendingIntent pendingIntent, ItemInfo itemInfo)422     public void setSecondTask(PendingIntent pendingIntent, ItemInfo itemInfo) {
423         mSplitSelectDataHolder.setSecondTask(pendingIntent, itemInfo);
424     }
425 
setSecondWidget(PendingIntent pendingIntent, Intent widgetIntent)426     public void setSecondWidget(PendingIntent pendingIntent, Intent widgetIntent) {
427         mSplitSelectDataHolder.setSecondWidget(pendingIntent, widgetIntent, null /*itemInfo*/);
428     }
429 
430     /**
431      * To be called when we want to launch split pairs from Overview. Split can be initiated from
432      * either Overview or home, or all apps. Either both taskIds are set, or a pending intent + a
433      * fill in intent with a taskId2 are set.
434      * @param shellInstanceId loggingId to be used by shell, will be non-null for actions that
435      *                   create a split instance, null for cases that bring existing instaces to the
436      *                   foreground (quickswitch, launching previous pairs from overview)
437      */
launchTasks(@ullable Consumer<Boolean> callback, boolean freezeTaskList, @PersistentSnapPosition int snapPosition, @Nullable InstanceId shellInstanceId)438     public void launchTasks(@Nullable Consumer<Boolean> callback, boolean freezeTaskList,
439             @PersistentSnapPosition int snapPosition, @Nullable InstanceId shellInstanceId) {
440         TestLogging.recordEvent(
441                 TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
442         final ActivityOptions options1 = ActivityOptions.makeBasic();
443         if (freezeTaskList) {
444             options1.setFreezeRecentTasksReordering();
445         }
446 
447         SplitSelectDataHolder.SplitLaunchData launchData =
448                 mSplitSelectDataHolder.getSplitLaunchData();
449         int firstTaskId = launchData.getInitialTaskId();
450         int secondTaskId = launchData.getSecondTaskId();
451         ShortcutInfo firstShortcut = launchData.getInitialShortcut();
452         ShortcutInfo secondShortcut = launchData.getSecondShortcut();
453         PendingIntent firstPI = launchData.getInitialPendingIntent();
454         PendingIntent secondPI = launchData.getSecondPendingIntent();
455         Intent widgetIntent = launchData.getWidgetSecondIntent();
456         int firstUserId = launchData.getInitialUserId();
457         int secondUserId = launchData.getSecondUserId();
458         int initialStagePosition = launchData.getInitialStagePosition();
459         Bundle optionsBundle = options1.toBundle();
460         Bundle extrasBundle = new Bundle(1);
461         extrasBundle.putParcelable(KEY_EXTRA_WIDGET_INTENT, widgetIntent);
462         if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
463             final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId,
464                     secondTaskId, callback, "LaunchSplitPair");
465             switch (launchData.getSplitLaunchType()) {
466                 case SPLIT_TASK_TASK ->
467                         mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
468                                 null /* options2 */, initialStagePosition, snapPosition,
469                                 remoteTransition, shellInstanceId);
470 
471                 case SPLIT_TASK_PENDINGINTENT ->
472                         mSystemUiProxy.startIntentAndTask(secondPI, secondUserId, optionsBundle,
473                                 firstTaskId, extrasBundle, initialStagePosition, snapPosition,
474                                 remoteTransition, shellInstanceId);
475 
476                 case SPLIT_TASK_SHORTCUT ->
477                         mSystemUiProxy.startShortcutAndTask(secondShortcut, optionsBundle,
478                                 firstTaskId, null /*options2*/, initialStagePosition, snapPosition,
479                                 remoteTransition, shellInstanceId);
480 
481                 case SPLIT_PENDINGINTENT_TASK ->
482                         mSystemUiProxy.startIntentAndTask(firstPI, firstUserId, optionsBundle,
483                                 secondTaskId, null /*options2*/, initialStagePosition, snapPosition,
484                                 remoteTransition, shellInstanceId);
485 
486                 case SPLIT_PENDINGINTENT_PENDINGINTENT ->
487                         mSystemUiProxy.startIntents(firstPI, firstUserId, firstShortcut,
488                                 optionsBundle, secondPI, secondUserId, secondShortcut, extrasBundle,
489                                 initialStagePosition, snapPosition, remoteTransition,
490                                 shellInstanceId);
491 
492                 case SPLIT_SHORTCUT_TASK ->
493                         mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle,
494                                 secondTaskId, null /*options2*/, initialStagePosition, snapPosition,
495                                 remoteTransition, shellInstanceId);
496             }
497         } else {
498             final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, secondTaskId,
499                     callback);
500             switch (launchData.getSplitLaunchType()) {
501                 case SPLIT_TASK_TASK ->
502                         mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle,
503                                 secondTaskId, null /* options2 */, initialStagePosition,
504                                 snapPosition, adapter, shellInstanceId);
505 
506                 case SPLIT_TASK_PENDINGINTENT ->
507                         mSystemUiProxy.startIntentAndTaskWithLegacyTransition(secondPI,
508                                 secondUserId, optionsBundle, firstTaskId, null /*options2*/,
509                                 initialStagePosition, snapPosition, adapter, shellInstanceId);
510 
511                 case SPLIT_TASK_SHORTCUT ->
512                         mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(secondShortcut,
513                                 optionsBundle, firstTaskId, null /*options2*/, initialStagePosition,
514                                 snapPosition, adapter, shellInstanceId);
515 
516                 case SPLIT_PENDINGINTENT_TASK ->
517                         mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId,
518                                 optionsBundle, secondTaskId, null /*options2*/,
519                                 initialStagePosition, snapPosition, adapter, shellInstanceId);
520 
521                 case SPLIT_PENDINGINTENT_PENDINGINTENT ->
522                         mSystemUiProxy.startIntentsWithLegacyTransition(firstPI, firstUserId,
523                                 firstShortcut, optionsBundle, secondPI, secondUserId,
524                                 secondShortcut, null /*options2*/, initialStagePosition,
525                                 snapPosition, adapter, shellInstanceId);
526 
527                 case SPLIT_SHORTCUT_TASK ->
528                         mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(firstShortcut,
529                                 optionsBundle, secondTaskId, null /*options2*/,
530                                 initialStagePosition, snapPosition, adapter, shellInstanceId);
531             }
532         }
533     }
534 
535     /**
536      * Used to launch split screen from a split pair that already exists, optionally with a custom
537      * remote transition.
538      * <p>
539      * See {@link SplitSelectStateController#launchExistingSplitPair(
540      * GroupedTaskView, int, int, int, Consumer, boolean, int, RemoteTransition)}
541      */
launchExistingSplitPair(@ullable GroupedTaskView groupedTaskView, int firstTaskId, int secondTaskId, @StagePosition int stagePosition, Consumer<Boolean> callback, boolean freezeTaskList, @PersistentSnapPosition int snapPosition)542     public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView,
543             int firstTaskId, int secondTaskId, @StagePosition int stagePosition,
544             Consumer<Boolean> callback, boolean freezeTaskList,
545             @PersistentSnapPosition int snapPosition) {
546         launchExistingSplitPair(
547                 groupedTaskView,
548                 firstTaskId,
549                 secondTaskId,
550                 stagePosition,
551                 callback,
552                 freezeTaskList,
553                 snapPosition,
554                 /* remoteTransition= */ null);
555     }
556 
557 
558     /**
559      * Used to launch split screen from a split pair that already exists (usually accessible through
560      * Overview). This is different than {@link #launchTasks(Consumer, boolean, int, InstanceId)}
561      * in that this only launches split screen that are existing tasks. This doesn't determine which
562      * API should be used (i.e. launching split with existing tasks vs intents vs shortcuts, etc).
563      *
564      * <p/>
565      * NOTE: This is not to be used to launch AppPairs.
566      */
launchExistingSplitPair(@ullable GroupedTaskView groupedTaskView, int firstTaskId, int secondTaskId, @StagePosition int stagePosition, Consumer<Boolean> callback, boolean freezeTaskList, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition)567     public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView,
568             int firstTaskId, int secondTaskId, @StagePosition int stagePosition,
569             Consumer<Boolean> callback, boolean freezeTaskList,
570             @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition) {
571         mLaunchingTaskView = groupedTaskView;
572         final ActivityOptions options1 = ActivityOptions.makeBasic();
573         if (freezeTaskList) {
574             options1.setFreezeRecentTasksReordering();
575         }
576         Bundle optionsBundle = options1.toBundle();
577 
578         if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
579             final RemoteTransition transition = remoteTransition == null
580                     ? getShellRemoteTransition(
581                             firstTaskId, secondTaskId, callback, "LaunchExistingPair")
582                     : remoteTransition;
583             mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, null /* options2 */,
584                     stagePosition, snapPosition, transition, null /*shellInstanceId*/);
585         } else {
586             final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId,
587                     secondTaskId, callback);
588             mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle, secondTaskId,
589                     null /* options2 */, stagePosition, snapPosition, adapter,
590                     null /*shellInstanceId*/);
591         }
592     }
593 
594     /**
595      * Launches the initially selected task/intent in fullscreen (note the same SystemUi APIs are
596      * used as {@link #launchSplitTasks(int, Consumer)} because they are overloaded to launch both
597      * split and fullscreen tasks)
598      */
launchInitialAppFullscreen(Consumer<Boolean> callback)599     public void launchInitialAppFullscreen(Consumer<Boolean> callback) {
600         final ActivityOptions options1 = ActivityOptions.makeBasic();
601         SplitSelectDataHolder.SplitLaunchData launchData =
602                 mSplitSelectDataHolder.getFullscreenLaunchData();
603         int firstTaskId = launchData.getInitialTaskId();
604         int secondTaskId = launchData.getSecondTaskId();
605         PendingIntent firstPI = launchData.getInitialPendingIntent();
606         int firstUserId = launchData.getInitialUserId();
607         int initialStagePosition = launchData.getInitialStagePosition();
608         ShortcutInfo initialShortcut = launchData.getInitialShortcut();
609         Bundle optionsBundle = options1.toBundle();
610 
611         final RemoteSplitLaunchTransitionRunner animationRunner =
612                 new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback);
613         final RemoteTransition remoteTransition = new RemoteTransition(animationRunner,
614                 ActivityThread.currentActivityThread().getApplicationThread(),
615                 "LaunchAppFullscreen");
616         InstanceId instanceId = mSessionInstanceIds.first;
617         if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
618             switch (launchData.getSplitLaunchType()) {
619                 case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasks(firstTaskId,
620                         optionsBundle, secondTaskId, null /* options2 */, initialStagePosition,
621                         SNAP_TO_50_50, remoteTransition, instanceId);
622                 case SPLIT_SINGLE_INTENT_FULLSCREEN -> mSystemUiProxy.startIntentAndTask(firstPI,
623                         firstUserId, optionsBundle, secondTaskId, null /*options2*/,
624                         initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId);
625                 case SPLIT_SINGLE_SHORTCUT_FULLSCREEN -> mSystemUiProxy.startShortcutAndTask(
626                         initialShortcut, optionsBundle, firstTaskId, null /* options2 */,
627                         initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId);
628             }
629         } else {
630             final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId,
631                     secondTaskId, callback);
632             switch (launchData.getSplitLaunchType()) {
633                 case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasksWithLegacyTransition(
634                         firstTaskId, optionsBundle, secondTaskId, null /* options2 */,
635                         initialStagePosition, SNAP_TO_50_50, adapter, instanceId);
636                 case SPLIT_SINGLE_INTENT_FULLSCREEN ->
637                         mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId,
638                                 optionsBundle, secondTaskId, null /*options2*/,
639                                 initialStagePosition, SNAP_TO_50_50, adapter, instanceId);
640                 case SPLIT_SINGLE_SHORTCUT_FULLSCREEN ->
641                         mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(
642                                 initialShortcut, optionsBundle, firstTaskId, null /* options2 */,
643                                 initialStagePosition, SNAP_TO_50_50, adapter, instanceId);
644             }
645         }
646     }
647 
648     /**
649      * Init {@code SplitFromDesktopController}
650      */
initSplitFromDesktopController(QuickstepLauncher launcher, OverviewComponentObserver overviewComponentObserver)651     public void initSplitFromDesktopController(QuickstepLauncher launcher,
652             OverviewComponentObserver overviewComponentObserver) {
653         initSplitFromDesktopController(
654                 new SplitFromDesktopController(launcher, overviewComponentObserver));
655     }
656 
657     @VisibleForTesting
initSplitFromDesktopController(SplitFromDesktopController controller)658     void initSplitFromDesktopController(SplitFromDesktopController controller) {
659         mSplitFromDesktopController = controller;
660     }
661 
getShellRemoteTransition(int firstTaskId, int secondTaskId, @Nullable Consumer<Boolean> callback, String transitionName)662     private RemoteTransition getShellRemoteTransition(int firstTaskId, int secondTaskId,
663             @Nullable Consumer<Boolean> callback, String transitionName) {
664         final RemoteSplitLaunchTransitionRunner animationRunner =
665                 new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback);
666         return new RemoteTransition(animationRunner,
667                 ActivityThread.currentActivityThread().getApplicationThread(), transitionName);
668     }
669 
getLegacyRemoteAdapter(int firstTaskId, int secondTaskId, @Nullable Consumer<Boolean> callback)670     private RemoteAnimationAdapter getLegacyRemoteAdapter(int firstTaskId, int secondTaskId,
671             @Nullable Consumer<Boolean> callback) {
672         final RemoteSplitLaunchAnimationRunner animationRunner =
673                 new RemoteSplitLaunchAnimationRunner(firstTaskId, secondTaskId, callback);
674         return new RemoteAnimationAdapter(animationRunner, 300, 150,
675                 ActivityThread.currentActivityThread().getApplicationThread());
676     }
677 
678     /**
679      * Will initialize {@link #mSessionInstanceIds} if null and log the first split event from
680      * {@link #mSplitSelectDataHolder}
681      */
createAndLogInstanceIdsForSession()682     private void createAndLogInstanceIdsForSession() {
683         if (mSessionInstanceIds != null) {
684             Log.w(TAG, "SessionIds should be null");
685         }
686         // Log separately the start of the session and then the first app selected
687         mSessionInstanceIds = LogUtils.getShellShareableInstanceId();
688         mStatsLogManager.logger()
689                 .withInstanceId(mSessionInstanceIds.second)
690                 .log(LAUNCHER_SPLIT_SELECTION_INITIATED);
691 
692         mStatsLogManager.logger()
693                 .withItemInfo(mSplitSelectDataHolder.getItemInfo())
694                 .withInstanceId(mSessionInstanceIds.second)
695                 .log(mSplitSelectDataHolder.getSplitEvent());
696     }
697 
getActiveSplitStagePosition()698     public @StagePosition int getActiveSplitStagePosition() {
699         return mSplitSelectDataHolder.getInitialStagePosition();
700     }
701 
getSplitEvent()702     public StatsLogManager.EventEnum getSplitEvent() {
703         return mSplitSelectDataHolder.getSplitEvent();
704     }
705 
setRecentsAnimationRunning(boolean running)706     public void setRecentsAnimationRunning(boolean running) {
707         mRecentsAnimationRunning = running;
708     }
709 
isAnimateCurrentTaskDismissal()710     public boolean isAnimateCurrentTaskDismissal() {
711         return mAnimateCurrentTaskDismissal;
712     }
713 
setAnimateCurrentTaskDismissal(boolean animateCurrentTaskDismissal)714     public void setAnimateCurrentTaskDismissal(boolean animateCurrentTaskDismissal) {
715         mAnimateCurrentTaskDismissal = animateCurrentTaskDismissal;
716     }
717 
isDismissingFromSplitPair()718     public boolean isDismissingFromSplitPair() {
719         return mDismissingFromSplitPair;
720     }
721 
setDismissingFromSplitPair(boolean dismissingFromSplitPair)722     public void setDismissingFromSplitPair(boolean dismissingFromSplitPair) {
723         mDismissingFromSplitPair = dismissingFromSplitPair;
724     }
725 
getSplitAnimationController()726     public SplitAnimationController getSplitAnimationController() {
727         return mSplitAnimationController;
728     }
729 
setLaunchingCuj(int launchCuj)730     public void setLaunchingCuj(int launchCuj) {
731         mLaunchCuj = launchCuj;
732     }
733 
734     /**
735      * Requires Shell Transitions
736      */
737     private class RemoteSplitLaunchTransitionRunner extends RemoteTransitionStub {
738 
739         private final int mInitialTaskId;
740         private final int mSecondTaskId;
741         private Consumer<Boolean> mFinishCallback;
742 
RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId, @Nullable Consumer<Boolean> callback)743         RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId,
744                 @Nullable Consumer<Boolean> callback) {
745             mInitialTaskId = initialTaskId;
746             mSecondTaskId = secondTaskId;
747             mFinishCallback = callback;
748         }
749 
750         @Override
startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishedCallback)751         public void startAnimation(IBinder transition, TransitionInfo info,
752                 SurfaceControl.Transaction t,
753                 IRemoteTransitionFinishedCallback finishedCallback) {
754             final Runnable finishAdapter = () ->  {
755                 try {
756                     finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
757                 } catch (RemoteException e) {
758                     Log.e(TAG, "Failed to call transition finished callback", e);
759                 }
760             };
761 
762             MAIN_EXECUTOR.execute(() -> {
763                 // Only animate from taskView if it's already visible
764                 boolean shouldLaunchFromTaskView = mLaunchingTaskView != null
765                         && mLaunchingTaskView.getRecentsView() != null
766                         && mLaunchingTaskView.getRecentsView().isTaskViewVisible(
767                         mLaunchingTaskView);
768                 mSplitAnimationController.playSplitLaunchAnimation(
769                         shouldLaunchFromTaskView ? mLaunchingTaskView : null,
770                         mLaunchingIconView,
771                         mInitialTaskId,
772                         mSecondTaskId,
773                         null /* apps */,
774                         null /* wallpapers */,
775                         null /* nonApps */,
776                         mStateManager,
777                         mDepthController,
778                         info, t, () -> {
779                             finishAdapter.run();
780                             cleanup(true /*success*/);
781                         });
782             });
783         }
784 
785         @Override
onTransitionConsumed(IBinder transition, boolean aborted)786         public void onTransitionConsumed(IBinder transition, boolean aborted)
787                 throws RemoteException {
788             MAIN_EXECUTOR.execute(() -> {
789                 cleanup(false /*success*/);
790             });
791         }
792 
793         /**
794          * Must be called on UI thread.
795          * @param success if launching the split apps occurred successfully or not
796          */
797         @UiThread
cleanup(boolean success)798         private void cleanup(boolean success) {
799             if (mFinishCallback != null) {
800                 mFinishCallback.accept(success);
801                 mFinishCallback = null;
802             }
803             resetState();
804         }
805     }
806 
807     /**
808      * LEGACY
809      * Remote animation runner for animation to launch an app.
810      */
811     private class RemoteSplitLaunchAnimationRunner extends RemoteAnimationRunnerCompat {
812 
813         private final int mInitialTaskId;
814         private final int mSecondTaskId;
815         private final Consumer<Boolean> mSuccessCallback;
816 
RemoteSplitLaunchAnimationRunner(int initialTaskId, int secondTaskId, @Nullable Consumer<Boolean> successCallback)817         RemoteSplitLaunchAnimationRunner(int initialTaskId, int secondTaskId,
818                 @Nullable Consumer<Boolean> successCallback) {
819             mInitialTaskId = initialTaskId;
820             mSecondTaskId = secondTaskId;
821             mSuccessCallback = successCallback;
822         }
823 
824         @Override
onAnimationStart(int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, Runnable finishedCallback)825         public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
826                 RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
827                 Runnable finishedCallback) {
828             postAsyncCallback(mHandler,
829                     () -> mSplitAnimationController.playSplitLaunchAnimation(mLaunchingTaskView,
830                             mLaunchingIconView, mInitialTaskId, mSecondTaskId, apps, wallpapers,
831                             nonApps, mStateManager, mDepthController, null /* info */, null /* t */,
832                             () -> {
833                                 finishedCallback.run();
834                                 if (mSuccessCallback != null) {
835                                     mSuccessCallback.accept(true);
836                                 }
837                                 resetState();
838                             }));
839         }
840 
841         @Override
onAnimationCancelled()842         public void onAnimationCancelled() {
843             postAsyncCallback(mHandler, () -> {
844                 if (mSuccessCallback != null) {
845                     // Launching legacy tasks while recents animation is running will always cause
846                     // onAnimationCancelled to be called (should be fixed w/ shell transitions?)
847                     mSuccessCallback.accept(mRecentsAnimationRunning);
848                 }
849                 resetState();
850             });
851         }
852     }
853 
854     /**
855      * To be called whenever we exit split selection state. If
856      * {@link FeatureFlags#enableSplitContextually()} is set, this should be the
857      * central way split is getting reset, which should then go through the callbacks to reset
858      * other state.
859      */
resetState()860     public void resetState() {
861         mSplitSelectDataHolder.resetState();
862         dispatchOnSplitSelectionExit();
863         mRecentsAnimationRunning = false;
864         mLaunchingTaskView = null;
865         mLaunchingIconView = null;
866         mAnimateCurrentTaskDismissal = false;
867         mDismissingFromSplitPair = false;
868         mFirstFloatingTaskView = null;
869         mSplitInstructionsView = null;
870         mLaunchingFirstAppFullscreen = false;
871 
872         if (mLaunchCuj != -1) {
873             InteractionJankMonitorWrapper.end(mLaunchCuj);
874         }
875         mLaunchCuj = -1;
876 
877         if (mSessionInstanceIds != null) {
878             mStatsLogManager.logger()
879                     .withInstanceId(mSessionInstanceIds.second)
880                     .log(LAUNCHER_SPLIT_SELECTION_COMPLETE);
881         }
882         mSessionInstanceIds = null;
883     }
884 
885     /**
886      * @return {@code true} if first task has been selected and waiting for the second task to be
887      *         chosen
888      */
isSplitSelectActive()889     public boolean isSplitSelectActive() {
890         return mSplitSelectDataHolder.isSplitSelectActive();
891     }
892 
893     /**
894      * @return {@code true} if the first and second task have been chosen and split is waiting to
895      *          be launched
896      */
isBothSplitAppsConfirmed()897     public boolean isBothSplitAppsConfirmed() {
898         return mSplitSelectDataHolder.isBothSplitAppsConfirmed();
899     }
900 
isLaunchingFirstAppFullscreen()901     public boolean isLaunchingFirstAppFullscreen() {
902         return mLaunchingFirstAppFullscreen;
903     }
904 
getInitialTaskId()905     public int getInitialTaskId() {
906         return mSplitSelectDataHolder.getInitialTaskId();
907     }
908 
getSecondTaskId()909     public int getSecondTaskId() {
910         return mSplitSelectDataHolder.getSecondTaskId();
911     }
912 
setLaunchingFirstAppFullscreen()913     public void setLaunchingFirstAppFullscreen() {
914         mLaunchingFirstAppFullscreen = true;
915     }
setFirstFloatingTaskView(FloatingTaskView floatingTaskView)916     public void setFirstFloatingTaskView(FloatingTaskView floatingTaskView) {
917         mFirstFloatingTaskView = floatingTaskView;
918     }
919 
setSplitInstructionsView(SplitInstructionsView splitInstructionsView)920     public void setSplitInstructionsView(SplitInstructionsView splitInstructionsView) {
921         mSplitInstructionsView = splitInstructionsView;
922     }
923 
924     @Nullable
getFirstFloatingTaskView()925     public FloatingTaskView getFirstFloatingTaskView() {
926         return mFirstFloatingTaskView;
927     }
928 
929     @Nullable
getSplitInstructionsView()930     public SplitInstructionsView getSplitInstructionsView() {
931         return mSplitInstructionsView;
932     }
933 
getAppPairsController()934     public AppPairsController getAppPairsController() {
935         return mAppPairsController;
936     }
937 
setLaunchingIconView(AppPairIcon launchingIconView)938     public void setLaunchingIconView(AppPairIcon launchingIconView) {
939         mLaunchingIconView = launchingIconView;
940     }
941 
getSplitBackHandler()942     public BackPressHandler getSplitBackHandler() {
943         return mSplitBackHandler;
944     }
945 
dump(String prefix, PrintWriter writer)946     public void dump(String prefix, PrintWriter writer) {
947         if (mSplitSelectDataHolder != null) {
948             mSplitSelectDataHolder.dump(prefix, writer);
949         }
950     }
951 
952     public class SplitFromDesktopController {
953         private static final String TAG = "SplitFromDesktopController";
954 
955         private final QuickstepLauncher mLauncher;
956         private final OverviewComponentObserver mOverviewComponentObserver;
957         private final int mSplitPlaceholderSize;
958         private final int mSplitPlaceholderInset;
959         private ActivityManager.RunningTaskInfo mTaskInfo;
960         private ISplitSelectListener mSplitSelectListener;
961         private Drawable mAppIcon;
962 
SplitFromDesktopController(QuickstepLauncher launcher, OverviewComponentObserver overviewComponentObserver)963         public SplitFromDesktopController(QuickstepLauncher launcher,
964                 OverviewComponentObserver overviewComponentObserver) {
965             mLauncher = launcher;
966             mOverviewComponentObserver = overviewComponentObserver;
967             mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize(
968                     R.dimen.split_placeholder_size);
969             mSplitPlaceholderInset = mLauncher.getResources().getDimensionPixelSize(
970                     R.dimen.split_placeholder_inset);
971             mSplitSelectListener = new ISplitSelectListener.Stub() {
972                 @Override
973                 public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
974                         int splitPosition, Rect taskBounds) {
975                     MAIN_EXECUTOR.execute(() -> enterSplitSelect(taskInfo, splitPosition,
976                             taskBounds));
977                     return true;
978                 }
979             };
980             SystemUiProxy.INSTANCE.get(mLauncher).registerSplitSelectListener(mSplitSelectListener);
981         }
982 
onDestroy()983         void onDestroy() {
984             SystemUiProxy.INSTANCE.get(mLauncher).unregisterSplitSelectListener(
985                     mSplitSelectListener);
986             mSplitSelectListener = null;
987         }
988 
989         /**
990          * Enter split select from desktop mode.
991          * @param taskInfo the desktop task to move to split stage
992          * @param splitPosition the stage position used for this transition
993          * @param taskBounds the bounds of the task, used for {@link FloatingTaskView} animation
994          */
enterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, int splitPosition, Rect taskBounds)995         public void enterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
996                 int splitPosition, Rect taskBounds) {
997             mTaskInfo = taskInfo;
998             String packageName = mTaskInfo.realActivity.getPackageName();
999             PackageManager pm = mLauncher.getApplicationContext().getPackageManager();
1000             IconProvider provider = new IconProvider(mLauncher.getApplicationContext());
1001             try {
1002                 mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
1003                      PackageManager.ComponentInfoFlags.of(0)));
1004             } catch (PackageManager.NameNotFoundException e) {
1005                 Log.w(TAG, "Package not found: " + packageName, e);
1006             }
1007             RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks(
1008                     SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()),
1009                     false /* allowMinimizeSplitScreen */);
1010 
1011             DesktopSplitRecentsAnimationListener listener =
1012                     new DesktopSplitRecentsAnimationListener(splitPosition, taskBounds);
1013 
1014             MAIN_EXECUTOR.execute(() -> {
1015                 callbacks.addListener(listener);
1016                 UI_HELPER_EXECUTOR.execute(
1017                         // Transition from app to enter stage split in launcher with
1018                         // recents animation.
1019                         () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
1020                                 mOverviewComponentObserver.getOverviewIntent(),
1021                                 SystemClock.uptimeMillis(), callbacks, null, null));
1022             });
1023         }
1024 
1025         private class DesktopSplitRecentsAnimationListener implements
1026                 RecentsAnimationCallbacks.RecentsAnimationListener {
1027             private final Rect mTempRect = new Rect();
1028             private final RectF mTaskBounds = new RectF();
1029             private final int mSplitPosition;
1030 
DesktopSplitRecentsAnimationListener(int splitPosition, Rect taskBounds)1031             DesktopSplitRecentsAnimationListener(int splitPosition, Rect taskBounds) {
1032                 mSplitPosition = splitPosition;
1033                 mTaskBounds.set(taskBounds);
1034             }
1035 
1036             @Override
onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets)1037             public void onRecentsAnimationStart(RecentsAnimationController controller,
1038                     RecentsAnimationTargets targets) {
1039                 StatsLogManager.LauncherEvent launcherDesktopSplitEvent =
1040                         mSplitPosition == STAGE_POSITION_BOTTOM_OR_RIGHT ?
1041                         LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM :
1042                         LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP;
1043                 setInitialTaskSelect(mTaskInfo, mSplitPosition,
1044                         null, launcherDesktopSplitEvent);
1045 
1046                 RecentsView recentsView = mLauncher.getOverviewPanel();
1047                 recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds(
1048                         mSplitPlaceholderSize, mSplitPlaceholderInset,
1049                         mLauncher.getDeviceProfile(), getActiveSplitStagePosition(), mTempRect);
1050 
1051                 PendingAnimation anim = new PendingAnimation(
1052                         SplitAnimationTimings.TABLET_HOME_TO_SPLIT.getDuration());
1053                 final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView(
1054                         mLauncher, mLauncher.getDragLayer(),
1055                         null /* thumbnail */,
1056                         mAppIcon, new RectF());
1057                 floatingTaskView.setAlpha(1);
1058                 floatingTaskView.addStagingAnimation(anim, mTaskBounds, mTempRect,
1059                         false /* fadeWithThumbnail */, true /* isStagedTask */);
1060                 setFirstFloatingTaskView(floatingTaskView);
1061 
1062                 anim.addListener(new AnimatorListenerAdapter() {
1063                     @Override
1064                     public void onAnimationStart(Animator animation) {
1065                         controller.finish(true /* toRecents */, null /* onFinishComplete */,
1066                                 false /* sendUserLeaveHint */);
1067                     }
1068                     @Override
1069                     public void onAnimationEnd(Animator animation) {
1070                         SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext())
1071                                 .onDesktopSplitSelectAnimComplete(mTaskInfo);
1072                     }
1073                 });
1074                 anim.buildAnim().start();
1075             }
1076         }
1077     }
1078 }
1079