• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.wm.shell.splitscreen;
18 
19 import static android.app.ActivityManager.START_SUCCESS;
20 import static android.app.ActivityManager.START_TASK_TO_FRONT;
21 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
23 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
24 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
25 import static android.view.Display.DEFAULT_DISPLAY;
26 
27 import static com.android.wm.shell.Flags.enableFlexibleSplit;
28 import static com.android.wm.shell.common.MultiInstanceHelper.getComponent;
29 import static com.android.wm.shell.common.MultiInstanceHelper.getShortcutComponent;
30 import static com.android.wm.shell.common.MultiInstanceHelper.samePackage;
31 import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
32 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
33 import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
34 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
35 import static com.android.wm.shell.shared.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
36 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0;
37 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1;
38 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED;
39 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
40 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
41 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
42 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
43 
44 import android.app.ActivityManager;
45 import android.app.ActivityOptions;
46 import android.app.ActivityTaskManager;
47 import android.app.PendingIntent;
48 import android.app.TaskInfo;
49 import android.content.ComponentName;
50 import android.content.Context;
51 import android.content.Intent;
52 import android.content.pm.LauncherApps;
53 import android.content.pm.ShortcutInfo;
54 import android.graphics.Rect;
55 import android.os.Bundle;
56 import android.os.Handler;
57 import android.os.IBinder;
58 import android.os.RemoteException;
59 import android.os.UserHandle;
60 import android.util.ArrayMap;
61 import android.util.Log;
62 import android.util.Slog;
63 import android.view.IRemoteAnimationFinishedCallback;
64 import android.view.IRemoteAnimationRunner;
65 import android.view.RemoteAnimationAdapter;
66 import android.view.RemoteAnimationTarget;
67 import android.view.SurfaceControl;
68 import android.view.WindowManager;
69 import android.widget.Toast;
70 import android.window.RemoteTransition;
71 import android.window.WindowContainerToken;
72 import android.window.WindowContainerTransaction;
73 
74 import androidx.annotation.BinderThread;
75 import androidx.annotation.IntDef;
76 import androidx.annotation.NonNull;
77 import androidx.annotation.Nullable;
78 
79 import com.android.internal.annotations.VisibleForTesting;
80 import com.android.internal.logging.InstanceId;
81 import com.android.internal.protolog.ProtoLog;
82 import com.android.launcher3.icons.IconProvider;
83 import com.android.wm.shell.R;
84 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
85 import com.android.wm.shell.ShellTaskOrganizer;
86 import com.android.wm.shell.common.ComponentUtils;
87 import com.android.wm.shell.common.DisplayController;
88 import com.android.wm.shell.common.DisplayImeController;
89 import com.android.wm.shell.common.DisplayInsetsController;
90 import com.android.wm.shell.common.ExternalInterfaceBinder;
91 import com.android.wm.shell.common.LaunchAdjacentController;
92 import com.android.wm.shell.common.MultiInstanceHelper;
93 import com.android.wm.shell.common.RemoteCallable;
94 import com.android.wm.shell.common.ShellExecutor;
95 import com.android.wm.shell.common.SingleInstanceRemoteListener;
96 import com.android.wm.shell.common.SyncTransactionQueue;
97 import com.android.wm.shell.common.split.SplitScreenUtils;
98 import com.android.wm.shell.common.split.SplitState;
99 import com.android.wm.shell.desktopmode.DesktopTasksController;
100 import com.android.wm.shell.draganddrop.DragAndDropController;
101 import com.android.wm.shell.draganddrop.SplitDragPolicy;
102 import com.android.wm.shell.protolog.ShellProtoLogGroup;
103 import com.android.wm.shell.recents.RecentTasksController;
104 import com.android.wm.shell.shared.TransactionPool;
105 import com.android.wm.shell.shared.annotations.ExternalThread;
106 import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
107 import com.android.wm.shell.shared.split.SplitScreenConstants.SplitIndex;
108 import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
109 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
110 import com.android.wm.shell.sysui.KeyguardChangeListener;
111 import com.android.wm.shell.sysui.ShellCommandHandler;
112 import com.android.wm.shell.sysui.ShellController;
113 import com.android.wm.shell.sysui.ShellInit;
114 import com.android.wm.shell.transition.Transitions;
115 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
116 
117 import java.io.PrintWriter;
118 import java.lang.annotation.Retention;
119 import java.lang.annotation.RetentionPolicy;
120 import java.util.Optional;
121 import java.util.concurrent.Executor;
122 import java.util.concurrent.atomic.AtomicBoolean;
123 
124 /**
125  * Class manages split-screen multitasking mode and implements the main interface
126  * {@link SplitScreen}.
127  *
128  * @see StageCoordinator
129  */
130 // TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
131 public class SplitScreenController implements SplitDragPolicy.Starter,
132         RemoteCallable<SplitScreenController>, KeyguardChangeListener {
133     private static final String TAG = SplitScreenController.class.getSimpleName();
134 
135     public static final int EXIT_REASON_UNKNOWN = 0;
136     public static final int EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW = 1;
137     public static final int EXIT_REASON_APP_FINISHED = 2;
138     public static final int EXIT_REASON_DEVICE_FOLDED = 3;
139     public static final int EXIT_REASON_DRAG_DIVIDER = 4;
140     public static final int EXIT_REASON_RETURN_HOME = 5;
141     public static final int EXIT_REASON_ROOT_TASK_VANISHED = 6;
142     public static final int EXIT_REASON_SCREEN_LOCKED = 7;
143     public static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
144     public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
145     public static final int EXIT_REASON_RECREATE_SPLIT = 10;
146     public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 11;
147     public static final int EXIT_REASON_DESKTOP_MODE = 12;
148     public static final int EXIT_REASON_FULLSCREEN_REQUEST = 13;
149     @IntDef(value = {
150             EXIT_REASON_UNKNOWN,
151             EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
152             EXIT_REASON_APP_FINISHED,
153             EXIT_REASON_DEVICE_FOLDED,
154             EXIT_REASON_DRAG_DIVIDER,
155             EXIT_REASON_RETURN_HOME,
156             EXIT_REASON_ROOT_TASK_VANISHED,
157             EXIT_REASON_SCREEN_LOCKED,
158             EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP,
159             EXIT_REASON_CHILD_TASK_ENTER_PIP,
160             EXIT_REASON_RECREATE_SPLIT,
161             EXIT_REASON_FULLSCREEN_SHORTCUT,
162             EXIT_REASON_DESKTOP_MODE,
163             EXIT_REASON_FULLSCREEN_REQUEST
164     })
165     @Retention(RetentionPolicy.SOURCE)
166     @interface ExitReason{}
167 
168     public static final int ENTER_REASON_UNKNOWN = 0;
169     public static final int ENTER_REASON_MULTI_INSTANCE = 1;
170     public static final int ENTER_REASON_DRAG = 2;
171     public static final int ENTER_REASON_LAUNCHER = 3;
172     /** Acts as a mapping to the actual EnterReasons as defined in the logging proto */
173     @IntDef(value = {
174             ENTER_REASON_MULTI_INSTANCE,
175             ENTER_REASON_DRAG,
176             ENTER_REASON_LAUNCHER,
177             ENTER_REASON_UNKNOWN
178     })
179     public @interface SplitEnterReason {
180     }
181 
182     private final ShellCommandHandler mShellCommandHandler;
183     private final ShellController mShellController;
184     private final ShellTaskOrganizer mTaskOrganizer;
185     private final SyncTransactionQueue mSyncQueue;
186     private final Context mContext;
187     private final LauncherApps mLauncherApps;
188     private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
189     private final ShellExecutor mMainExecutor;
190     private final Handler mMainHandler;
191     private final SplitScreenImpl mImpl = new SplitScreenImpl();
192     private final DisplayController mDisplayController;
193     private final DisplayImeController mDisplayImeController;
194     private final DisplayInsetsController mDisplayInsetsController;
195     private final DragAndDropController mDragAndDropController;
196     private final Transitions mTransitions;
197     private final TransactionPool mTransactionPool;
198     private final IconProvider mIconProvider;
199     private final Optional<RecentTasksController> mRecentTasksOptional;
200     private final LaunchAdjacentController mLaunchAdjacentController;
201     private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
202     private final Optional<DesktopTasksController> mDesktopTasksController;
203     private final MultiInstanceHelper mMultiInstanceHelpher;
204     private final SplitState mSplitState;
205     private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
206 
207     @VisibleForTesting
208     StageCoordinator mStageCoordinator;
209 
210     /**
211      * @param stageCoordinator if null, a stage coordinator will be created when this controller is
212      *                         initialized. Can be non-null for testing purposes.
213      */
SplitScreenController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTDAOrganizer, DisplayController displayController, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, DragAndDropController dragAndDropController, Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, Optional<WindowDecorViewModel> windowDecorViewModel, Optional<DesktopTasksController> desktopTasksController, @Nullable StageCoordinator stageCoordinator, MultiInstanceHelper multiInstanceHelper, SplitState splitState, ShellExecutor mainExecutor, Handler mainHandler)214     public SplitScreenController(Context context,
215             ShellInit shellInit,
216             ShellCommandHandler shellCommandHandler,
217             ShellController shellController,
218             ShellTaskOrganizer shellTaskOrganizer,
219             SyncTransactionQueue syncQueue,
220             RootTaskDisplayAreaOrganizer rootTDAOrganizer,
221             DisplayController displayController,
222             DisplayImeController displayImeController,
223             DisplayInsetsController displayInsetsController,
224             DragAndDropController dragAndDropController,
225             Transitions transitions,
226             TransactionPool transactionPool,
227             IconProvider iconProvider,
228             Optional<RecentTasksController> recentTasks,
229             LaunchAdjacentController launchAdjacentController,
230             Optional<WindowDecorViewModel> windowDecorViewModel,
231             Optional<DesktopTasksController> desktopTasksController,
232             @Nullable StageCoordinator stageCoordinator,
233             MultiInstanceHelper multiInstanceHelper,
234             SplitState splitState,
235             ShellExecutor mainExecutor,
236             Handler mainHandler) {
237         mShellCommandHandler = shellCommandHandler;
238         mShellController = shellController;
239         mTaskOrganizer = shellTaskOrganizer;
240         mSyncQueue = syncQueue;
241         mContext = context;
242         mLauncherApps = context.getSystemService(LauncherApps.class);
243         mRootTDAOrganizer = rootTDAOrganizer;
244         mMainExecutor = mainExecutor;
245         mMainHandler = mainHandler;
246         mDisplayController = displayController;
247         mDisplayImeController = displayImeController;
248         mDisplayInsetsController = displayInsetsController;
249         mDragAndDropController = dragAndDropController;
250         mTransitions = transitions;
251         mTransactionPool = transactionPool;
252         mIconProvider = iconProvider;
253         mRecentTasksOptional = recentTasks;
254         mLaunchAdjacentController = launchAdjacentController;
255         mWindowDecorViewModel = windowDecorViewModel;
256         mDesktopTasksController = desktopTasksController;
257         mStageCoordinator = stageCoordinator;
258         mMultiInstanceHelpher = multiInstanceHelper;
259         mSplitState = splitState;
260         mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
261         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
262         //                    override for this controller from the base module
263         if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
264             shellInit.addInitCallback(this::onInit, this);
265         }
266     }
267 
asSplitScreen()268     public SplitScreen asSplitScreen() {
269         return mImpl;
270     }
271 
createExternalInterface()272     private ExternalInterfaceBinder createExternalInterface() {
273         return new ISplitScreenImpl(this);
274     }
275 
276     /**
277      * This will be called after ShellTaskOrganizer has initialized/registered because of the
278      * dependency order.
279      */
280     @VisibleForTesting
onInit()281     void onInit() {
282         mShellCommandHandler.addDumpCallback(this::dump, this);
283         mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler,
284                 this);
285         mShellController.addKeyguardChangeListener(this);
286         mShellController.addExternalInterface(ISplitScreen.DESCRIPTOR,
287                 this::createExternalInterface, this);
288         if (mStageCoordinator == null) {
289             // TODO: Multi-display
290             mStageCoordinator = createStageCoordinator();
291         }
292         if (mDragAndDropController != null) {
293             mDragAndDropController.setSplitScreenController(this);
294         }
295         mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this));
296         mDesktopTasksController.ifPresent(controller -> controller.setSplitScreenController(this));
297     }
298 
createStageCoordinator()299     protected StageCoordinator createStageCoordinator() {
300         return new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
301                 mTaskOrganizer, mDisplayController, mDisplayImeController,
302                 mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
303                 mMainExecutor, mMainHandler, mRecentTasksOptional, mLaunchAdjacentController,
304                 mWindowDecorViewModel, mSplitState, mDesktopTasksController, mRootTDAOrganizer);
305     }
306 
307     @Override
getContext()308     public Context getContext() {
309         return mContext;
310     }
311 
312     @Override
getRemoteCallExecutor()313     public ShellExecutor getRemoteCallExecutor() {
314         return mMainExecutor;
315     }
316 
isSplitScreenVisible()317     public boolean isSplitScreenVisible() {
318         return mStageCoordinator.isSplitScreenVisible();
319     }
320 
getTransitionHandler()321     public StageCoordinator getTransitionHandler() {
322         return mStageCoordinator;
323     }
324 
getMultiDisplayProvider()325     public SplitMultiDisplayProvider getMultiDisplayProvider() {
326         return mStageCoordinator;
327     }
328 
329     @Nullable
getTaskInfo(@plitPosition int splitPosition)330     public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) {
331         if (!isSplitScreenVisible() || splitPosition == SPLIT_POSITION_UNDEFINED) {
332             return null;
333         }
334 
335         final int taskId = mStageCoordinator.getTaskId(splitPosition);
336         return mTaskOrganizer.getRunningTaskInfo(taskId);
337     }
338 
339     /**
340      * @return an Array of RunningTaskInfo's ordered by leftToRight or topTopBottom
341      */
getAllTaskInfos()342     public ActivityManager.RunningTaskInfo[] getAllTaskInfos() {
343         // TODO(b/349828130) Add the third stage task info and not rely on positions
344         ActivityManager.RunningTaskInfo topLeftTask = getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
345         ActivityManager.RunningTaskInfo bottomRightTask =
346                 getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
347         if (topLeftTask != null && bottomRightTask != null) {
348             return new ActivityManager.RunningTaskInfo[]{topLeftTask, bottomRightTask};
349         }
350 
351         return new ActivityManager.RunningTaskInfo[0];
352     }
353 
354     /** Check task is under split or not by taskId. */
isTaskInSplitScreen(int taskId)355     public boolean isTaskInSplitScreen(int taskId) {
356         return mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
357     }
358 
359     /** Get the split stage of task is under it. */
getStageOfTask(int taskId)360     public @StageType int getStageOfTask(int taskId) {
361         return mStageCoordinator.getStageOfTask(taskId);
362     }
363 
364     /**
365      * @return {@code true} if we should create a left-right split, {@code false} if we should
366      * create a top-bottom split.
367      */
isLeftRightSplit()368     public boolean isLeftRightSplit() {
369         return mStageCoordinator.isLeftRightSplit();
370     }
371 
372     /** Check split is foreground and task is under split or not by taskId. */
isTaskInSplitScreenForeground(int taskId)373     public boolean isTaskInSplitScreenForeground(int taskId) {
374         return isTaskInSplitScreen(taskId) && isSplitScreenVisible();
375     }
376 
377     /** Check whether the task is the single-top root or the root of one of the stages. */
isTaskRootOrStageRoot(int taskId)378     public boolean isTaskRootOrStageRoot(int taskId) {
379         return mStageCoordinator.isRootOrStageRoot(taskId);
380     }
381 
getSplitPosition(int taskId)382     public @SplitPosition int getSplitPosition(int taskId) {
383         return mStageCoordinator.getSplitPosition(taskId);
384     }
385 
moveToSideStage(int taskId, @SplitPosition int sideStagePosition)386     public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
387         return moveToStage(taskId, sideStagePosition, new WindowContainerTransaction());
388     }
389 
390     /**
391      * Update surfaces of the split screen layout based on the current state
392      * @param transaction to write the updates to
393      */
updateSplitScreenSurfaces(SurfaceControl.Transaction transaction)394     public void updateSplitScreenSurfaces(SurfaceControl.Transaction transaction) {
395         mStageCoordinator.updateSurfaces(transaction);
396     }
397 
moveToStage(int taskId, @SplitPosition int stagePosition, WindowContainerTransaction wct)398     private boolean moveToStage(int taskId, @SplitPosition int stagePosition,
399             WindowContainerTransaction wct) {
400         final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
401         if (task == null) {
402             throw new IllegalArgumentException("Unknown taskId" + taskId);
403         }
404         if (isTaskInSplitScreen(taskId)) {
405             throw new IllegalArgumentException("taskId is in split" + taskId);
406         }
407         return mStageCoordinator.moveToStage(task, stagePosition, wct);
408     }
409 
setSideStagePosition(@plitPosition int sideStagePosition)410     public void setSideStagePosition(@SplitPosition int sideStagePosition) {
411         mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
412     }
413 
414     /**
415      * Doing necessary window transaction for other transition handler need to enter split in
416      * transition.
417      */
prepareEnterSplitScreen(WindowContainerTransaction wct, ActivityManager.RunningTaskInfo taskInfo, int startPosition)418     public void prepareEnterSplitScreen(WindowContainerTransaction wct,
419             ActivityManager.RunningTaskInfo taskInfo, int startPosition) {
420         mStageCoordinator.prepareEnterSplitScreen(wct, taskInfo, startPosition,
421                 false /* resizeAnim */, SPLIT_INDEX_UNDEFINED);
422     }
423 
424     /**
425      * Doing necessary surface transaction for other transition handler need to enter split in
426      * transition when finished.
427      */
finishEnterSplitScreen(SurfaceControl.Transaction finishT)428     public void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
429         mStageCoordinator.finishEnterSplitScreen(finishT);
430     }
431 
432     /**
433      * Performs previous child eviction and such to prepare for the pip task expending into one of
434      * the split stages
435      *
436      * @param taskInfo TaskInfo of the pip task
437      */
onPipExpandToSplit(WindowContainerTransaction wct, ActivityManager.RunningTaskInfo taskInfo)438     public void onPipExpandToSplit(WindowContainerTransaction wct,
439             ActivityManager.RunningTaskInfo taskInfo) {
440         mStageCoordinator.onPipExpandToSplit(wct, taskInfo);
441     }
442 
443     /**
444      * Doing necessary window transaction for other transition handler need to exit split in
445      * transition.
446      */
prepareExitSplitScreen(WindowContainerTransaction wct, @StageType int stageToTop, @ExitReason int reason)447     public void prepareExitSplitScreen(WindowContainerTransaction wct,
448             @StageType int stageToTop, @ExitReason int reason) {
449         mStageCoordinator.prepareExitSplitScreen(stageToTop, wct, reason);
450         mStageCoordinator.clearSplitPairedInRecents(reason);
451     }
452 
453     /**
454      * Determines which split position a new instance of a task should take.
455      * @param callingTask The task requesting a new instance.
456      * @return the split position of the new instance
457      */
determineNewInstancePosition(@onNull ActivityManager.RunningTaskInfo callingTask)458     public int determineNewInstancePosition(@NonNull ActivityManager.RunningTaskInfo callingTask) {
459         if (callingTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
460                 || getSplitPosition(callingTask.taskId) == SPLIT_POSITION_TOP_OR_LEFT) {
461             return SPLIT_POSITION_BOTTOM_OR_RIGHT;
462         } else {
463             return SPLIT_POSITION_TOP_OR_LEFT;
464         }
465     }
466 
467     /**
468      * Determines which split index a new instance of a task should take.
469      * @param callingTask The task requesting a new instance.
470      * @return the split index of the new instance
471      */
472     @SplitIndex
determineNewInstanceIndex(@onNull ActivityManager.RunningTaskInfo callingTask)473     public int determineNewInstanceIndex(@NonNull ActivityManager.RunningTaskInfo callingTask) {
474         if (!enableFlexibleSplit()) {
475             throw new IllegalStateException("Use determineNewInstancePosition");
476         }
477         if (callingTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
478                 || getSplitPosition(callingTask.taskId) == SPLIT_POSITION_TOP_OR_LEFT) {
479             return SPLIT_INDEX_1;
480         } else {
481             return SPLIT_INDEX_0;
482         }
483     }
484 
enterSplitScreen(int taskId, boolean leftOrTop)485     public void enterSplitScreen(int taskId, boolean leftOrTop) {
486         enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction());
487     }
488 
enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct)489     public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
490         final int stagePosition =
491                 leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
492         moveToStage(taskId, stagePosition, wct);
493     }
494 
exitSplitScreen(int toTopTaskId, @ExitReason int exitReason)495     public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
496         mStageCoordinator.dismissSplitScreen(toTopTaskId, exitReason);
497     }
498 
499     @Override
onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss)500     public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
501             boolean animatingDismiss) {
502         mStageCoordinator.onKeyguardStateChanged(visible, occluded);
503     }
504 
onStartedGoingToSleep()505     public void onStartedGoingToSleep() {
506         mStageCoordinator.onStartedGoingToSleep();
507     }
508 
onStartedWakingUp()509     public void onStartedWakingUp() {
510         mStageCoordinator.onStartedWakingUp();
511     }
512 
exitSplitScreenOnHide(boolean exitSplitScreenOnHide)513     public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
514         mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
515     }
516 
getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds)517     public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
518         mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
519     }
520 
521     /** Get the parent-based coordinates for split stages. */
getRefStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds)522     public void getRefStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
523         mStageCoordinator.getRefStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
524     }
525 
registerSplitScreenListener(SplitScreen.SplitScreenListener listener)526     public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
527         mStageCoordinator.registerSplitScreenListener(listener);
528     }
529 
unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener)530     public void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
531         mStageCoordinator.unregisterSplitScreenListener(listener);
532     }
533 
534     /** Register a split select listener */
registerSplitSelectListener(SplitScreen.SplitSelectListener listener)535     public void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) {
536         mStageCoordinator.registerSplitSelectListener(listener);
537     }
538 
539     /** Unregister a split select listener */
unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener)540     public void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) {
541         mStageCoordinator.unregisterSplitSelectListener(listener);
542     }
543 
goToFullscreenFromSplit()544     public void goToFullscreenFromSplit() {
545         if (mStageCoordinator.isSplitActive()) {
546             mStageCoordinator.goToFullscreenFromSplit();
547         }
548     }
549 
setSplitscreenFocus(boolean leftOrTop)550     public void setSplitscreenFocus(boolean leftOrTop) {
551         if (mStageCoordinator.isSplitActive()) {
552             mStageCoordinator.grantFocusToPosition(leftOrTop);
553         }
554     }
555 
556     /** Move the specified task to fullscreen, regardless of focus state. */
moveTaskToFullscreen(int taskId, int exitReason)557     public void moveTaskToFullscreen(int taskId, int exitReason) {
558         mStageCoordinator.moveTaskToFullscreen(taskId, exitReason);
559     }
560 
isLaunchToSplit(TaskInfo taskInfo)561     public boolean isLaunchToSplit(TaskInfo taskInfo) {
562         return mStageCoordinator.isLaunchToSplit(taskInfo);
563     }
564 
getActivateSplitPosition(TaskInfo taskInfo)565     public int getActivateSplitPosition(TaskInfo taskInfo) {
566         return mStageCoordinator.getActivateSplitPosition(taskInfo);
567     }
568 
569     /** Start two tasks in parallel as a splitscreen pair. */
startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)570     public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
571             @Nullable Bundle options2, @SplitPosition int splitPosition,
572             @PersistentSnapPosition int snapPosition,
573             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
574         mStageCoordinator.startTasks(taskId1, options1, taskId2, options2, splitPosition,
575                 snapPosition, remoteTransition, instanceId);
576     }
577 
578     /**
579      * Move a task to split select
580      * @param taskInfo the task being moved to split select
581      * @param wct transaction to apply if this is a valid request
582      * @param splitPosition the split position this task should move to
583      * @param taskBounds current freeform bounds of the task entering split
584      *
585      * @return the token of the transition that started as a result of entering split select.
586      */
587     @Nullable
requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, WindowContainerTransaction wct, int splitPosition, Rect taskBounds)588     public IBinder requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
589             WindowContainerTransaction wct, int splitPosition, Rect taskBounds) {
590         return mStageCoordinator.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds);
591     }
592 
593     /**
594      * Starts an existing task into split.
595      * TODO(b/351900580): We should remove this path and use StageCoordinator#startTask() instead
596      * @param hideTaskToken is not supported.
597      */
startTask(int taskId, @SplitPosition int position, @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken)598     public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
599             @Nullable WindowContainerToken hideTaskToken) {
600         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
601                 "Legacy startTask does not support hide task token");
602         if (isTaskInSplitScreenForeground(taskId)) return;
603         final int[] result = new int[1];
604         IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
605             @Override
606             public void onAnimationStart(@WindowManager.TransitionOldType int transit,
607                     RemoteAnimationTarget[] apps,
608                     RemoteAnimationTarget[] wallpapers,
609                     RemoteAnimationTarget[] nonApps,
610                     final IRemoteAnimationFinishedCallback finishedCallback) {
611                 try {
612                     finishedCallback.onAnimationFinished();
613                 } catch (RemoteException e) {
614                     Slog.e(TAG, "Failed to invoke onAnimationFinished", e);
615                 }
616                 if (result[0] == START_SUCCESS || result[0] == START_TASK_TO_FRONT) {
617                     final WindowContainerTransaction evictWct = new WindowContainerTransaction();
618                     mStageCoordinator.prepareEvictNonOpeningChildTasks(position, apps, evictWct);
619                     mSyncQueue.queue(evictWct);
620                 }
621             }
622             @Override
623             public void onAnimationCancelled() {
624                 final WindowContainerTransaction evictWct = new WindowContainerTransaction();
625                 mStageCoordinator.prepareEvictInvisibleChildTasks(evictWct);
626                 mSyncQueue.queue(evictWct);
627             }
628         };
629         options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
630                 null /* wct */);
631         RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper,
632                 0 /* duration */, 0 /* statusBarTransitionDelay */);
633         ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
634         activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
635 
636         try {
637             result[0] = ActivityTaskManager.getService().startActivityFromRecents(taskId,
638                     activityOptions.toBundle());
639         } catch (RemoteException e) {
640             Slog.e(TAG, "Failed to launch task", e);
641         }
642     }
643 
644     /**
645      * Starts an existing task via StageCoordinator.
646      */
startTask(int taskId, @SplitPosition int position, @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index)647     public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
648             @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index) {
649         mStageCoordinator.startTask(taskId, position, options, hideTaskToken, index);
650     }
651 
652     /**
653      * See {@link #startShortcut(String, String, int, Bundle, UserHandle)}
654      * @param instanceId to be used by {@link SplitscreenEventLogger}
655      */
startShortcut(String packageName, String shortcutId, @SplitPosition int position, @Nullable Bundle options, UserHandle user, @NonNull InstanceId instanceId)656     public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
657             @Nullable Bundle options, UserHandle user, @NonNull InstanceId instanceId) {
658         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startShortcut: reason=%d", ENTER_REASON_LAUNCHER);
659         mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER);
660         startShortcut(packageName, shortcutId, position, options, user);
661     }
662 
663     @Override
startShortcut(String packageName, String shortcutId, @SplitPosition int position, @Nullable Bundle options, UserHandle user)664     public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
665             @Nullable Bundle options, UserHandle user) {
666         if (options == null) options = new Bundle();
667         final ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
668         final int userId = user.getIdentifier();
669 
670         if (samePackage(packageName, getPackageName(reverseSplitPosition(position), null),
671                 userId, getUserId(reverseSplitPosition(position), null))) {
672             if (mMultiInstanceHelpher.supportsMultiInstanceSplit(
673                     getShortcutComponent(packageName, shortcutId, user, mLauncherApps), userId)) {
674                 activityOptions.setApplyMultipleTaskFlagForShortcut(true);
675                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
676             } else if (isSplitScreenVisible()) {
677                 mStageCoordinator.switchSplitPosition("startShortcut");
678                 return;
679             } else {
680                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
681                         "Cancel entering split as not supporting multi-instances");
682                 Log.w(TAG, splitFailureMessage("startShortcut",
683                         "app package " + packageName + " does not support multi-instance"));
684                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
685                         Toast.LENGTH_SHORT).show();
686                 return;
687             }
688         }
689 
690         mStageCoordinator.startShortcut(packageName, shortcutId, position,
691                 activityOptions.toBundle(), user);
692     }
693 
startShortcutAndTask(@onNull ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)694     void startShortcutAndTask(@NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options1,
695             int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
696             @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
697             InstanceId instanceId) {
698         if (options1 == null) options1 = new Bundle();
699         final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
700         final String packageName1 = shortcutInfo.getPackage();
701         // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in
702         //       recents that hasn't launched and is not being organized
703         final String packageName2 = ComponentUtils.getPackageName(taskId, mTaskOrganizer);
704         final int userId1 = shortcutInfo.getUserId();
705         final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
706         if (samePackage(packageName1, packageName2, userId1, userId2)) {
707             if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity(),
708                     userId1)) {
709                 activityOptions.setApplyMultipleTaskFlagForShortcut(true);
710                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
711             } else {
712                 if (mRecentTasksOptional.isPresent()) {
713                     mRecentTasksOptional.get().removeSplitPair(taskId);
714                 }
715                 taskId = INVALID_TASK_ID;
716                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
717                         "Cancel entering split as not supporting multi-instances");
718                 Log.w(TAG, splitFailureMessage("startShortcutAndTask",
719                         "app package " + packageName1 + " does not support multi-instance"));
720                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
721                         Toast.LENGTH_SHORT).show();
722             }
723         }
724         mStageCoordinator.startShortcutAndTask(shortcutInfo, activityOptions.toBundle(), taskId,
725                 options2, splitPosition, snapPosition, remoteTransition, instanceId);
726     }
727 
728     /**
729      * See {@link #startIntent(PendingIntent, int, Intent, int, Bundle)}
730      * @param instanceId to be used by {@link SplitscreenEventLogger}
731      */
startIntentWithInstanceId(PendingIntent intent, int userId, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId)732     public void startIntentWithInstanceId(PendingIntent intent, int userId,
733             @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options,
734             @NonNull InstanceId instanceId) {
735         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntentWithInstanceId: reason=%d",
736                 ENTER_REASON_LAUNCHER);
737         mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER);
738         // TODO(b/349828130) currently pass in index_undefined until we can revisit these
739         //  specific cases in the future. Only focusing on parity with starting intent/task
740         startIntent(intent, userId, fillInIntent, position, options, null /* hideTaskToken */,
741                 SPLIT_INDEX_UNDEFINED);
742     }
743 
startIntentAndTask(PendingIntent pendingIntent, int userId1, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)744     private void startIntentAndTask(PendingIntent pendingIntent, int userId1,
745             @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
746             @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
747             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
748         Intent fillInIntent = null;
749         final String packageName1 = ComponentUtils.getPackageName(pendingIntent);
750         // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in
751         //       recents that hasn't launched and is not being organized
752         final String packageName2 = ComponentUtils.getPackageName(taskId, mTaskOrganizer);
753         final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
754         boolean setSecondIntentMultipleTask = false;
755         if (samePackage(packageName1, packageName2, userId1, userId2)) {
756             if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent),
757                     userId1)) {
758                 setSecondIntentMultipleTask = true;
759                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
760             } else {
761                 if (mRecentTasksOptional.isPresent()) {
762                     mRecentTasksOptional.get().removeSplitPair(taskId);
763                 }
764                 taskId = INVALID_TASK_ID;
765                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
766                         "Cancel entering split as not supporting multi-instances");
767                 Log.w(TAG, splitFailureMessage("startIntentAndTask",
768                         "app package " + packageName1 + " does not support multi-instance"));
769                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
770                         Toast.LENGTH_SHORT).show();
771             }
772         }
773         if (options2 != null) {
774             Intent widgetIntent = options2.getParcelable(KEY_EXTRA_WIDGET_INTENT, Intent.class);
775             fillInIntent = resolveWidgetFillinIntent(widgetIntent, setSecondIntentMultipleTask);
776         }
777         mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
778                 options2, splitPosition, snapPosition, remoteTransition, instanceId);
779     }
780 
startIntents(PendingIntent pendingIntent1, int userId1, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)781     private void startIntents(PendingIntent pendingIntent1, int userId1,
782             @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
783             PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
784             @Nullable Bundle options2, @SplitPosition int splitPosition,
785             @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
786             InstanceId instanceId) {
787         Intent fillInIntent1 = null;
788         Intent fillInIntent2 = null;
789         final String packageName1 = ComponentUtils.getPackageName(pendingIntent1);
790         final String packageName2 = ComponentUtils.getPackageName(pendingIntent2);
791         final ActivityOptions activityOptions1 = options1 != null
792                 ? ActivityOptions.fromBundle(options1) : ActivityOptions.makeBasic();
793         final ActivityOptions activityOptions2 = options2 != null
794                 ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
795         boolean setSecondIntentMultipleTask = false;
796         if (samePackage(packageName1, packageName2, userId1, userId2)) {
797             if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1),
798                     userId1)) {
799                 fillInIntent1 = new Intent();
800                 fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
801                 setSecondIntentMultipleTask = true;
802 
803                 if (shortcutInfo1 != null) {
804                     activityOptions1.setApplyMultipleTaskFlagForShortcut(true);
805                 }
806                 if (shortcutInfo2 != null) {
807                     activityOptions2.setApplyMultipleTaskFlagForShortcut(true);
808                 }
809                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
810             } else {
811                 pendingIntent2 = null;
812                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
813                         "Cancel entering split as not supporting multi-instances");
814                 Log.w(TAG, splitFailureMessage("startIntents",
815                         "app package " + packageName1 + " does not support multi-instance"));
816                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
817                         Toast.LENGTH_SHORT).show();
818             }
819         }
820         if (options2 != null) {
821             Intent widgetIntent = options2.getParcelable(KEY_EXTRA_WIDGET_INTENT, Intent.class);
822             fillInIntent2 = resolveWidgetFillinIntent(widgetIntent, setSecondIntentMultipleTask);
823         }
824         mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1,
825                 activityOptions1.toBundle(), pendingIntent2, fillInIntent2, shortcutInfo2,
826                 activityOptions2.toBundle(), splitPosition, snapPosition, remoteTransition,
827                 instanceId);
828     }
829 
830     @Override
startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index)831     public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
832             @SplitPosition int position, @Nullable Bundle options,
833             @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index) {
834         startIntent(intent, userId1, fillInIntent, position, options, hideTaskToken,
835                 false /* forceLaunchNewTask */, index);
836     }
837 
838     /**
839      * Starts the given intent into split.
840      *
841      * @param hideTaskToken If non-null, a task matching this token will be moved to back in the
842      *                      same window container transaction as the starting of the intent.
843      * @param forceLaunchNewTask If true, this method will skip the check for a background task
844      *                           matching the intent and launch a new task.
845      */
startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken, boolean forceLaunchNewTask, @SplitIndex int index)846     public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
847             @SplitPosition int position, @Nullable Bundle options,
848             @Nullable WindowContainerToken hideTaskToken, boolean forceLaunchNewTask,
849             @SplitIndex int index) {
850         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
851                 "startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1,
852                 fillInIntent, position);
853         // Flag this as a no-user-action launch to prevent sending user leaving event to the current
854         // top activity since it's going to be put into another side of the split. This prevents the
855         // current top activity from going into pip mode due to user leaving event.
856         if (fillInIntent == null) fillInIntent = new Intent();
857         fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
858 
859         final String packageName1 = ComponentUtils.getPackageName(intent);
860         final String packageName2 = getPackageName(reverseSplitPosition(position), hideTaskToken);
861         final int userId2 = getUserId(reverseSplitPosition(position), hideTaskToken);
862         final ComponentName component = intent.getIntent().getComponent();
863 
864         // To prevent accumulating large number of instances in the background, reuse task
865         // in the background. If we don't explicitly reuse, new may be created even if the app
866         // isn't multi-instance because WM won't automatically remove/reuse the previous instance
867         final ActivityManager.RecentTaskInfo taskInfo = forceLaunchNewTask ? null :
868                 mRecentTasksOptional
869                         .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1,
870                         hideTaskToken))
871                 .orElse(null);
872         if (taskInfo != null) {
873             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
874                     "Found suitable background task=%s", taskInfo);
875             mStageCoordinator.startTask(taskInfo.taskId, position, options, hideTaskToken, index);
876 
877             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background");
878             return;
879         }
880         if (samePackage(packageName1, packageName2, userId1, userId2)) {
881             if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(intent), userId1)) {
882                 // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
883                 // the split and there is no reusable background task.
884                 fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
885                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
886             } else if (isSplitScreenVisible()) {
887                 mStageCoordinator.switchSplitPosition("startIntent");
888                 return;
889             } else {
890                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
891                         "Cancel entering split as not supporting multi-instances");
892                 Log.w(TAG, splitFailureMessage("startIntent",
893                         "app package " + packageName1 + " does not support multi-instance"));
894                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
895                         Toast.LENGTH_SHORT).show();
896                 return;
897             }
898         }
899 
900         mStageCoordinator.startIntent(intent, fillInIntent, position, options, hideTaskToken,
901                 index);
902     }
903 
904     /**
905      * Retrieve package name of a specific split position if split screen is activated, otherwise
906      * returns the package name of the top running task.
907      * TODO(b/351900580): Merge this with getUserId() so we don't make multiple binder calls
908      */
909     @Nullable
getPackageName(@plitPosition int position, @Nullable WindowContainerToken ignoreTaskToken)910     private String getPackageName(@SplitPosition int position,
911             @Nullable WindowContainerToken ignoreTaskToken) {
912         ActivityManager.RunningTaskInfo taskInfo;
913         if (isSplitScreenVisible()) {
914             taskInfo = getTaskInfo(position);
915         } else {
916             taskInfo = mRecentTasksOptional
917                     .map(recentTasks -> recentTasks.getTopRunningTask(ignoreTaskToken))
918                     .orElse(null);
919             if (!isValidToSplit(taskInfo)) {
920                 return null;
921             }
922         }
923 
924         return taskInfo != null ? ComponentUtils.getPackageName(taskInfo.baseIntent) : null;
925     }
926 
927     /**
928      * Retrieve user id of a specific split position if split screen is activated, otherwise
929      * returns the user id of the top running task.
930      * TODO: Merge this with getPackageName() so we don't make multiple binder calls
931      */
getUserId(@plitPosition int position, @Nullable WindowContainerToken ignoreTaskToken)932     private int getUserId(@SplitPosition int position,
933             @Nullable WindowContainerToken ignoreTaskToken) {
934         ActivityManager.RunningTaskInfo taskInfo;
935         if (isSplitScreenVisible()) {
936             taskInfo = getTaskInfo(position);
937         } else {
938             taskInfo = mRecentTasksOptional
939                     .map(recentTasks -> recentTasks.getTopRunningTask(ignoreTaskToken))
940                     .orElse(null);
941             if (!isValidToSplit(taskInfo)) {
942                 return -1;
943             }
944         }
945 
946         return taskInfo != null ? taskInfo.userId : -1;
947     }
948 
949     /**
950      * Determines whether the widgetIntent needs to be modified if multiple tasks of its
951      * corresponding package/app are supported. There are 4 possible paths:
952      *  <li> We select a widget for second app which is the same as the first app </li>
953      *  <li> We select a widget for second app which is different from the first app </li>
954      *  <li> No widgets involved, we select a second app that is the same as first app </li>
955      *  <li> No widgets involved, we select a second app that is different from the first app
956      *       (returns null) </li>
957      *
958      * @return an {@link Intent} with the appropriate {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}
959      *         added on or not depending on {@param launchMultipleTasks}.
960      */
961     @Nullable
resolveWidgetFillinIntent(@ullable Intent widgetIntent, boolean launchMultipleTasks)962     private Intent resolveWidgetFillinIntent(@Nullable Intent widgetIntent,
963             boolean launchMultipleTasks) {
964         Intent fillInIntent2 = null;
965         if (launchMultipleTasks && widgetIntent != null) {
966             fillInIntent2 = widgetIntent;
967             fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
968         } else if (widgetIntent != null) {
969             fillInIntent2 = widgetIntent;
970         } else if (launchMultipleTasks) {
971             fillInIntent2 = new Intent();
972             fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
973         }
974         return fillInIntent2;
975     }
976 
reparentSplitTasksForAnimation(RemoteAnimationTarget[] apps, SurfaceControl.Transaction t, String callsite)977     private SurfaceControl reparentSplitTasksForAnimation(RemoteAnimationTarget[] apps,
978             SurfaceControl.Transaction t, String callsite) {
979         final SurfaceControl.Builder builder = new SurfaceControl.Builder()
980                 .setContainerLayer()
981                 .setName("RecentsAnimationSplitTasks")
982                 .setHidden(false)
983                 .setCallsite(callsite);
984         mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
985         final SurfaceControl splitTasksLayer = builder.build();
986 
987         for (int i = 0; i < apps.length; ++i) {
988             final RemoteAnimationTarget appTarget = apps[i];
989             t.reparent(appTarget.leash, splitTasksLayer);
990             t.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
991                     appTarget.screenSpaceBounds.top);
992         }
993         return splitTasksLayer;
994     }
995     /**
996      * Drop callback when splitscreen is entered.
997      */
onDroppedToSplit(@plitPosition int position, InstanceId dragSessionId)998     public void onDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
999         mStageCoordinator.onDroppedToSplit(position, dragSessionId);
1000     }
1001 
switchSplitPosition(String reason)1002     void switchSplitPosition(String reason) {
1003         if (isSplitScreenVisible()) {
1004             mStageCoordinator.switchSplitPosition(reason);
1005         }
1006     }
1007 
1008     /**
1009      * Return the {@param exitReason} as a string.
1010      */
exitReasonToString(int exitReason)1011     public static String exitReasonToString(int exitReason) {
1012         switch (exitReason) {
1013             case EXIT_REASON_UNKNOWN:
1014                 return "UNKNOWN_EXIT";
1015             case EXIT_REASON_DRAG_DIVIDER:
1016                 return "DRAG_DIVIDER";
1017             case EXIT_REASON_RETURN_HOME:
1018                 return "RETURN_HOME";
1019             case EXIT_REASON_SCREEN_LOCKED:
1020                 return "SCREEN_LOCKED";
1021             case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
1022                 return "SCREEN_LOCKED_SHOW_ON_TOP";
1023             case EXIT_REASON_DEVICE_FOLDED:
1024                 return "DEVICE_FOLDED";
1025             case EXIT_REASON_ROOT_TASK_VANISHED:
1026                 return "ROOT_TASK_VANISHED";
1027             case EXIT_REASON_APP_FINISHED:
1028                 return "APP_FINISHED";
1029             case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW:
1030                 return "APP_DOES_NOT_SUPPORT_MULTIWINDOW";
1031             case EXIT_REASON_CHILD_TASK_ENTER_PIP:
1032                 return "CHILD_TASK_ENTER_PIP";
1033             case EXIT_REASON_RECREATE_SPLIT:
1034                 return "RECREATE_SPLIT";
1035             case EXIT_REASON_DESKTOP_MODE:
1036                 return "DESKTOP_MODE";
1037             case EXIT_REASON_FULLSCREEN_REQUEST:
1038                 return "FULLSCREEN_REQUEST";
1039             default:
1040                 return "unknown reason, reason int = " + exitReason;
1041         }
1042     }
1043 
dump(@onNull PrintWriter pw, String prefix)1044     public void dump(@NonNull PrintWriter pw, String prefix) {
1045         pw.println(prefix + TAG);
1046         if (mStageCoordinator != null) {
1047             mStageCoordinator.dump(pw, prefix);
1048         }
1049     }
1050 
1051     /**
1052      * The interface for calls from outside the Shell, within the host process.
1053      */
1054     @ExternalThread
1055     private class SplitScreenImpl implements SplitScreen {
1056         private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
1057         private final SplitScreen.SplitScreenListener mListener = new SplitScreenListener() {
1058             @Override
1059             public void onStagePositionChanged(int stage, int position) {
1060                 for (int i = 0; i < mExecutors.size(); i++) {
1061                     final int index = i;
1062                     mExecutors.valueAt(index).execute(() -> {
1063                         mExecutors.keyAt(index).onStagePositionChanged(stage, position);
1064                     });
1065                 }
1066             }
1067 
1068             @Override
1069             public void onTaskStageChanged(int taskId, int stage, boolean visible) {
1070                 for (int i = 0; i < mExecutors.size(); i++) {
1071                     final int index = i;
1072                     mExecutors.valueAt(index).execute(() -> {
1073                         mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible);
1074                     });
1075                 }
1076             }
1077 
1078             @Override
1079             public void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) {
1080                 for (int i = 0; i < mExecutors.size(); i++) {
1081                     final int index = i;
1082                     mExecutors.valueAt(index).execute(() -> {
1083                         mExecutors.keyAt(index).onSplitBoundsChanged(rootBounds, mainBounds,
1084                                 sideBounds);
1085                     });
1086                 }
1087             }
1088 
1089             @Override
1090             public void onSplitVisibilityChanged(boolean visible) {
1091                 for (int i = 0; i < mExecutors.size(); i++) {
1092                     final int index = i;
1093                     mExecutors.valueAt(index).execute(() -> {
1094                         mExecutors.keyAt(index).onSplitVisibilityChanged(visible);
1095                     });
1096                 }
1097             }
1098         };
1099 
1100         @Override
startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, int splitPosition, int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)1101         public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
1102                 @Nullable Bundle options2, int splitPosition, int snapPosition,
1103                 @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
1104             mMainExecutor.execute(() -> SplitScreenController.this.startTasks(
1105                     taskId1, options1, taskId2, options2, splitPosition, snapPosition,
1106                     remoteTransition, instanceId));
1107         }
1108 
1109         @Override
registerSplitScreenListener(SplitScreenListener listener, Executor executor)1110         public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
1111             if (mExecutors.containsKey(listener)) return;
1112 
1113             mMainExecutor.execute(() -> {
1114                 if (mExecutors.size() == 0) {
1115                     SplitScreenController.this.registerSplitScreenListener(mListener);
1116                 }
1117 
1118                 mExecutors.put(listener, executor);
1119             });
1120 
1121             executor.execute(() -> {
1122                 mStageCoordinator.sendStatusToListener(listener);
1123             });
1124         }
1125 
1126         @Override
unregisterSplitScreenListener(SplitScreenListener listener)1127         public void unregisterSplitScreenListener(SplitScreenListener listener) {
1128             mMainExecutor.execute(() -> {
1129                 mExecutors.remove(listener);
1130 
1131                 if (mExecutors.size() == 0) {
1132                     SplitScreenController.this.unregisterSplitScreenListener(mListener);
1133                 }
1134             });
1135         }
1136 
1137         @Override
registerSplitAnimationListener(@onNull SplitInvocationListener listener, @NonNull Executor executor)1138         public void registerSplitAnimationListener(@NonNull SplitInvocationListener listener,
1139                 @NonNull Executor executor) {
1140             mStageCoordinator.registerSplitAnimationListener(listener, executor);
1141         }
1142 
1143         @Override
onStartedGoingToSleep()1144         public void onStartedGoingToSleep() {
1145             mMainExecutor.execute(SplitScreenController.this::onStartedGoingToSleep);
1146         }
1147 
1148         @Override
onStartedWakingUp()1149         public void onStartedWakingUp() {
1150             mMainExecutor.execute(SplitScreenController.this::onStartedWakingUp);
1151         }
1152 
1153         @Override
goToFullscreenFromSplit()1154         public void goToFullscreenFromSplit() {
1155             mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit);
1156         }
1157 
1158         @Override
setSplitscreenFocus(boolean leftOrTop)1159         public void setSplitscreenFocus(boolean leftOrTop) {
1160             mMainExecutor.execute(
1161                     () -> SplitScreenController.this.setSplitscreenFocus(leftOrTop));
1162         }
1163     }
1164 
1165     /**
1166      * The interface for calls from outside the host process.
1167      */
1168     @BinderThread
1169     private static class ISplitScreenImpl extends ISplitScreen.Stub
1170             implements ExternalInterfaceBinder {
1171         private SplitScreenController mController;
1172         private final SingleInstanceRemoteListener<SplitScreenController,
1173                 ISplitScreenListener> mListener;
1174         private final SingleInstanceRemoteListener<SplitScreenController,
1175                 ISplitSelectListener> mSelectListener;
1176         private final SplitScreen.SplitScreenListener mSplitScreenListener =
1177                 new SplitScreen.SplitScreenListener() {
1178                     @Override
1179                     public void onStagePositionChanged(int stage, int position) {
1180                         mListener.call(l -> l.onStagePositionChanged(stage, position));
1181                     }
1182 
1183                     @Override
1184                     public void onTaskStageChanged(int taskId, int stage, boolean visible) {
1185                         mListener.call(l -> l.onTaskStageChanged(taskId, stage, visible));
1186                     }
1187                 };
1188 
1189         private final SplitScreen.SplitSelectListener mSplitSelectListener =
1190                 new SplitScreen.SplitSelectListener() {
1191                     @Override
1192                     public boolean onRequestEnterSplitSelect(
1193                             ActivityManager.RunningTaskInfo taskInfo, int splitPosition,
1194                             Rect taskBounds) {
1195                         AtomicBoolean result = new AtomicBoolean(false);
1196                         mSelectListener.call(l -> result.set(l.onRequestSplitSelect(taskInfo,
1197                                 splitPosition, taskBounds)));
1198                         return result.get();
1199                     }
1200                 };
1201 
ISplitScreenImpl(SplitScreenController controller)1202         public ISplitScreenImpl(SplitScreenController controller) {
1203             mController = controller;
1204             mListener = new SingleInstanceRemoteListener<>(controller,
1205                     c -> c.registerSplitScreenListener(mSplitScreenListener),
1206                     c -> c.unregisterSplitScreenListener(mSplitScreenListener));
1207             mSelectListener = new SingleInstanceRemoteListener<>(controller,
1208                     c -> c.registerSplitSelectListener(mSplitSelectListener),
1209                     c -> c.unregisterSplitSelectListener(mSplitSelectListener));
1210         }
1211 
1212         /**
1213          * Invalidates this instance, preventing future calls from updating the controller.
1214          */
1215         @Override
invalidate()1216         public void invalidate() {
1217             mController = null;
1218             // Unregister the listeners to ensure any binder death recipients are unlinked
1219             mListener.unregister();
1220             mSelectListener.unregister();
1221         }
1222 
1223         @Override
registerSplitScreenListener(ISplitScreenListener listener)1224         public void registerSplitScreenListener(ISplitScreenListener listener) {
1225             executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
1226                     (controller) -> mListener.register(listener));
1227         }
1228 
1229         @Override
unregisterSplitScreenListener(ISplitScreenListener listener)1230         public void unregisterSplitScreenListener(ISplitScreenListener listener) {
1231             executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
1232                     (controller) -> mListener.unregister());
1233         }
1234 
1235         @Override
registerSplitSelectListener(ISplitSelectListener listener)1236         public void registerSplitSelectListener(ISplitSelectListener listener) {
1237             executeRemoteCallWithTaskPermission(mController, "registerSplitSelectListener",
1238                     (controller) -> mSelectListener.register(listener));
1239         }
1240 
1241         @Override
unregisterSplitSelectListener(ISplitSelectListener listener)1242         public void unregisterSplitSelectListener(ISplitSelectListener listener) {
1243             executeRemoteCallWithTaskPermission(mController, "unregisterSplitSelectListener",
1244                     (controller) -> mSelectListener.unregister());
1245         }
1246 
1247         @Override
exitSplitScreen(int toTopTaskId)1248         public void exitSplitScreen(int toTopTaskId) {
1249             executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
1250                     (controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
1251         }
1252 
1253         @Override
exitSplitScreenOnHide(boolean exitSplitScreenOnHide)1254         public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
1255             executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
1256                     (controller) -> controller.exitSplitScreenOnHide(exitSplitScreenOnHide));
1257         }
1258 
1259         @Override
startTask(int taskId, int position, @Nullable Bundle options)1260         public void startTask(int taskId, int position, @Nullable Bundle options) {
1261             executeRemoteCallWithTaskPermission(mController, "startTask",
1262                     (controller) -> controller.startTask(taskId, position, options,
1263                             null /* hideTaskToken */));
1264         }
1265 
1266         @Override
startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)1267         public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
1268                 @Nullable Bundle options2, @SplitPosition int splitPosition,
1269                 @PersistentSnapPosition int snapPosition,
1270                 @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
1271             executeRemoteCallWithTaskPermission(mController, "startTasks",
1272                     (controller) -> controller.mStageCoordinator.startTasks(taskId1, options1,
1273                             taskId2, options2, splitPosition, snapPosition, remoteTransition,
1274                             instanceId));
1275         }
1276 
1277         @Override
startIntentAndTask(PendingIntent pendingIntent, int userId1, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)1278         public void startIntentAndTask(PendingIntent pendingIntent, int userId1,
1279                 @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
1280                 @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
1281                 @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
1282             executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
1283                     (controller) -> controller.startIntentAndTask(pendingIntent, userId1, options1,
1284                             taskId, options2, splitPosition, snapPosition, remoteTransition,
1285                             instanceId));
1286         }
1287 
1288         @Override
startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)1289         public void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
1290                 int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
1291                 @PersistentSnapPosition int snapPosition,
1292                 @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
1293             executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask",
1294                     (controller) -> controller.startShortcutAndTask(shortcutInfo, options1, taskId,
1295                             options2, splitPosition, snapPosition, remoteTransition, instanceId));
1296         }
1297 
1298         @Override
startIntents(PendingIntent pendingIntent1, int userId1, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)1299         public void startIntents(PendingIntent pendingIntent1, int userId1,
1300                 @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
1301                 PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
1302                 @Nullable Bundle options2, @SplitPosition int splitPosition,
1303                 @PersistentSnapPosition int snapPosition,
1304                 @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
1305             executeRemoteCallWithTaskPermission(mController, "startIntents",
1306                     (controller) ->
1307                             controller.startIntents(pendingIntent1, userId1, shortcutInfo1,
1308                                     options1, pendingIntent2, userId2, shortcutInfo2, options2,
1309                                     splitPosition, snapPosition, remoteTransition, instanceId)
1310             );
1311         }
1312 
1313         @Override
startShortcut(String packageName, String shortcutId, int position, @Nullable Bundle options, UserHandle user, InstanceId instanceId)1314         public void startShortcut(String packageName, String shortcutId, int position,
1315                 @Nullable Bundle options, UserHandle user, InstanceId instanceId) {
1316             executeRemoteCallWithTaskPermission(mController, "startShortcut",
1317                     (controller) -> controller.startShortcut(packageName, shortcutId, position,
1318                             options, user, instanceId));
1319         }
1320 
1321         @Override
startIntent(PendingIntent intent, int userId, Intent fillInIntent, int position, @Nullable Bundle options, InstanceId instanceId)1322         public void startIntent(PendingIntent intent, int userId, Intent fillInIntent, int position,
1323                 @Nullable Bundle options, InstanceId instanceId) {
1324             executeRemoteCallWithTaskPermission(mController, "startIntent",
1325                     (controller) -> controller.startIntentWithInstanceId(intent, userId,
1326                             fillInIntent, position, options, instanceId));
1327         }
1328 
1329         @Override
switchSplitPosition()1330         public void switchSplitPosition() {
1331             executeRemoteCallWithTaskPermission(mController, "switchSplitPosition",
1332                     (controller) -> controller.switchSplitPosition("remoteCall"));
1333         }
1334     }
1335 }
1336