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