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