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