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