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.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; 20 import static android.app.ActivityTaskManager.INVALID_TASK_ID; 21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 23 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 24 import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; 25 import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; 26 import static android.view.Display.DEFAULT_DISPLAY; 27 import static android.view.RemoteAnimationTarget.MODE_OPENING; 28 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; 29 import static android.view.WindowManager.TRANSIT_CHANGE; 30 import static android.view.WindowManager.TRANSIT_CLOSE; 31 import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; 32 import static android.view.WindowManager.TRANSIT_TO_BACK; 33 import static android.view.WindowManager.TRANSIT_TO_FRONT; 34 import static android.view.WindowManager.transitTypeToString; 35 import static android.window.TransitionInfo.FLAG_IS_DISPLAY; 36 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; 37 38 import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; 39 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; 40 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; 41 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; 42 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; 43 import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString; 44 import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor; 45 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; 46 import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; 47 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; 48 import static com.android.wm.shell.shared.TransitionUtil.isClosingType; 49 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; 50 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; 51 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; 52 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; 53 import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER; 54 import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE; 55 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW; 56 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED; 57 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; 58 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE; 59 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED; 60 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; 61 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_REQUEST; 62 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT; 63 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT; 64 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME; 65 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ROOT_TASK_VANISHED; 66 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP; 67 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN; 68 import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; 69 import static com.android.wm.shell.transition.MixedTransitionHelper.getPipReplacingChange; 70 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; 71 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE; 72 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN; 73 74 import android.animation.Animator; 75 import android.animation.AnimatorListenerAdapter; 76 import android.animation.ValueAnimator; 77 import android.annotation.CallSuper; 78 import android.annotation.NonNull; 79 import android.annotation.Nullable; 80 import android.app.ActivityManager; 81 import android.app.ActivityOptions; 82 import android.app.IActivityTaskManager; 83 import android.app.PendingIntent; 84 import android.app.TaskInfo; 85 import android.app.WindowConfiguration; 86 import android.content.ActivityNotFoundException; 87 import android.content.Context; 88 import android.content.Intent; 89 import android.content.pm.LauncherApps; 90 import android.content.pm.ShortcutInfo; 91 import android.graphics.Rect; 92 import android.hardware.devicestate.DeviceStateManager; 93 import android.os.Bundle; 94 import android.os.Debug; 95 import android.os.IBinder; 96 import android.os.RemoteException; 97 import android.os.ServiceManager; 98 import android.os.UserHandle; 99 import android.util.ArrayMap; 100 import android.util.ArraySet; 101 import android.util.IntArray; 102 import android.util.Log; 103 import android.util.Slog; 104 import android.view.Choreographer; 105 import android.view.IRemoteAnimationFinishedCallback; 106 import android.view.IRemoteAnimationRunner; 107 import android.view.RemoteAnimationAdapter; 108 import android.view.RemoteAnimationTarget; 109 import android.view.SurfaceControl; 110 import android.view.SurfaceSession; 111 import android.view.WindowManager; 112 import android.widget.Toast; 113 import android.window.DisplayAreaInfo; 114 import android.window.RemoteTransition; 115 import android.window.TransitionInfo; 116 import android.window.TransitionRequestInfo; 117 import android.window.WindowContainerToken; 118 import android.window.WindowContainerTransaction; 119 120 import com.android.internal.annotations.VisibleForTesting; 121 import com.android.internal.logging.InstanceId; 122 import com.android.internal.protolog.common.ProtoLog; 123 import com.android.internal.util.ArrayUtils; 124 import com.android.launcher3.icons.IconProvider; 125 import com.android.wm.shell.R; 126 import com.android.wm.shell.ShellTaskOrganizer; 127 import com.android.wm.shell.common.DisplayController; 128 import com.android.wm.shell.common.DisplayImeController; 129 import com.android.wm.shell.common.DisplayInsetsController; 130 import com.android.wm.shell.common.LaunchAdjacentController; 131 import com.android.wm.shell.common.ScreenshotUtils; 132 import com.android.wm.shell.common.ShellExecutor; 133 import com.android.wm.shell.common.SyncTransactionQueue; 134 import com.android.wm.shell.common.TransactionPool; 135 import com.android.wm.shell.common.split.SplitLayout; 136 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; 137 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; 138 import com.android.wm.shell.common.split.SplitScreenUtils; 139 import com.android.wm.shell.common.split.SplitWindowManager; 140 import com.android.wm.shell.protolog.ShellProtoLogGroup; 141 import com.android.wm.shell.recents.RecentTasksController; 142 import com.android.wm.shell.shared.TransitionUtil; 143 import com.android.wm.shell.splitscreen.SplitScreen.StageType; 144 import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason; 145 import com.android.wm.shell.splitscreen.SplitScreenController.SplitEnterReason; 146 import com.android.wm.shell.transition.DefaultMixedHandler; 147 import com.android.wm.shell.transition.LegacyTransitions; 148 import com.android.wm.shell.transition.Transitions; 149 import com.android.wm.shell.util.SplitBounds; 150 import com.android.wm.shell.windowdecor.WindowDecorViewModel; 151 152 import dalvik.annotation.optimization.NeverCompile; 153 154 import java.io.PrintWriter; 155 import java.util.ArrayList; 156 import java.util.HashSet; 157 import java.util.List; 158 import java.util.Optional; 159 import java.util.Set; 160 import java.util.concurrent.Executor; 161 162 /** 163 * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and 164 * {@link SideStage} stages. 165 * Some high-level rules: 166 * - The {@link StageCoordinator} is only considered active if the {@link SideStage} contains at 167 * least one child task. 168 * - The {@link MainStage} should only have children if the coordinator is active. 169 * - The {@link SplitLayout} divider is only visible if both the {@link MainStage} 170 * and {@link SideStage} are visible. 171 * - Both stages are put under a single-top root task. 172 * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and 173 * {@link #onStageHasChildrenChanged(StageListenerImpl).} 174 */ 175 public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 176 DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler, 177 ShellTaskOrganizer.TaskListener { 178 179 private static final String TAG = StageCoordinator.class.getSimpleName(); 180 181 private final SurfaceSession mSurfaceSession = new SurfaceSession(); 182 183 private final MainStage mMainStage; 184 private final StageListenerImpl mMainStageListener = new StageListenerImpl(); 185 private final SideStage mSideStage; 186 private final StageListenerImpl mSideStageListener = new StageListenerImpl(); 187 @SplitPosition 188 private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT; 189 190 private final int mDisplayId; 191 private SplitLayout mSplitLayout; 192 private ValueAnimator mDividerFadeInAnimator; 193 private boolean mDividerVisible; 194 private boolean mKeyguardShowing; 195 private boolean mShowDecorImmediately; 196 private final SyncTransactionQueue mSyncQueue; 197 private final ShellTaskOrganizer mTaskOrganizer; 198 private final Context mContext; 199 private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>(); 200 private final Set<SplitScreen.SplitSelectListener> mSelectListeners = new HashSet<>(); 201 private final DisplayController mDisplayController; 202 private final DisplayImeController mDisplayImeController; 203 private final DisplayInsetsController mDisplayInsetsController; 204 private final TransactionPool mTransactionPool; 205 private SplitScreenTransitions mSplitTransitions; 206 private final SplitscreenEventLogger mLogger; 207 private final ShellExecutor mMainExecutor; 208 // Cache live tile tasks while entering recents, evict them from stages in finish transaction 209 // if user is opening another task(s). 210 private final ArrayList<Integer> mPausingTasks = new ArrayList<>(); 211 private final Optional<RecentTasksController> mRecentTasks; 212 private final LaunchAdjacentController mLaunchAdjacentController; 213 private final Optional<WindowDecorViewModel> mWindowDecorViewModel; 214 215 private final Rect mTempRect1 = new Rect(); 216 private final Rect mTempRect2 = new Rect(); 217 218 /** 219 * A single-top root task which the split divider attached to. 220 */ 221 @VisibleForTesting 222 ActivityManager.RunningTaskInfo mRootTaskInfo; 223 224 private SurfaceControl mRootTaskLeash; 225 226 // Tracks whether we should update the recent tasks. Only allow this to happen in between enter 227 // and exit, since exit itself can trigger a number of changes that update the stages. 228 private boolean mShouldUpdateRecents; 229 private boolean mExitSplitScreenOnHide; 230 private boolean mIsDividerRemoteAnimating; 231 private boolean mIsDropEntering; 232 private boolean mSkipEvictingMainStageChildren; 233 private boolean mIsExiting; 234 private boolean mIsRootTranslucent; 235 @VisibleForTesting 236 int mTopStageAfterFoldDismiss; 237 238 private DefaultMixedHandler mMixedHandler; 239 private final Toast mSplitUnsupportedToast; 240 private SplitRequest mSplitRequest; 241 /** Used to notify others of when shell is animating into split screen */ 242 private SplitScreen.SplitInvocationListener mSplitInvocationListener; 243 private Executor mSplitInvocationListenerExecutor; 244 245 /** 246 * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support 247 * CompatUI layouts. CompatUI is handled separately by MainStage and SideStage. 248 */ 249 @Override supportCompatUI()250 public boolean supportCompatUI() { 251 return false; 252 } 253 254 /** NOTE: Will overwrite any previously set {@link #mSplitInvocationListener} */ registerSplitAnimationListener( @onNull SplitScreen.SplitInvocationListener listener, @NonNull Executor executor)255 public void registerSplitAnimationListener( 256 @NonNull SplitScreen.SplitInvocationListener listener, @NonNull Executor executor) { 257 mSplitInvocationListener = listener; 258 mSplitInvocationListenerExecutor = executor; 259 mSplitTransitions.registerSplitAnimListener(listener, executor); 260 } 261 262 class SplitRequest { 263 @SplitPosition 264 int mActivatePosition; 265 int mActivateTaskId; 266 int mActivateTaskId2; 267 Intent mStartIntent; 268 Intent mStartIntent2; 269 SplitRequest(int taskId, Intent startIntent, int position)270 SplitRequest(int taskId, Intent startIntent, int position) { 271 mActivateTaskId = taskId; 272 mStartIntent = startIntent; 273 mActivatePosition = position; 274 } SplitRequest(Intent startIntent, int position)275 SplitRequest(Intent startIntent, int position) { 276 mStartIntent = startIntent; 277 mActivatePosition = position; 278 } SplitRequest(Intent startIntent, Intent startIntent2, int position)279 SplitRequest(Intent startIntent, Intent startIntent2, int position) { 280 mStartIntent = startIntent; 281 mStartIntent2 = startIntent2; 282 mActivatePosition = position; 283 } SplitRequest(int taskId1, int position)284 SplitRequest(int taskId1, int position) { 285 mActivateTaskId = taskId1; 286 mActivatePosition = position; 287 } SplitRequest(int taskId1, int taskId2, int position)288 SplitRequest(int taskId1, int taskId2, int position) { 289 mActivateTaskId = taskId1; 290 mActivateTaskId2 = taskId2; 291 mActivatePosition = position; 292 } 293 } 294 295 private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = 296 new SplitWindowManager.ParentContainerCallbacks() { 297 @Override 298 public void attachToParentSurface(SurfaceControl.Builder b) { 299 b.setParent(mRootTaskLeash); 300 } 301 302 @Override 303 public void onLeashReady(SurfaceControl leash) { 304 // This is for avoiding divider invisible due to delay of creating so only need 305 // to do when divider should visible case. 306 if (mDividerVisible) { 307 mSyncQueue.runInSync(t -> applyDividerVisibility(t)); 308 } 309 } 310 }; 311 StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, ShellTaskOrganizer taskOrganizer, DisplayController displayController, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider, ShellExecutor mainExecutor, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, Optional<WindowDecorViewModel> windowDecorViewModel)312 protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, 313 ShellTaskOrganizer taskOrganizer, DisplayController displayController, 314 DisplayImeController displayImeController, 315 DisplayInsetsController displayInsetsController, Transitions transitions, 316 TransactionPool transactionPool, 317 IconProvider iconProvider, ShellExecutor mainExecutor, 318 Optional<RecentTasksController> recentTasks, 319 LaunchAdjacentController launchAdjacentController, 320 Optional<WindowDecorViewModel> windowDecorViewModel) { 321 mContext = context; 322 mDisplayId = displayId; 323 mSyncQueue = syncQueue; 324 mTaskOrganizer = taskOrganizer; 325 mLogger = new SplitscreenEventLogger(); 326 mMainExecutor = mainExecutor; 327 mRecentTasks = recentTasks; 328 mLaunchAdjacentController = launchAdjacentController; 329 mWindowDecorViewModel = windowDecorViewModel; 330 331 taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */); 332 333 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Creating main/side root task"); 334 mMainStage = new MainStage( 335 mContext, 336 mTaskOrganizer, 337 mDisplayId, 338 mMainStageListener, 339 mSyncQueue, 340 mSurfaceSession, 341 iconProvider, 342 mWindowDecorViewModel); 343 mSideStage = new SideStage( 344 mContext, 345 mTaskOrganizer, 346 mDisplayId, 347 mSideStageListener, 348 mSyncQueue, 349 mSurfaceSession, 350 iconProvider, 351 mWindowDecorViewModel); 352 mDisplayController = displayController; 353 mDisplayImeController = displayImeController; 354 mDisplayInsetsController = displayInsetsController; 355 mTransactionPool = transactionPool; 356 final DeviceStateManager deviceStateManager = 357 mContext.getSystemService(DeviceStateManager.class); 358 deviceStateManager.registerCallback(taskOrganizer.getExecutor(), 359 new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged)); 360 mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, 361 this::onTransitionAnimationComplete, this); 362 mDisplayController.addDisplayWindowListener(this); 363 transitions.addHandler(this); 364 mSplitUnsupportedToast = Toast.makeText(mContext, 365 R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT); 366 // With shell transition, we should update recents tile each callback so set this to true by 367 // default. 368 mShouldUpdateRecents = ENABLE_SHELL_TRANSITIONS; 369 } 370 371 @VisibleForTesting StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, ShellTaskOrganizer taskOrganizer, MainStage mainStage, SideStage sideStage, DisplayController displayController, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, Optional<WindowDecorViewModel> windowDecorViewModel)372 StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, 373 ShellTaskOrganizer taskOrganizer, MainStage mainStage, SideStage sideStage, 374 DisplayController displayController, DisplayImeController displayImeController, 375 DisplayInsetsController displayInsetsController, SplitLayout splitLayout, 376 Transitions transitions, TransactionPool transactionPool, 377 ShellExecutor mainExecutor, 378 Optional<RecentTasksController> recentTasks, 379 LaunchAdjacentController launchAdjacentController, 380 Optional<WindowDecorViewModel> windowDecorViewModel) { 381 mContext = context; 382 mDisplayId = displayId; 383 mSyncQueue = syncQueue; 384 mTaskOrganizer = taskOrganizer; 385 mMainStage = mainStage; 386 mSideStage = sideStage; 387 mDisplayController = displayController; 388 mDisplayImeController = displayImeController; 389 mDisplayInsetsController = displayInsetsController; 390 mTransactionPool = transactionPool; 391 mSplitLayout = splitLayout; 392 mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, 393 this::onTransitionAnimationComplete, this); 394 mLogger = new SplitscreenEventLogger(); 395 mMainExecutor = mainExecutor; 396 mRecentTasks = recentTasks; 397 mLaunchAdjacentController = launchAdjacentController; 398 mWindowDecorViewModel = windowDecorViewModel; 399 mDisplayController.addDisplayWindowListener(this); 400 transitions.addHandler(this); 401 mSplitUnsupportedToast = Toast.makeText(mContext, 402 R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT); 403 } 404 setMixedHandler(DefaultMixedHandler mixedHandler)405 public void setMixedHandler(DefaultMixedHandler mixedHandler) { 406 mMixedHandler = mixedHandler; 407 } 408 409 @VisibleForTesting getSplitTransitions()410 SplitScreenTransitions getSplitTransitions() { 411 return mSplitTransitions; 412 } 413 414 @VisibleForTesting setSplitTransitions(SplitScreenTransitions splitScreenTransitions)415 void setSplitTransitions(SplitScreenTransitions splitScreenTransitions) { 416 mSplitTransitions = splitScreenTransitions; 417 } 418 isSplitScreenVisible()419 public boolean isSplitScreenVisible() { 420 return mSideStageListener.mVisible && mMainStageListener.mVisible; 421 } 422 isSplitActive()423 public boolean isSplitActive() { 424 return mMainStage.isActive(); 425 } 426 427 /** @return whether this transition-request has the launch-adjacent flag. */ requestHasLaunchAdjacentFlag(TransitionRequestInfo request)428 public boolean requestHasLaunchAdjacentFlag(TransitionRequestInfo request) { 429 final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); 430 return triggerTask != null && triggerTask.baseIntent != null 431 && (triggerTask.baseIntent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0; 432 } 433 434 /** @return whether the transition-request implies entering pip from split. */ requestImpliesSplitToPip(TransitionRequestInfo request)435 public boolean requestImpliesSplitToPip(TransitionRequestInfo request) { 436 if (!isSplitActive() || !mMixedHandler.requestHasPipEnter(request)) { 437 return false; 438 } 439 440 if (request.getTriggerTask() != null && getSplitPosition( 441 request.getTriggerTask().taskId) != SPLIT_POSITION_UNDEFINED) { 442 return true; 443 } 444 445 // If one of the splitting tasks support auto-pip, wm-core might reparent the task to TDA 446 // and file a TRANSIT_PIP transition when finishing transitions. 447 // @see com.android.server.wm.RootWindowContainer#moveActivityToPinnedRootTask 448 if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { 449 return true; 450 } 451 452 return false; 453 } 454 455 /** Checks if `transition` is a pending enter-split transition. */ isPendingEnter(IBinder transition)456 public boolean isPendingEnter(IBinder transition) { 457 return mSplitTransitions.isPendingEnter(transition); 458 } 459 460 @StageType getStageOfTask(int taskId)461 int getStageOfTask(int taskId) { 462 if (mMainStage.containsTask(taskId)) { 463 return STAGE_TYPE_MAIN; 464 } else if (mSideStage.containsTask(taskId)) { 465 return STAGE_TYPE_SIDE; 466 } 467 468 return STAGE_TYPE_UNDEFINED; 469 } 470 isRootOrStageRoot(int taskId)471 boolean isRootOrStageRoot(int taskId) { 472 if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskId) { 473 return true; 474 } 475 return mMainStage.isRootTaskId(taskId) || mSideStage.isRootTaskId(taskId); 476 } 477 moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition, WindowContainerTransaction wct)478 boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition, 479 WindowContainerTransaction wct) { 480 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "moveToStage: task=%d position=%d", task.taskId, 481 stagePosition); 482 prepareEnterSplitScreen(wct, task, stagePosition, false /* resizeAnim */); 483 if (ENABLE_SHELL_TRANSITIONS) { 484 mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, 485 null, this, 486 isSplitScreenVisible() 487 ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN, 488 !mIsDropEntering); 489 } else { 490 mSyncQueue.queue(wct); 491 mSyncQueue.runInSync(t -> { 492 updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); 493 }); 494 } 495 // Due to drag already pip task entering split by this method so need to reset flag here. 496 mIsDropEntering = false; 497 mSkipEvictingMainStageChildren = false; 498 return true; 499 } 500 removeFromSideStage(int taskId)501 boolean removeFromSideStage(int taskId) { 502 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "removeFromSideStage: task=%d", taskId); 503 final WindowContainerTransaction wct = new WindowContainerTransaction(); 504 505 /** 506 * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the 507 * {@link SideStage} no longer has children. 508 */ 509 final boolean result = mSideStage.removeTask(taskId, 510 mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null, 511 wct); 512 mTaskOrganizer.applyTransaction(wct); 513 return result; 514 } 515 getLogger()516 SplitscreenEventLogger getLogger() { 517 return mLogger; 518 } 519 requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, WindowContainerTransaction wct, int splitPosition, Rect taskBounds)520 void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, 521 WindowContainerTransaction wct, int splitPosition, Rect taskBounds) { 522 boolean enteredSplitSelect = false; 523 for (SplitScreen.SplitSelectListener listener : mSelectListeners) { 524 enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo, splitPosition, 525 taskBounds); 526 } 527 if (enteredSplitSelect) { 528 mTaskOrganizer.applyTransaction(wct); 529 } 530 } 531 startShortcut(String packageName, String shortcutId, @SplitPosition int position, Bundle options, UserHandle user)532 void startShortcut(String packageName, String shortcutId, @SplitPosition int position, 533 Bundle options, UserHandle user) { 534 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startShortcut: pkg=%s id=%s position=%d user=%d", 535 packageName, shortcutId, position, user.getIdentifier()); 536 final boolean isEnteringSplit = !isSplitActive(); 537 538 IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { 539 @Override 540 public void onAnimationStart(@WindowManager.TransitionOldType int transit, 541 RemoteAnimationTarget[] apps, 542 RemoteAnimationTarget[] wallpapers, 543 RemoteAnimationTarget[] nonApps, 544 final IRemoteAnimationFinishedCallback finishedCallback) { 545 if (isEnteringSplit && mSideStage.getChildCount() == 0) { 546 mMainExecutor.execute(() -> exitSplitScreen( 547 null /* childrenToTop */, EXIT_REASON_UNKNOWN)); 548 Log.w(TAG, splitFailureMessage("startShortcut", 549 "side stage was not populated")); 550 handleUnsupportedSplitStart(); 551 } 552 553 if (finishedCallback != null) { 554 try { 555 finishedCallback.onAnimationFinished(); 556 } catch (RemoteException e) { 557 Slog.e(TAG, "Error finishing legacy transition: ", e); 558 } 559 } 560 561 if (!isEnteringSplit && apps != null) { 562 final WindowContainerTransaction evictWct = new WindowContainerTransaction(); 563 prepareEvictNonOpeningChildTasks(position, apps, evictWct); 564 mSyncQueue.queue(evictWct); 565 } 566 } 567 @Override 568 public void onAnimationCancelled() { 569 if (isEnteringSplit) { 570 mMainExecutor.execute(() -> exitSplitScreen( 571 mSideStage.getChildCount() == 0 ? mMainStage : mSideStage, 572 EXIT_REASON_UNKNOWN)); 573 } 574 } 575 }; 576 options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, 577 null /* wct */); 578 RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper, 579 0 /* duration */, 0 /* statusBarTransitionDelay */); 580 ActivityOptions activityOptions = ActivityOptions.fromBundle(options); 581 // Flag this as a no-user-action launch to prevent sending user leaving event to the current 582 // top activity since it's going to be put into another side of the split. This prevents the 583 // current top activity from going into pip mode due to user leaving event. 584 activityOptions.setApplyNoUserActionFlagForShortcut(true); 585 activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); 586 try { 587 LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class); 588 launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */, 589 activityOptions.toBundle(), user); 590 } catch (ActivityNotFoundException e) { 591 Slog.e(TAG, "Failed to launch shortcut", e); 592 } 593 } 594 595 /** Use this method to launch an existing Task via a taskId */ startTask(int taskId, @SplitPosition int position, @Nullable Bundle options)596 void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) { 597 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: task=%d position=%d", taskId, position); 598 mSplitRequest = new SplitRequest(taskId, position); 599 final WindowContainerTransaction wct = new WindowContainerTransaction(); 600 options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); 601 wct.startTask(taskId, options); 602 // If this should be mixed, send the task to avoid split handle transition directly. 603 if (mMixedHandler != null && mMixedHandler.isTaskInPip(taskId, mTaskOrganizer)) { 604 mTaskOrganizer.applyTransaction(wct); 605 return; 606 } 607 608 // Don't evict the main stage children as this can race and happen after the activity is 609 // started into that stage 610 if (!isSplitScreenVisible()) { 611 mSkipEvictingMainStageChildren = true; 612 // Starting the split task without evicting children will bring the single root task 613 // container forward, so ensure that we hide the divider before we start animate it 614 setDividerVisibility(false, null); 615 } 616 617 // If split screen is not activated, we're expecting to open a pair of apps to split. 618 final int extraTransitType = mMainStage.isActive() 619 ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN; 620 prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering); 621 622 mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this, 623 extraTransitType, !mIsDropEntering); 624 } 625 626 /** Launches an activity into split. */ startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options)627 void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, 628 @Nullable Bundle options) { 629 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntent: intent=%s position=%d", intent.getIntent(), 630 position); 631 mSplitRequest = new SplitRequest(intent.getIntent(), position); 632 if (!ENABLE_SHELL_TRANSITIONS) { 633 startIntentLegacy(intent, fillInIntent, position, options); 634 return; 635 } 636 637 final WindowContainerTransaction wct = new WindowContainerTransaction(); 638 options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); 639 wct.sendPendingIntent(intent, fillInIntent, options); 640 641 // If this should be mixed, just send the intent to avoid split handle transition directly. 642 if (mMixedHandler != null && mMixedHandler.isIntentInPip(intent)) { 643 mTaskOrganizer.applyTransaction(wct); 644 return; 645 } 646 647 // Don't evict the main stage children as this can race and happen after the activity is 648 // started into that stage 649 if (!isSplitScreenVisible()) { 650 mSkipEvictingMainStageChildren = true; 651 // Starting the split task without evicting children will bring the single root task 652 // container forward, so ensure that we hide the divider before we start animate it 653 setDividerVisibility(false, null); 654 } 655 656 // If split screen is not activated, we're expecting to open a pair of apps to split. 657 final int extraTransitType = mMainStage.isActive() 658 ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN; 659 prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering); 660 661 mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this, 662 extraTransitType, !mIsDropEntering); 663 } 664 665 /** Launches an activity into split by legacy transition. */ startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options)666 void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, 667 @Nullable Bundle options) { 668 final boolean isEnteringSplit = !isSplitActive(); 669 670 LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() { 671 @Override 672 public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, 673 RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, 674 IRemoteAnimationFinishedCallback finishedCallback, 675 SurfaceControl.Transaction t) { 676 if (isEnteringSplit && mSideStage.getChildCount() == 0) { 677 mMainExecutor.execute(() -> exitSplitScreen( 678 null /* childrenToTop */, EXIT_REASON_UNKNOWN)); 679 Log.w(TAG, splitFailureMessage("startIntentLegacy", 680 "side stage was not populated")); 681 handleUnsupportedSplitStart(); 682 } 683 684 if (apps != null) { 685 for (int i = 0; i < apps.length; ++i) { 686 if (apps[i].mode == MODE_OPENING) { 687 t.show(apps[i].leash); 688 } 689 } 690 } 691 t.apply(); 692 693 if (finishedCallback != null) { 694 try { 695 finishedCallback.onAnimationFinished(); 696 } catch (RemoteException e) { 697 Slog.e(TAG, "Error finishing legacy transition: ", e); 698 } 699 } 700 701 702 if (!isEnteringSplit && apps != null) { 703 final WindowContainerTransaction evictWct = new WindowContainerTransaction(); 704 prepareEvictNonOpeningChildTasks(position, apps, evictWct); 705 mSyncQueue.queue(evictWct); 706 } 707 } 708 }; 709 710 final WindowContainerTransaction wct = new WindowContainerTransaction(); 711 options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct); 712 713 // If split still not active, apply windows bounds first to avoid surface reset to 714 // wrong pos by SurfaceAnimator from wms. 715 if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) { 716 updateWindowBounds(mSplitLayout, wct); 717 } 718 wct.sendPendingIntent(intent, fillInIntent, options); 719 mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); 720 } 721 722 /** Starts 2 tasks in one transition. */ startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)723 void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, 724 @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, 725 @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { 726 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 727 "startTasks: task1=%d task2=%d position=%d snapPosition=%d", 728 taskId1, taskId2, splitPosition, snapPosition); 729 final WindowContainerTransaction wct = new WindowContainerTransaction(); 730 if (taskId2 == INVALID_TASK_ID) { 731 startSingleTask(taskId1, options1, wct, remoteTransition); 732 return; 733 } 734 735 setSideStagePosition(splitPosition, wct); 736 options1 = options1 != null ? options1 : new Bundle(); 737 addActivityOptions(options1, mSideStage); 738 wct.startTask(taskId1, options1); 739 740 startWithTask(wct, taskId2, options2, snapPosition, remoteTransition, instanceId); 741 } 742 743 /** Start an intent and a task to a split pair in one transition. */ startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)744 void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent, 745 @Nullable Bundle options1, int taskId, @Nullable Bundle options2, 746 @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, 747 @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { 748 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 749 "startIntentAndTask: intent=%s task1=%d position=%d snapPosition=%d", 750 pendingIntent.getIntent(), taskId, splitPosition, snapPosition); 751 final WindowContainerTransaction wct = new WindowContainerTransaction(); 752 boolean firstIntentPipped = mMixedHandler.isIntentInPip(pendingIntent); 753 boolean secondTaskPipped = mMixedHandler.isTaskInPip(taskId, mTaskOrganizer); 754 if (taskId == INVALID_TASK_ID || secondTaskPipped) { 755 startSingleIntent(pendingIntent, fillInIntent, options1, wct, remoteTransition); 756 return; 757 } 758 759 if (firstIntentPipped) { 760 startSingleTask(taskId, options2, wct, remoteTransition); 761 return; 762 } 763 764 setSideStagePosition(splitPosition, wct); 765 options1 = options1 != null ? options1 : new Bundle(); 766 addActivityOptions(options1, mSideStage); 767 wct.sendPendingIntent(pendingIntent, fillInIntent, options1); 768 769 startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId); 770 } 771 772 /** 773 * @param taskId Starts this task in fullscreen, removing it from existing pairs if it was part 774 * of one. 775 */ startSingleTask(int taskId, Bundle options, WindowContainerTransaction wct, RemoteTransition remoteTransition)776 private void startSingleTask(int taskId, Bundle options, WindowContainerTransaction wct, 777 RemoteTransition remoteTransition) { 778 if (mMainStage.containsTask(taskId) || mSideStage.containsTask(taskId)) { 779 prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); 780 } 781 if (mRecentTasks.isPresent()) { 782 mRecentTasks.get().removeSplitPair(taskId); 783 } 784 options = options != null ? options : new Bundle(); 785 addActivityOptions(options, null); 786 wct.startTask(taskId, options); 787 mSplitTransitions.startFullscreenTransition(wct, remoteTransition); 788 } 789 790 /** Starts a shortcut and a task to a split pair in one transition. */ startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)791 void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1, 792 int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, 793 @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, 794 InstanceId instanceId) { 795 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 796 "startShortcutAndTask: shortcut=%s task1=%d position=%d snapPosition=%d", 797 shortcutInfo, taskId, splitPosition, snapPosition); 798 final WindowContainerTransaction wct = new WindowContainerTransaction(); 799 if (taskId == INVALID_TASK_ID) { 800 options1 = options1 != null ? options1 : new Bundle(); 801 addActivityOptions(options1, null); 802 wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1); 803 mSplitTransitions.startFullscreenTransition(wct, remoteTransition); 804 return; 805 } 806 807 setSideStagePosition(splitPosition, wct); 808 options1 = options1 != null ? options1 : new Bundle(); 809 addActivityOptions(options1, mSideStage); 810 wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1); 811 812 startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId); 813 } 814 815 /** 816 * Starts with the second task to a split pair in one transition. 817 * 818 * @param wct transaction to start the first task 819 * @param instanceId if {@code null}, will not log. Otherwise it will be used in 820 * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)} 821 */ startWithTask(WindowContainerTransaction wct, int mainTaskId, @Nullable Bundle mainOptions, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)822 private void startWithTask(WindowContainerTransaction wct, int mainTaskId, 823 @Nullable Bundle mainOptions, @PersistentSnapPosition int snapPosition, 824 @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { 825 if (!mMainStage.isActive()) { 826 // Build a request WCT that will launch both apps such that task 0 is on the main stage 827 // while task 1 is on the side stage. 828 mMainStage.activate(wct, false /* reparent */); 829 } 830 mSplitLayout.setDivideRatio(snapPosition); 831 updateWindowBounds(mSplitLayout, wct); 832 wct.reorder(mRootTaskInfo.token, true); 833 wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, 834 false /* reparentLeafTaskIfRelaunch */); 835 setRootForceTranslucent(false, wct); 836 837 // Make sure the launch options will put tasks in the corresponding split roots 838 mainOptions = mainOptions != null ? mainOptions : new Bundle(); 839 addActivityOptions(mainOptions, mMainStage); 840 841 // Add task launch requests 842 wct.startTask(mainTaskId, mainOptions); 843 844 // leave recents animation by re-start pausing tasks 845 if (mPausingTasks.contains(mainTaskId)) { 846 mPausingTasks.clear(); 847 } 848 mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this, 849 TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); 850 setEnterInstanceId(instanceId); 851 } 852 startIntents(PendingIntent pendingIntent1, Intent fillInIntent1, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, @Nullable PendingIntent pendingIntent2, Intent fillInIntent2, @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId)853 void startIntents(PendingIntent pendingIntent1, Intent fillInIntent1, 854 @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, 855 @Nullable PendingIntent pendingIntent2, Intent fillInIntent2, 856 @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, 857 @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, 858 @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { 859 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 860 "startIntents: intent1=%s intent2=%s position=%d snapPosition=%d", 861 pendingIntent1.getIntent(), 862 (pendingIntent2 != null ? pendingIntent2.getIntent() : "null"), 863 splitPosition, snapPosition); 864 final WindowContainerTransaction wct = new WindowContainerTransaction(); 865 if (pendingIntent2 == null) { 866 options1 = options1 != null ? options1 : new Bundle(); 867 addActivityOptions(options1, null); 868 if (shortcutInfo1 != null) { 869 wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1); 870 } else { 871 wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1); 872 } 873 mSplitTransitions.startFullscreenTransition(wct, remoteTransition); 874 return; 875 } 876 877 boolean handledForPipSplitLaunch = handlePippedSplitIntentsLaunch( 878 pendingIntent1, 879 pendingIntent2, 880 options1, 881 options2, 882 shortcutInfo1, 883 shortcutInfo2, 884 wct, 885 fillInIntent1, 886 fillInIntent2, 887 remoteTransition); 888 if (handledForPipSplitLaunch) { 889 return; 890 } 891 892 if (!mMainStage.isActive()) { 893 // Build a request WCT that will launch both apps such that task 0 is on the main stage 894 // while task 1 is on the side stage. 895 mMainStage.activate(wct, false /* reparent */); 896 } 897 898 setSideStagePosition(splitPosition, wct); 899 mSplitLayout.setDivideRatio(snapPosition); 900 updateWindowBounds(mSplitLayout, wct); 901 wct.reorder(mRootTaskInfo.token, true); 902 wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, 903 false /* reparentLeafTaskIfRelaunch */); 904 setRootForceTranslucent(false, wct); 905 906 options1 = options1 != null ? options1 : new Bundle(); 907 addActivityOptions(options1, mSideStage); 908 if (shortcutInfo1 != null) { 909 wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1); 910 } else { 911 wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1); 912 } 913 options2 = options2 != null ? options2 : new Bundle(); 914 addActivityOptions(options2, mMainStage); 915 if (shortcutInfo2 != null) { 916 wct.startShortcut(mContext.getPackageName(), shortcutInfo2, options2); 917 } else { 918 wct.sendPendingIntent(pendingIntent2, fillInIntent2, options2); 919 } 920 921 mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this, 922 TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); 923 setEnterInstanceId(instanceId); 924 } 925 926 /** 927 * Checks if either of the apps in the desired split launch is currently in Pip. If so, it will 928 * launch the non-pipped app as a fullscreen app, otherwise no-op. 929 */ handlePippedSplitIntentsLaunch(PendingIntent pendingIntent1, PendingIntent pendingIntent2, Bundle options1, Bundle options2, ShortcutInfo shortcutInfo1, ShortcutInfo shortcutInfo2, WindowContainerTransaction wct, Intent fillInIntent1, Intent fillInIntent2, RemoteTransition remoteTransition)930 private boolean handlePippedSplitIntentsLaunch(PendingIntent pendingIntent1, 931 PendingIntent pendingIntent2, Bundle options1, Bundle options2, 932 ShortcutInfo shortcutInfo1, ShortcutInfo shortcutInfo2, WindowContainerTransaction wct, 933 Intent fillInIntent1, Intent fillInIntent2, RemoteTransition remoteTransition) { 934 // If one of the split apps to start is in Pip, only launch the non-pip app in fullscreen 935 boolean firstIntentPipped = mMixedHandler.isIntentInPip(pendingIntent1); 936 boolean secondIntentPipped = mMixedHandler.isIntentInPip(pendingIntent2); 937 if (firstIntentPipped || secondIntentPipped) { 938 Bundle options = secondIntentPipped ? options1 : options2; 939 options = options == null ? new Bundle() : options; 940 addActivityOptions(options, null); 941 if (shortcutInfo1 != null || shortcutInfo2 != null) { 942 ShortcutInfo infoToLaunch = secondIntentPipped ? shortcutInfo1 : shortcutInfo2; 943 wct.startShortcut(mContext.getPackageName(), infoToLaunch, options); 944 mSplitTransitions.startFullscreenTransition(wct, remoteTransition); 945 } else { 946 PendingIntent intentToLaunch = secondIntentPipped ? pendingIntent1 : pendingIntent2; 947 Intent fillInIntentToLaunch = secondIntentPipped ? fillInIntent1 : fillInIntent2; 948 startSingleIntent(intentToLaunch, fillInIntentToLaunch, options, wct, 949 remoteTransition); 950 } 951 return true; 952 } 953 return false; 954 } 955 956 /** @param pendingIntent Starts this intent in fullscreen */ startSingleIntent(PendingIntent pendingIntent, Intent fillInIntent, Bundle options, WindowContainerTransaction wct, RemoteTransition remoteTransition)957 private void startSingleIntent(PendingIntent pendingIntent, Intent fillInIntent, Bundle options, 958 WindowContainerTransaction wct, 959 RemoteTransition remoteTransition) { 960 Bundle optionsToLaunch = options != null ? options : new Bundle(); 961 addActivityOptions(optionsToLaunch, null); 962 wct.sendPendingIntent(pendingIntent, fillInIntent, optionsToLaunch); 963 mSplitTransitions.startFullscreenTransition(wct, remoteTransition); 964 } 965 966 /** Starts a pair of tasks using legacy transition. */ startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId)967 void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1, 968 int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, 969 @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, 970 InstanceId instanceId) { 971 final WindowContainerTransaction wct = new WindowContainerTransaction(); 972 if (options1 == null) options1 = new Bundle(); 973 if (taskId2 == INVALID_TASK_ID) { 974 // Launching a solo task. 975 // Exit split first if this task under split roots. 976 if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) { 977 exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); 978 } 979 ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); 980 activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); 981 options1 = activityOptions.toBundle(); 982 addActivityOptions(options1, null /* launchTarget */); 983 wct.startTask(taskId1, options1); 984 mSyncQueue.queue(wct); 985 return; 986 } 987 988 addActivityOptions(options1, mSideStage); 989 wct.startTask(taskId1, options1); 990 mSplitRequest = new SplitRequest(taskId1, taskId2, splitPosition); 991 startWithLegacyTransition(wct, taskId2, options2, splitPosition, snapPosition, adapter, 992 instanceId); 993 } 994 995 /** Starts a pair of intents using legacy transition. */ startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Intent fillInIntent1, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, @Nullable PendingIntent pendingIntent2, Intent fillInIntent2, @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId)996 void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Intent fillInIntent1, 997 @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, 998 @Nullable PendingIntent pendingIntent2, Intent fillInIntent2, 999 @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, 1000 @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, 1001 RemoteAnimationAdapter adapter, InstanceId instanceId) { 1002 final WindowContainerTransaction wct = new WindowContainerTransaction(); 1003 if (options1 == null) options1 = new Bundle(); 1004 if (pendingIntent2 == null) { 1005 // Launching a solo intent or shortcut as fullscreen. 1006 launchAsFullscreenWithRemoteAnimation(pendingIntent1, fillInIntent1, shortcutInfo1, 1007 options1, adapter, wct); 1008 return; 1009 } 1010 1011 addActivityOptions(options1, mSideStage); 1012 if (shortcutInfo1 != null) { 1013 wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1); 1014 } else { 1015 wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1); 1016 mSplitRequest = new SplitRequest(pendingIntent1.getIntent(), 1017 pendingIntent2 != null ? pendingIntent2.getIntent() : null, splitPosition); 1018 } 1019 startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, shortcutInfo2, options2, 1020 splitPosition, snapPosition, adapter, instanceId); 1021 } 1022 startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId)1023 void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent, 1024 @Nullable Bundle options1, int taskId, @Nullable Bundle options2, 1025 @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, 1026 RemoteAnimationAdapter adapter, InstanceId instanceId) { 1027 final WindowContainerTransaction wct = new WindowContainerTransaction(); 1028 if (options1 == null) options1 = new Bundle(); 1029 if (taskId == INVALID_TASK_ID) { 1030 // Launching a solo intent as fullscreen. 1031 launchAsFullscreenWithRemoteAnimation(pendingIntent, fillInIntent, null, options1, 1032 adapter, wct); 1033 return; 1034 } 1035 1036 addActivityOptions(options1, mSideStage); 1037 wct.sendPendingIntent(pendingIntent, fillInIntent, options1); 1038 mSplitRequest = new SplitRequest(taskId, pendingIntent.getIntent(), splitPosition); 1039 startWithLegacyTransition(wct, taskId, options2, splitPosition, snapPosition, adapter, 1040 instanceId); 1041 } 1042 1043 /** Starts a pair of shortcut and task using legacy transition. */ startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId)1044 void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, 1045 @Nullable Bundle options1, int taskId, @Nullable Bundle options2, 1046 @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, 1047 RemoteAnimationAdapter adapter, InstanceId instanceId) { 1048 final WindowContainerTransaction wct = new WindowContainerTransaction(); 1049 if (options1 == null) options1 = new Bundle(); 1050 if (taskId == INVALID_TASK_ID) { 1051 // Launching a solo shortcut as fullscreen. 1052 launchAsFullscreenWithRemoteAnimation(null, null, shortcutInfo, options1, adapter, wct); 1053 return; 1054 } 1055 1056 addActivityOptions(options1, mSideStage); 1057 wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1); 1058 startWithLegacyTransition(wct, taskId, options2, splitPosition, snapPosition, adapter, 1059 instanceId); 1060 } 1061 launchAsFullscreenWithRemoteAnimation(@ullable PendingIntent pendingIntent, @Nullable Intent fillInIntent, @Nullable ShortcutInfo shortcutInfo, @Nullable Bundle options, RemoteAnimationAdapter adapter, WindowContainerTransaction wct)1062 private void launchAsFullscreenWithRemoteAnimation(@Nullable PendingIntent pendingIntent, 1063 @Nullable Intent fillInIntent, @Nullable ShortcutInfo shortcutInfo, 1064 @Nullable Bundle options, RemoteAnimationAdapter adapter, 1065 WindowContainerTransaction wct) { 1066 LegacyTransitions.ILegacyTransition transition = 1067 (transit, apps, wallpapers, nonApps, finishedCallback, t) -> { 1068 if (apps == null || apps.length == 0) { 1069 onRemoteAnimationFinished(apps); 1070 t.apply(); 1071 try { 1072 adapter.getRunner().onAnimationCancelled(); 1073 } catch (RemoteException e) { 1074 Slog.e(TAG, "Error starting remote animation", e); 1075 } 1076 return; 1077 } 1078 1079 for (int i = 0; i < apps.length; ++i) { 1080 if (apps[i].mode == MODE_OPENING) { 1081 t.show(apps[i].leash); 1082 } 1083 } 1084 t.apply(); 1085 1086 try { 1087 adapter.getRunner().onAnimationStart( 1088 transit, apps, wallpapers, nonApps, finishedCallback); 1089 } catch (RemoteException e) { 1090 Slog.e(TAG, "Error starting remote animation", e); 1091 } 1092 }; 1093 1094 addActivityOptions(options, null /* launchTarget */); 1095 if (shortcutInfo != null) { 1096 wct.startShortcut(mContext.getPackageName(), shortcutInfo, options); 1097 } else if (pendingIntent != null) { 1098 wct.sendPendingIntent(pendingIntent, fillInIntent, options); 1099 } else { 1100 Slog.e(TAG, "Pending intent and shortcut are null is invalid case."); 1101 } 1102 mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); 1103 } 1104 startWithLegacyTransition(WindowContainerTransaction wct, @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent, @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions, @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId)1105 private void startWithLegacyTransition(WindowContainerTransaction wct, 1106 @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent, 1107 @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions, 1108 @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition, 1109 RemoteAnimationAdapter adapter, InstanceId instanceId) { 1110 startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent, 1111 mainShortcutInfo, mainOptions, sidePosition, snapPosition, adapter, instanceId); 1112 } 1113 startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId, @Nullable Bundle mainOptions, @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId)1114 private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId, 1115 @Nullable Bundle mainOptions, @SplitPosition int sidePosition, 1116 @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, 1117 InstanceId instanceId) { 1118 startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */, 1119 null /* mainFillInIntent */, null /* mainShortcutInfo */, mainOptions, sidePosition, 1120 snapPosition, adapter, instanceId); 1121 } 1122 1123 /** 1124 * @param wct transaction to start the first task 1125 * @param instanceId if {@code null}, will not log. Otherwise it will be used in 1126 * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)} 1127 */ startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId, @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent, @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle options, @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId)1128 private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId, 1129 @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent, 1130 @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle options, 1131 @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition, 1132 RemoteAnimationAdapter adapter, InstanceId instanceId) { 1133 if (!isSplitScreenVisible()) { 1134 exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); 1135 } 1136 1137 // Init divider first to make divider leash for remote animation target. 1138 mSplitLayout.init(); 1139 mSplitLayout.setDivideRatio(snapPosition); 1140 1141 // Apply surface bounds before animation start. 1142 SurfaceControl.Transaction startT = mTransactionPool.acquire(); 1143 updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */); 1144 startT.apply(); 1145 mTransactionPool.release(startT); 1146 1147 // Set false to avoid record new bounds with old task still on top; 1148 mShouldUpdateRecents = false; 1149 mIsDividerRemoteAnimating = true; 1150 if (mSplitRequest == null) { 1151 mSplitRequest = new SplitRequest(mainTaskId, 1152 mainPendingIntent != null ? mainPendingIntent.getIntent() : null, 1153 sidePosition); 1154 } 1155 setSideStagePosition(sidePosition, wct); 1156 if (!mMainStage.isActive()) { 1157 mMainStage.activate(wct, false /* reparent */); 1158 } 1159 1160 if (options == null) options = new Bundle(); 1161 addActivityOptions(options, mMainStage); 1162 1163 updateWindowBounds(mSplitLayout, wct); 1164 wct.reorder(mRootTaskInfo.token, true); 1165 setRootForceTranslucent(false, wct); 1166 1167 // TODO(b/268008375): Merge APIs to start a split pair into one. 1168 if (mainTaskId != INVALID_TASK_ID) { 1169 options = wrapAsSplitRemoteAnimation(adapter, options); 1170 wct.startTask(mainTaskId, options); 1171 mSyncQueue.queue(wct); 1172 } else { 1173 if (mainShortcutInfo != null) { 1174 wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options); 1175 } else { 1176 wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options); 1177 } 1178 mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct); 1179 } 1180 1181 setEnterInstanceId(instanceId); 1182 } 1183 wrapAsSplitRemoteAnimation(RemoteAnimationAdapter adapter, Bundle options)1184 private Bundle wrapAsSplitRemoteAnimation(RemoteAnimationAdapter adapter, Bundle options) { 1185 final WindowContainerTransaction evictWct = new WindowContainerTransaction(); 1186 if (isSplitScreenVisible()) { 1187 mMainStage.evictAllChildren(evictWct); 1188 mSideStage.evictAllChildren(evictWct); 1189 } 1190 1191 IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { 1192 @Override 1193 public void onAnimationStart(@WindowManager.TransitionOldType int transit, 1194 RemoteAnimationTarget[] apps, 1195 RemoteAnimationTarget[] wallpapers, 1196 RemoteAnimationTarget[] nonApps, 1197 final IRemoteAnimationFinishedCallback finishedCallback) { 1198 IRemoteAnimationFinishedCallback wrapCallback = 1199 new IRemoteAnimationFinishedCallback.Stub() { 1200 @Override 1201 public void onAnimationFinished() throws RemoteException { 1202 onRemoteAnimationFinishedOrCancelled(evictWct); 1203 finishedCallback.onAnimationFinished(); 1204 } 1205 }; 1206 Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication()); 1207 try { 1208 adapter.getRunner().onAnimationStart(transit, apps, wallpapers, 1209 ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps, 1210 getDividerBarLegacyTarget()), wrapCallback); 1211 } catch (RemoteException e) { 1212 Slog.e(TAG, "Error starting remote animation", e); 1213 } 1214 } 1215 1216 @Override 1217 public void onAnimationCancelled() { 1218 onRemoteAnimationFinishedOrCancelled(evictWct); 1219 setDividerVisibility(true, null); 1220 try { 1221 adapter.getRunner().onAnimationCancelled(); 1222 } catch (RemoteException e) { 1223 Slog.e(TAG, "Error starting remote animation", e); 1224 } 1225 } 1226 }; 1227 RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter( 1228 wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay()); 1229 ActivityOptions activityOptions = ActivityOptions.fromBundle(options); 1230 activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); 1231 return activityOptions.toBundle(); 1232 } 1233 wrapAsSplitRemoteAnimation( RemoteAnimationAdapter adapter)1234 private LegacyTransitions.ILegacyTransition wrapAsSplitRemoteAnimation( 1235 RemoteAnimationAdapter adapter) { 1236 LegacyTransitions.ILegacyTransition transition = 1237 (transit, apps, wallpapers, nonApps, finishedCallback, t) -> { 1238 if (apps == null || apps.length == 0) { 1239 onRemoteAnimationFinished(apps); 1240 t.apply(); 1241 try { 1242 adapter.getRunner().onAnimationCancelled(); 1243 } catch (RemoteException e) { 1244 Slog.e(TAG, "Error starting remote animation", e); 1245 } 1246 return; 1247 } 1248 1249 // Wrap the divider bar into non-apps target to animate together. 1250 nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps, 1251 getDividerBarLegacyTarget()); 1252 1253 for (int i = 0; i < apps.length; ++i) { 1254 if (apps[i].mode == MODE_OPENING) { 1255 t.show(apps[i].leash); 1256 // Reset the surface position of the opening app to prevent offset. 1257 t.setPosition(apps[i].leash, 0, 0); 1258 } 1259 } 1260 setDividerVisibility(true, t); 1261 t.apply(); 1262 1263 IRemoteAnimationFinishedCallback wrapCallback = 1264 new IRemoteAnimationFinishedCallback.Stub() { 1265 @Override 1266 public void onAnimationFinished() throws RemoteException { 1267 onRemoteAnimationFinished(apps); 1268 finishedCallback.onAnimationFinished(); 1269 } 1270 }; 1271 Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication()); 1272 try { 1273 adapter.getRunner().onAnimationStart( 1274 transit, apps, wallpapers, nonApps, wrapCallback); 1275 } catch (RemoteException e) { 1276 Slog.e(TAG, "Error starting remote animation", e); 1277 } 1278 }; 1279 1280 return transition; 1281 } 1282 setEnterInstanceId(InstanceId instanceId)1283 private void setEnterInstanceId(InstanceId instanceId) { 1284 if (instanceId != null) { 1285 mLogger.enterRequested(instanceId, ENTER_REASON_LAUNCHER); 1286 } 1287 } 1288 onRemoteAnimationFinishedOrCancelled(WindowContainerTransaction evictWct)1289 private void onRemoteAnimationFinishedOrCancelled(WindowContainerTransaction evictWct) { 1290 mIsDividerRemoteAnimating = false; 1291 mShouldUpdateRecents = true; 1292 clearRequestIfPresented(); 1293 // If any stage has no child after animation finished, it means that split will display 1294 // nothing, such status will happen if task and intent is same app but not support 1295 // multi-instance, we should exit split and expand that app as full screen. 1296 if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { 1297 mMainExecutor.execute(() -> 1298 exitSplitScreen(mMainStage.getChildCount() == 0 1299 ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); 1300 Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled", 1301 "main or side stage was not populated.")); 1302 handleUnsupportedSplitStart(); 1303 } else { 1304 mSyncQueue.queue(evictWct); 1305 mSyncQueue.runInSync(t -> { 1306 updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); 1307 }); 1308 } 1309 } 1310 onRemoteAnimationFinished(RemoteAnimationTarget[] apps)1311 private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) { 1312 mIsDividerRemoteAnimating = false; 1313 mShouldUpdateRecents = true; 1314 clearRequestIfPresented(); 1315 // If any stage has no child after finished animation, that side of the split will display 1316 // nothing. This might happen if starting the same app on the both sides while not 1317 // supporting multi-instance. Exit the split screen and expand that app to full screen. 1318 if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { 1319 mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0 1320 ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); 1321 Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished", 1322 "main or side stage was not populated")); 1323 handleUnsupportedSplitStart(); 1324 return; 1325 } 1326 1327 final WindowContainerTransaction evictWct = new WindowContainerTransaction(); 1328 mMainStage.evictNonOpeningChildren(apps, evictWct); 1329 mSideStage.evictNonOpeningChildren(apps, evictWct); 1330 mSyncQueue.queue(evictWct); 1331 } 1332 prepareEvictNonOpeningChildTasks(@plitPosition int position, RemoteAnimationTarget[] apps, WindowContainerTransaction wct)1333 void prepareEvictNonOpeningChildTasks(@SplitPosition int position, RemoteAnimationTarget[] apps, 1334 WindowContainerTransaction wct) { 1335 if (position == mSideStagePosition) { 1336 mSideStage.evictNonOpeningChildren(apps, wct); 1337 } else { 1338 mMainStage.evictNonOpeningChildren(apps, wct); 1339 } 1340 } 1341 prepareEvictInvisibleChildTasks(WindowContainerTransaction wct)1342 void prepareEvictInvisibleChildTasks(WindowContainerTransaction wct) { 1343 mMainStage.evictInvisibleChildren(wct); 1344 mSideStage.evictInvisibleChildren(wct); 1345 } 1346 resolveStartStage(@tageType int stage, @SplitPosition int position, @Nullable Bundle options, @Nullable WindowContainerTransaction wct)1347 Bundle resolveStartStage(@StageType int stage, @SplitPosition int position, 1348 @Nullable Bundle options, @Nullable WindowContainerTransaction wct) { 1349 switch (stage) { 1350 case STAGE_TYPE_UNDEFINED: { 1351 if (position != SPLIT_POSITION_UNDEFINED) { 1352 if (isSplitScreenVisible()) { 1353 // Use the stage of the specified position 1354 options = resolveStartStage( 1355 position == mSideStagePosition ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN, 1356 position, options, wct); 1357 } else { 1358 // Use the side stage as default to active split screen 1359 options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct); 1360 } 1361 } else { 1362 Slog.w(TAG, 1363 "No stage type nor split position specified to resolve start stage"); 1364 } 1365 break; 1366 } 1367 case STAGE_TYPE_SIDE: { 1368 if (position != SPLIT_POSITION_UNDEFINED) { 1369 setSideStagePosition(position, wct); 1370 } else { 1371 position = getSideStagePosition(); 1372 } 1373 if (options == null) { 1374 options = new Bundle(); 1375 } 1376 updateActivityOptions(options, position); 1377 break; 1378 } 1379 case STAGE_TYPE_MAIN: { 1380 if (position != SPLIT_POSITION_UNDEFINED) { 1381 // Set the side stage opposite of what we want to the main stage. 1382 setSideStagePosition(reverseSplitPosition(position), wct); 1383 } else { 1384 position = getMainStagePosition(); 1385 } 1386 if (options == null) { 1387 options = new Bundle(); 1388 } 1389 updateActivityOptions(options, position); 1390 break; 1391 } 1392 default: 1393 throw new IllegalArgumentException("Unknown stage=" + stage); 1394 } 1395 1396 return options; 1397 } 1398 1399 @SplitPosition getSideStagePosition()1400 int getSideStagePosition() { 1401 return mSideStagePosition; 1402 } 1403 1404 @SplitPosition getMainStagePosition()1405 int getMainStagePosition() { 1406 return reverseSplitPosition(mSideStagePosition); 1407 } 1408 getTaskId(@plitPosition int splitPosition)1409 int getTaskId(@SplitPosition int splitPosition) { 1410 if (splitPosition == SPLIT_POSITION_UNDEFINED) { 1411 return INVALID_TASK_ID; 1412 } 1413 1414 return mSideStagePosition == splitPosition 1415 ? mSideStage.getTopVisibleChildTaskId() 1416 : mMainStage.getTopVisibleChildTaskId(); 1417 } 1418 switchSplitPosition(String reason)1419 void switchSplitPosition(String reason) { 1420 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "switchSplitPosition"); 1421 final SurfaceControl.Transaction t = mTransactionPool.acquire(); 1422 mTempRect1.setEmpty(); 1423 final StageTaskListener topLeftStage = 1424 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; 1425 final SurfaceControl topLeftScreenshot = ScreenshotUtils.takeScreenshot(t, 1426 topLeftStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1); 1427 final StageTaskListener bottomRightStage = 1428 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; 1429 final SurfaceControl bottomRightScreenshot = ScreenshotUtils.takeScreenshot(t, 1430 bottomRightStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1); 1431 mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash, 1432 insets -> { 1433 WindowContainerTransaction wct = new WindowContainerTransaction(); 1434 setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct); 1435 mSyncQueue.queue(wct); 1436 mSyncQueue.runInSync(st -> { 1437 updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */); 1438 st.setPosition(topLeftScreenshot, -insets.left, -insets.top); 1439 st.setPosition(bottomRightScreenshot, insets.left, insets.top); 1440 1441 final ValueAnimator va = ValueAnimator.ofFloat(1, 0); 1442 va.addUpdateListener(valueAnimator-> { 1443 final float progress = (float) valueAnimator.getAnimatedValue(); 1444 t.setAlpha(topLeftScreenshot, progress); 1445 t.setAlpha(bottomRightScreenshot, progress); 1446 t.apply(); 1447 }); 1448 va.addListener(new AnimatorListenerAdapter() { 1449 @Override 1450 public void onAnimationEnd( 1451 @androidx.annotation.NonNull Animator animation) { 1452 t.remove(topLeftScreenshot); 1453 t.remove(bottomRightScreenshot); 1454 t.apply(); 1455 mTransactionPool.release(t); 1456 } 1457 }); 1458 va.start(); 1459 }); 1460 }); 1461 1462 ProtoLog.v(WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason); 1463 mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(), 1464 getSideStagePosition(), mSideStage.getTopChildTaskUid(), 1465 mSplitLayout.isLeftRightSplit()); 1466 } 1467 setSideStagePosition(@plitPosition int sideStagePosition, @Nullable WindowContainerTransaction wct)1468 void setSideStagePosition(@SplitPosition int sideStagePosition, 1469 @Nullable WindowContainerTransaction wct) { 1470 setSideStagePosition(sideStagePosition, true /* updateBounds */, wct); 1471 } 1472 setSideStagePosition(@plitPosition int sideStagePosition, boolean updateBounds, @Nullable WindowContainerTransaction wct)1473 private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds, 1474 @Nullable WindowContainerTransaction wct) { 1475 if (mSideStagePosition == sideStagePosition) return; 1476 mSideStagePosition = sideStagePosition; 1477 sendOnStagePositionChanged(); 1478 1479 if (mSideStageListener.mVisible && updateBounds) { 1480 if (wct == null) { 1481 // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds. 1482 onLayoutSizeChanged(mSplitLayout); 1483 } else { 1484 updateWindowBounds(mSplitLayout, wct); 1485 sendOnBoundsChanged(); 1486 } 1487 } 1488 } 1489 onKeyguardVisibilityChanged(boolean showing)1490 void onKeyguardVisibilityChanged(boolean showing) { 1491 mKeyguardShowing = showing; 1492 if (!mMainStage.isActive()) { 1493 return; 1494 } 1495 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onKeyguardVisibilityChanged: showing=%b", showing); 1496 setDividerVisibility(!mKeyguardShowing, null); 1497 } 1498 onFinishedWakingUp()1499 void onFinishedWakingUp() { 1500 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinishedWakingUp"); 1501 if (!mMainStage.isActive()) { 1502 return; 1503 } 1504 1505 // Check if there's only one stage visible while keyguard occluded. 1506 final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible; 1507 final boolean oneStageVisible = 1508 mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible; 1509 if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) { 1510 // Dismiss split because there's show-when-locked activity showing on top of keyguard. 1511 // Also make sure the task contains show-when-locked activity remains on top after split 1512 // dismissed. 1513 final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage; 1514 exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); 1515 } 1516 1517 // Dismiss split if the flag record any side of stages. 1518 if (mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) { 1519 if (ENABLE_SHELL_TRANSITIONS) { 1520 // Need manually clear here due to this transition might be aborted due to keyguard 1521 // on top and lead to no visible change. 1522 clearSplitPairedInRecents(EXIT_REASON_DEVICE_FOLDED); 1523 final WindowContainerTransaction wct = new WindowContainerTransaction(); 1524 prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct); 1525 mSplitTransitions.startDismissTransition(wct, this, 1526 mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED); 1527 setSplitsVisible(false); 1528 } else { 1529 exitSplitScreen( 1530 mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage, 1531 EXIT_REASON_DEVICE_FOLDED); 1532 } 1533 mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; 1534 } 1535 } 1536 exitSplitScreenOnHide(boolean exitSplitScreenOnHide)1537 void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { 1538 mExitSplitScreenOnHide = exitSplitScreenOnHide; 1539 } 1540 1541 /** Exits split screen with legacy transition */ exitSplitScreen(int toTopTaskId, @ExitReason int exitReason)1542 void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) { 1543 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: topTaskId=%d reason=%s active=%b", 1544 toTopTaskId, exitReasonToString(exitReason), mMainStage.isActive()); 1545 if (!mMainStage.isActive()) return; 1546 1547 StageTaskListener childrenToTop = null; 1548 if (mMainStage.containsTask(toTopTaskId)) { 1549 childrenToTop = mMainStage; 1550 } else if (mSideStage.containsTask(toTopTaskId)) { 1551 childrenToTop = mSideStage; 1552 } 1553 1554 final WindowContainerTransaction wct = new WindowContainerTransaction(); 1555 if (childrenToTop != null) { 1556 childrenToTop.reorderChild(toTopTaskId, true /* onTop */, wct); 1557 } 1558 applyExitSplitScreen(childrenToTop, wct, exitReason); 1559 } 1560 1561 /** Exits split screen with legacy transition */ exitSplitScreen(@ullable StageTaskListener childrenToTop, @ExitReason int exitReason)1562 private void exitSplitScreen(@Nullable StageTaskListener childrenToTop, 1563 @ExitReason int exitReason) { 1564 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b", 1565 childrenToTop == mMainStage, exitReasonToString(exitReason), mMainStage.isActive()); 1566 if (!mMainStage.isActive()) return; 1567 1568 final WindowContainerTransaction wct = new WindowContainerTransaction(); 1569 applyExitSplitScreen(childrenToTop, wct, exitReason); 1570 } 1571 applyExitSplitScreen(@ullable StageTaskListener childrenToTop, WindowContainerTransaction wct, @ExitReason int exitReason)1572 private void applyExitSplitScreen(@Nullable StageTaskListener childrenToTop, 1573 WindowContainerTransaction wct, @ExitReason int exitReason) { 1574 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "applyExitSplitScreen: reason=%s", 1575 exitReasonToString(exitReason)); 1576 if (!mMainStage.isActive() || mIsExiting) return; 1577 1578 onSplitScreenExit(); 1579 clearSplitPairedInRecents(exitReason); 1580 1581 mShouldUpdateRecents = false; 1582 mIsDividerRemoteAnimating = false; 1583 mSplitRequest = null; 1584 1585 mSplitLayout.getInvisibleBounds(mTempRect1); 1586 if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) { 1587 mSideStage.removeAllTasks(wct, false /* toTop */); 1588 mMainStage.deactivate(wct, false /* toTop */); 1589 wct.reorder(mRootTaskInfo.token, false /* onTop */); 1590 setRootForceTranslucent(true, wct); 1591 wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); 1592 onTransitionAnimationComplete(); 1593 } else { 1594 // Expand to top side split as full screen for fading out decor animation and dismiss 1595 // another side split(Moving its children to bottom). 1596 mIsExiting = true; 1597 childrenToTop.resetBounds(wct); 1598 wct.reorder(childrenToTop.mRootTaskInfo.token, true); 1599 } 1600 wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, 1601 false /* reparentLeafTaskIfRelaunch */); 1602 mSyncQueue.queue(wct); 1603 mSyncQueue.runInSync(t -> { 1604 t.setWindowCrop(mMainStage.mRootLeash, null) 1605 .setWindowCrop(mSideStage.mRootLeash, null); 1606 t.hide(mMainStage.mDimLayer).hide(mSideStage.mDimLayer); 1607 setDividerVisibility(false, t); 1608 1609 if (childrenToTop == null) { 1610 t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right); 1611 } else { 1612 // In this case, exit still under progress, fade out the split decor after first WCT 1613 // done and do remaining WCT after animation finished. 1614 childrenToTop.fadeOutDecor(() -> { 1615 WindowContainerTransaction finishedWCT = new WindowContainerTransaction(); 1616 mIsExiting = false; 1617 mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */); 1618 mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */); 1619 finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */); 1620 setRootForceTranslucent(true, finishedWCT); 1621 finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); 1622 mSyncQueue.queue(finishedWCT); 1623 mSyncQueue.runInSync(at -> { 1624 at.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right); 1625 }); 1626 onTransitionAnimationComplete(); 1627 }); 1628 } 1629 }); 1630 1631 // Log the exit 1632 if (childrenToTop != null) { 1633 logExitToStage(exitReason, childrenToTop == mMainStage); 1634 } else { 1635 logExit(exitReason); 1636 } 1637 } 1638 dismissSplitScreen(int toTopTaskId, @ExitReason int exitReason)1639 void dismissSplitScreen(int toTopTaskId, @ExitReason int exitReason) { 1640 if (!mMainStage.isActive()) return; 1641 final int stage = getStageOfTask(toTopTaskId); 1642 final WindowContainerTransaction wct = new WindowContainerTransaction(); 1643 prepareExitSplitScreen(stage, wct); 1644 mSplitTransitions.startDismissTransition(wct, this, stage, exitReason); 1645 } 1646 1647 /** 1648 * Overridden by child classes. 1649 */ onSplitScreenEnter()1650 protected void onSplitScreenEnter() { 1651 } 1652 1653 /** 1654 * Overridden by child classes. 1655 */ onSplitScreenExit()1656 protected void onSplitScreenExit() { 1657 } 1658 1659 /** 1660 * Exits the split screen by finishing one of the tasks. 1661 */ exitStage(@plitPosition int stageToClose)1662 protected void exitStage(@SplitPosition int stageToClose) { 1663 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitStage: stageToClose=%d", stageToClose); 1664 mSplitLayout.flingDividerToDismiss(stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT, 1665 EXIT_REASON_APP_FINISHED); 1666 } 1667 1668 /** 1669 * Grants focus to the main or the side stages. 1670 */ grantFocusToStage(@plitPosition int stageToFocus)1671 protected void grantFocusToStage(@SplitPosition int stageToFocus) { 1672 IActivityTaskManager activityTaskManagerService = IActivityTaskManager.Stub.asInterface( 1673 ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE)); 1674 try { 1675 activityTaskManagerService.setFocusedTask(getTaskId(stageToFocus)); 1676 } catch (RemoteException | NullPointerException e) { 1677 ProtoLog.e(WM_SHELL_SPLIT_SCREEN, 1678 "Unable to update focus on the chosen stage: %s", e.getMessage()); 1679 } 1680 } 1681 grantFocusToPosition(boolean leftOrTop)1682 protected void grantFocusToPosition(boolean leftOrTop) { 1683 int stageToFocus; 1684 if (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT) { 1685 stageToFocus = leftOrTop ? getMainStagePosition() : getSideStagePosition(); 1686 } else { 1687 stageToFocus = leftOrTop ? getSideStagePosition() : getMainStagePosition(); 1688 } 1689 grantFocusToStage(stageToFocus); 1690 } 1691 clearRequestIfPresented()1692 private void clearRequestIfPresented() { 1693 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented"); 1694 if (mSideStageListener.mVisible && mSideStageListener.mHasChildren 1695 && mMainStageListener.mVisible && mSideStageListener.mHasChildren) { 1696 mSplitRequest = null; 1697 } 1698 } 1699 1700 /** 1701 * Returns whether the split pair in the recent tasks list should be broken. 1702 */ shouldBreakPairedTaskInRecents(@xitReason int exitReason)1703 private boolean shouldBreakPairedTaskInRecents(@ExitReason int exitReason) { 1704 switch (exitReason) { 1705 // One of the apps doesn't support MW 1706 case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW: 1707 // User has explicitly dragged the divider to dismiss split 1708 case EXIT_REASON_DRAG_DIVIDER: 1709 // Either of the split apps have finished 1710 case EXIT_REASON_APP_FINISHED: 1711 // One of the children enters PiP 1712 case EXIT_REASON_CHILD_TASK_ENTER_PIP: 1713 // One of the apps occludes lock screen. 1714 case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP: 1715 // User has unlocked the device after folded 1716 case EXIT_REASON_DEVICE_FOLDED: 1717 // The device is folded 1718 case EXIT_REASON_FULLSCREEN_SHORTCUT: 1719 // User has used a keyboard shortcut to go back to fullscreen from split 1720 case EXIT_REASON_DESKTOP_MODE: 1721 // One of the children enters desktop mode 1722 case EXIT_REASON_UNKNOWN: 1723 // Unknown reason 1724 return true; 1725 default: 1726 return false; 1727 } 1728 } 1729 clearSplitPairedInRecents(@xitReason int exitReason)1730 void clearSplitPairedInRecents(@ExitReason int exitReason) { 1731 if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return; 1732 1733 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearSplitPairedInRecents: reason=%s", 1734 exitReasonToString(exitReason)); 1735 mRecentTasks.ifPresent(recentTasks -> { 1736 // Notify recents if we are exiting in a way that breaks the pair, and disable further 1737 // updates to splits in the recents until we enter split again 1738 mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId)); 1739 mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId)); 1740 }); 1741 } 1742 1743 /** 1744 * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates 1745 * an existing WindowContainerTransaction (rather than applying immediately). This is intended 1746 * to be used when exiting split might be bundled with other window operations. 1747 */ prepareExitSplitScreen(@tageType int stageToTop, @NonNull WindowContainerTransaction wct)1748 void prepareExitSplitScreen(@StageType int stageToTop, 1749 @NonNull WindowContainerTransaction wct) { 1750 if (!mMainStage.isActive()) return; 1751 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareExitSplitScreen: stageToTop=%d", stageToTop); 1752 mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); 1753 mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN); 1754 } 1755 prepareEnterSplitScreen(WindowContainerTransaction wct)1756 private void prepareEnterSplitScreen(WindowContainerTransaction wct) { 1757 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen"); 1758 prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED, 1759 !mIsDropEntering); 1760 } 1761 1762 /** 1763 * Prepare transaction to active split screen. If there's a task indicated, the task will be put 1764 * into side stage. 1765 */ prepareEnterSplitScreen(WindowContainerTransaction wct, @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, boolean resizeAnim)1766 void prepareEnterSplitScreen(WindowContainerTransaction wct, 1767 @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, 1768 boolean resizeAnim) { 1769 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen: position=%d resize=%b", 1770 startPosition, resizeAnim); 1771 onSplitScreenEnter(); 1772 // Preemptively reset the reparenting behavior if we know that we are entering, as starting 1773 // split tasks with activity trampolines can inadvertently trigger the task to be 1774 // reparented out of the split root mid-launch 1775 wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, 1776 false /* setReparentLeafTaskIfRelaunch */); 1777 if (isSplitActive()) { 1778 prepareBringSplit(wct, taskInfo, startPosition, resizeAnim); 1779 } else { 1780 prepareActiveSplit(wct, taskInfo, startPosition, resizeAnim); 1781 } 1782 } 1783 prepareBringSplit(WindowContainerTransaction wct, @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, boolean resizeAnim)1784 private void prepareBringSplit(WindowContainerTransaction wct, 1785 @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, 1786 boolean resizeAnim) { 1787 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareBringSplit: task=%d isSplitVisible=%b", 1788 taskInfo != null ? taskInfo.taskId : -1, isSplitScreenVisible()); 1789 if (taskInfo != null) { 1790 wct.startTask(taskInfo.taskId, 1791 resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct)); 1792 } 1793 // If running background, we need to reparent current top visible task to main stage. 1794 if (!isSplitScreenVisible()) { 1795 // Ensure to evict old splitting tasks because the new split pair might be composed by 1796 // one of the splitting tasks, evicting the task when finishing entering transition 1797 // won't guarantee to put the task to the indicated new position. 1798 if (!mSkipEvictingMainStageChildren) { 1799 mMainStage.evictAllChildren(wct); 1800 } 1801 mMainStage.reparentTopTask(wct); 1802 prepareSplitLayout(wct, resizeAnim); 1803 } 1804 } 1805 prepareActiveSplit(WindowContainerTransaction wct, @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, boolean resizeAnim)1806 private void prepareActiveSplit(WindowContainerTransaction wct, 1807 @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, 1808 boolean resizeAnim) { 1809 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareActiveSplit: task=%d isSplitVisible=%b", 1810 taskInfo != null ? taskInfo.taskId : -1, isSplitScreenVisible()); 1811 if (!ENABLE_SHELL_TRANSITIONS) { 1812 // Legacy transition we need to create divider here, shell transition case we will 1813 // create it on #finishEnterSplitScreen 1814 mSplitLayout.init(); 1815 } else { 1816 // We handle split visibility itself on shell transition, but sometimes we didn't 1817 // reset it correctly after dismiss by some reason, so just set invisible before active. 1818 setSplitsVisible(false); 1819 } 1820 if (taskInfo != null) { 1821 setSideStagePosition(startPosition, wct); 1822 mSideStage.addTask(taskInfo, wct); 1823 } 1824 mMainStage.activate(wct, true /* includingTopTask */); 1825 prepareSplitLayout(wct, resizeAnim); 1826 } 1827 prepareSplitLayout(WindowContainerTransaction wct, boolean resizeAnim)1828 private void prepareSplitLayout(WindowContainerTransaction wct, boolean resizeAnim) { 1829 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareSplitLayout: resize=%b", resizeAnim); 1830 if (resizeAnim) { 1831 mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT); 1832 } else { 1833 mSplitLayout.resetDividerPosition(); 1834 } 1835 updateWindowBounds(mSplitLayout, wct); 1836 if (resizeAnim) { 1837 // Reset its smallest width dp to avoid is change layout before it actually resized to 1838 // split bounds. 1839 wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token, 1840 SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); 1841 mSplitLayout.getInvisibleBounds(mTempRect1); 1842 mSplitLayout.setTaskBounds(wct, mSideStage.mRootTaskInfo, mTempRect1); 1843 } 1844 wct.reorder(mRootTaskInfo.token, true); 1845 setRootForceTranslucent(false, wct); 1846 } 1847 finishEnterSplitScreen(SurfaceControl.Transaction finishT)1848 void finishEnterSplitScreen(SurfaceControl.Transaction finishT) { 1849 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen"); 1850 mSplitLayout.update(null, true /* resetImePosition */); 1851 mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash); 1852 mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash); 1853 setDividerVisibility(true, finishT); 1854 // Ensure divider surface are re-parented back into the hierarchy at the end of the 1855 // transition. See Transition#buildFinishTransaction for more detail. 1856 finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash); 1857 1858 updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */); 1859 finishT.show(mRootTaskLeash); 1860 setSplitsVisible(true); 1861 mIsDropEntering = false; 1862 mSkipEvictingMainStageChildren = false; 1863 mSplitRequest = null; 1864 updateRecentTasksSplitPair(); 1865 if (!mLogger.hasStartedSession()) { 1866 mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(), 1867 getMainStagePosition(), mMainStage.getTopChildTaskUid(), 1868 getSideStagePosition(), mSideStage.getTopChildTaskUid(), 1869 mSplitLayout.isLeftRightSplit()); 1870 } 1871 } 1872 getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds)1873 void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) { 1874 outTopOrLeftBounds.set(mSplitLayout.getBounds1()); 1875 outBottomOrRightBounds.set(mSplitLayout.getBounds2()); 1876 } 1877 1878 @SplitPosition getSplitPosition(int taskId)1879 int getSplitPosition(int taskId) { 1880 if (mSideStage.getTopVisibleChildTaskId() == taskId) { 1881 return getSideStagePosition(); 1882 } else if (mMainStage.getTopVisibleChildTaskId() == taskId) { 1883 return getMainStagePosition(); 1884 } 1885 return SPLIT_POSITION_UNDEFINED; 1886 } 1887 addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget)1888 private void addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget) { 1889 ActivityOptions options = ActivityOptions.fromBundle(opts); 1890 if (launchTarget != null) { 1891 options.setLaunchRootTask(launchTarget.mRootTaskInfo.token); 1892 } 1893 // Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split 1894 // will be canceled. 1895 options.setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED); 1896 options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); 1897 1898 // TODO (b/336477473): Disallow enter PiP when launching a task in split by default; 1899 // this might have to be changed as more split-to-pip cujs are defined. 1900 options.setDisallowEnterPictureInPictureWhileLaunching(true); 1901 opts.putAll(options.toBundle()); 1902 } 1903 updateActivityOptions(Bundle opts, @SplitPosition int position)1904 void updateActivityOptions(Bundle opts, @SplitPosition int position) { 1905 addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage); 1906 } 1907 registerSplitScreenListener(SplitScreen.SplitScreenListener listener)1908 void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) { 1909 if (mListeners.contains(listener)) return; 1910 mListeners.add(listener); 1911 sendStatusToListener(listener); 1912 } 1913 unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener)1914 void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) { 1915 mListeners.remove(listener); 1916 } 1917 registerSplitSelectListener(SplitScreen.SplitSelectListener listener)1918 void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) { 1919 mSelectListeners.add(listener); 1920 } 1921 unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener)1922 void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) { 1923 mSelectListeners.remove(listener); 1924 } 1925 sendStatusToListener(SplitScreen.SplitScreenListener listener)1926 void sendStatusToListener(SplitScreen.SplitScreenListener listener) { 1927 listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition()); 1928 listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition()); 1929 listener.onSplitVisibilityChanged(isSplitScreenVisible()); 1930 if (mSplitLayout != null) { 1931 listener.onSplitBoundsChanged(mSplitLayout.getRootBounds(), getMainStageBounds(), 1932 getSideStageBounds()); 1933 } 1934 mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE); 1935 mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN); 1936 } 1937 sendOnStagePositionChanged()1938 private void sendOnStagePositionChanged() { 1939 for (int i = mListeners.size() - 1; i >= 0; --i) { 1940 final SplitScreen.SplitScreenListener l = mListeners.get(i); 1941 l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition()); 1942 l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition()); 1943 } 1944 } 1945 sendOnBoundsChanged()1946 private void sendOnBoundsChanged() { 1947 if (mSplitLayout == null) return; 1948 for (int i = mListeners.size() - 1; i >= 0; --i) { 1949 mListeners.get(i).onSplitBoundsChanged(mSplitLayout.getRootBounds(), 1950 getMainStageBounds(), getSideStageBounds()); 1951 } 1952 } 1953 onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId, boolean present, boolean visible)1954 private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId, 1955 boolean present, boolean visible) { 1956 int stage; 1957 if (present) { 1958 stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; 1959 } else { 1960 // No longer on any stage 1961 stage = STAGE_TYPE_UNDEFINED; 1962 } 1963 if (stage == STAGE_TYPE_MAIN) { 1964 mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(), 1965 mSplitLayout.isLeftRightSplit()); 1966 } else { 1967 mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(), 1968 mSplitLayout.isLeftRightSplit()); 1969 } 1970 if (present) { 1971 updateRecentTasksSplitPair(); 1972 } 1973 1974 for (int i = mListeners.size() - 1; i >= 0; --i) { 1975 mListeners.get(i).onTaskStageChanged(taskId, stage, visible); 1976 } 1977 } 1978 updateRecentTasksSplitPair()1979 private void updateRecentTasksSplitPair() { 1980 // Preventing from single task update while processing recents. 1981 if (!mShouldUpdateRecents || !mPausingTasks.isEmpty()) { 1982 return; 1983 } 1984 mRecentTasks.ifPresent(recentTasks -> { 1985 Rect topLeftBounds = mSplitLayout.getBounds1(); 1986 Rect bottomRightBounds = mSplitLayout.getBounds2(); 1987 int mainStageTopTaskId = mMainStage.getTopVisibleChildTaskId(); 1988 int sideStageTopTaskId = mSideStage.getTopVisibleChildTaskId(); 1989 boolean sideStageTopLeft = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; 1990 int leftTopTaskId; 1991 int rightBottomTaskId; 1992 if (sideStageTopLeft) { 1993 leftTopTaskId = sideStageTopTaskId; 1994 rightBottomTaskId = mainStageTopTaskId; 1995 } else { 1996 leftTopTaskId = mainStageTopTaskId; 1997 rightBottomTaskId = sideStageTopTaskId; 1998 } 1999 SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds, 2000 leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition()); 2001 if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) { 2002 // Update the pair for the top tasks 2003 boolean added = recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, 2004 splitBounds); 2005 if (added) { 2006 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 2007 "updateRecentTasksSplitPair: adding split pair ltTask=%d rbTask=%d", 2008 leftTopTaskId, rightBottomTaskId); 2009 } 2010 } 2011 }); 2012 } 2013 sendSplitVisibilityChanged()2014 private void sendSplitVisibilityChanged() { 2015 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "sendSplitVisibilityChanged: dividerVisible=%b", 2016 mDividerVisible); 2017 for (int i = mListeners.size() - 1; i >= 0; --i) { 2018 final SplitScreen.SplitScreenListener l = mListeners.get(i); 2019 l.onSplitVisibilityChanged(mDividerVisible); 2020 } 2021 sendOnBoundsChanged(); 2022 } 2023 2024 @Override 2025 @CallSuper onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)2026 public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { 2027 if (mRootTaskInfo != null || taskInfo.hasParentTask()) { 2028 throw new IllegalArgumentException(this + "\n Unknown task appeared: " + taskInfo); 2029 } 2030 2031 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%s", taskInfo); 2032 mRootTaskInfo = taskInfo; 2033 mRootTaskLeash = leash; 2034 2035 if (mSplitLayout == null) { 2036 mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext, 2037 mRootTaskInfo.configuration, this, mParentContainerCallbacks, 2038 mDisplayController, mDisplayImeController, mTaskOrganizer, 2039 PARALLAX_ALIGN_CENTER /* parallaxType */); 2040 mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout); 2041 } 2042 2043 onRootTaskAppeared(); 2044 } 2045 2046 @Override 2047 @CallSuper onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)2048 public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { 2049 if (mRootTaskInfo == null || mRootTaskInfo.taskId != taskInfo.taskId) { 2050 throw new IllegalArgumentException(this + "\n Unknown task info changed: " + taskInfo); 2051 } 2052 mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo)); 2053 mRootTaskInfo = taskInfo; 2054 if (mSplitLayout != null 2055 && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration) 2056 && mMainStage.isActive()) { 2057 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: task=%d updating", 2058 taskInfo.taskId); 2059 // Clear the divider remote animating flag as the divider will be re-rendered to apply 2060 // the new rotation config. Don't reset the IME state since those updates are not in 2061 // sync with task info changes. 2062 mIsDividerRemoteAnimating = false; 2063 mSplitLayout.update(null /* t */, false /* resetImePosition */); 2064 onLayoutSizeChanged(mSplitLayout); 2065 } 2066 } 2067 2068 @Override 2069 @CallSuper onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)2070 public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 2071 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%s", taskInfo); 2072 if (mRootTaskInfo == null) { 2073 throw new IllegalArgumentException(this + "\n Unknown task vanished: " + taskInfo); 2074 } 2075 2076 onRootTaskVanished(); 2077 2078 if (mSplitLayout != null) { 2079 mSplitLayout.release(); 2080 mSplitLayout = null; 2081 } 2082 2083 mRootTaskInfo = null; 2084 mRootTaskLeash = null; 2085 mIsRootTranslucent = false; 2086 } 2087 2088 2089 @VisibleForTesting onRootTaskAppeared()2090 void onRootTaskAppeared() { 2091 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskAppeared: rootTask=%s mainRoot=%b sideRoot=%b", 2092 mRootTaskInfo, mMainStageListener.mHasRootTask, mSideStageListener.mHasRootTask); 2093 // Wait unit all root tasks appeared. 2094 if (mRootTaskInfo == null 2095 || !mMainStageListener.mHasRootTask 2096 || !mSideStageListener.mHasRootTask) { 2097 return; 2098 } 2099 2100 final WindowContainerTransaction wct = new WindowContainerTransaction(); 2101 wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true); 2102 wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true); 2103 // Make the stages adjacent to each other so they occlude what's behind them. 2104 wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); 2105 setRootForceTranslucent(true, wct); 2106 mSplitLayout.getInvisibleBounds(mTempRect1); 2107 wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); 2108 mSyncQueue.queue(wct); 2109 mSyncQueue.runInSync(t -> { 2110 t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top); 2111 }); 2112 mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token); 2113 } 2114 2115 /** Callback when split roots have child task appeared under it, this is a little different from 2116 * #onStageHasChildrenChanged because this would be called every time child task appeared. 2117 * NOTICE: This only be called on legacy transition. */ onChildTaskAppeared(StageListenerImpl stageListener, int taskId)2118 private void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) { 2119 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onChildTaskAppeared: isMainStage=%b task=%d", 2120 stageListener == mMainStageListener, taskId); 2121 // Handle entering split screen while there is a split pair running in the background. 2122 if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive() 2123 && mSplitRequest == null) { 2124 final WindowContainerTransaction wct = new WindowContainerTransaction(); 2125 prepareEnterSplitScreen(wct); 2126 mMainStage.evictAllChildren(wct); 2127 mSideStage.evictOtherChildren(wct, taskId); 2128 2129 mSyncQueue.queue(wct); 2130 mSyncQueue.runInSync(t -> { 2131 if (mIsDropEntering) { 2132 updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); 2133 mIsDropEntering = false; 2134 mSkipEvictingMainStageChildren = false; 2135 } else { 2136 mShowDecorImmediately = true; 2137 mSplitLayout.flingDividerToCenter(/*finishCallback*/ null); 2138 } 2139 }); 2140 } 2141 } 2142 onRootTaskVanished()2143 private void onRootTaskVanished() { 2144 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskVanished"); 2145 final WindowContainerTransaction wct = new WindowContainerTransaction(); 2146 mLaunchAdjacentController.clearLaunchAdjacentRoot(); 2147 applyExitSplitScreen(null /* childrenToTop */, wct, EXIT_REASON_ROOT_TASK_VANISHED); 2148 mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, mSplitLayout); 2149 } 2150 setRootForceTranslucent(boolean translucent, WindowContainerTransaction wct)2151 private void setRootForceTranslucent(boolean translucent, WindowContainerTransaction wct) { 2152 if (mIsRootTranslucent == translucent) return; 2153 2154 mIsRootTranslucent = translucent; 2155 wct.setForceTranslucent(mRootTaskInfo.token, translucent); 2156 } 2157 2158 /** Callback when split roots visiblility changed. 2159 * NOTICE: This only be called on legacy transition. */ onStageVisibilityChanged(StageListenerImpl stageListener)2160 private void onStageVisibilityChanged(StageListenerImpl stageListener) { 2161 // If split didn't active, just ignore this callback because we should already did these 2162 // on #applyExitSplitScreen. 2163 if (!isSplitActive()) { 2164 return; 2165 } 2166 2167 final boolean sideStageVisible = mSideStageListener.mVisible; 2168 final boolean mainStageVisible = mMainStageListener.mVisible; 2169 2170 // Wait for both stages having the same visibility to prevent causing flicker. 2171 if (mainStageVisible != sideStageVisible) { 2172 return; 2173 } 2174 2175 // TODO Protolog 2176 2177 // Check if it needs to dismiss split screen when both stage invisible. 2178 if (!mainStageVisible && mExitSplitScreenOnHide) { 2179 exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME); 2180 return; 2181 } 2182 2183 final WindowContainerTransaction wct = new WindowContainerTransaction(); 2184 if (!mainStageVisible) { 2185 // Split entering background. 2186 wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, 2187 true /* setReparentLeafTaskIfRelaunch */); 2188 setRootForceTranslucent(true, wct); 2189 } else { 2190 clearRequestIfPresented(); 2191 wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, 2192 false /* setReparentLeafTaskIfRelaunch */); 2193 setRootForceTranslucent(false, wct); 2194 } 2195 2196 mSyncQueue.queue(wct); 2197 setDividerVisibility(mainStageVisible, null); 2198 } 2199 2200 // Set divider visibility flag and try to apply it, the param transaction is used to apply. 2201 // See applyDividerVisibility for more detail. setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t)2202 private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) { 2203 if (visible == mDividerVisible) { 2204 return; 2205 } 2206 2207 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 2208 "setDividerVisibility: visible=%b keyguardShowing=%b dividerAnimating=%b caller=%s", 2209 visible, mKeyguardShowing, mIsDividerRemoteAnimating, Debug.getCaller()); 2210 2211 // Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard 2212 // dismissing animation. 2213 if (visible && mKeyguardShowing) { 2214 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 2215 " Defer showing divider bar due to keyguard showing."); 2216 return; 2217 } 2218 2219 mDividerVisible = visible; 2220 sendSplitVisibilityChanged(); 2221 2222 if (mIsDividerRemoteAnimating) { 2223 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 2224 " Skip animating divider bar due to it's remote animating."); 2225 return; 2226 } 2227 2228 applyDividerVisibility(t); 2229 } 2230 2231 // Apply divider visibility by current visibility flag. If param transaction is non-null, it 2232 // will apply by that transaction, if it is null and visible, it will run a fade-in animation, 2233 // otherwise hide immediately. applyDividerVisibility(@ullable SurfaceControl.Transaction t)2234 private void applyDividerVisibility(@Nullable SurfaceControl.Transaction t) { 2235 final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); 2236 if (dividerLeash == null) { 2237 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 2238 " Skip animating divider bar due to divider leash not ready."); 2239 return; 2240 } 2241 if (mIsDividerRemoteAnimating) { 2242 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 2243 " Skip animating divider bar due to it's remote animating."); 2244 return; 2245 } 2246 2247 if (mDividerFadeInAnimator != null && mDividerFadeInAnimator.isRunning()) { 2248 mDividerFadeInAnimator.cancel(); 2249 } 2250 2251 mSplitLayout.getRefDividerBounds(mTempRect1); 2252 if (t != null) { 2253 t.setVisibility(dividerLeash, mDividerVisible); 2254 t.setLayer(dividerLeash, Integer.MAX_VALUE); 2255 t.setPosition(dividerLeash, mTempRect1.left, mTempRect1.top); 2256 } else if (mDividerVisible) { 2257 final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); 2258 mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f); 2259 mDividerFadeInAnimator.addUpdateListener(animation -> { 2260 if (dividerLeash == null || !dividerLeash.isValid()) { 2261 mDividerFadeInAnimator.cancel(); 2262 return; 2263 } 2264 transaction.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); 2265 transaction.setAlpha(dividerLeash, (float) animation.getAnimatedValue()); 2266 transaction.apply(); 2267 }); 2268 mDividerFadeInAnimator.addListener(new AnimatorListenerAdapter() { 2269 @Override 2270 public void onAnimationStart(Animator animation) { 2271 if (dividerLeash == null || !dividerLeash.isValid()) { 2272 mDividerFadeInAnimator.cancel(); 2273 return; 2274 } 2275 mSplitLayout.getRefDividerBounds(mTempRect1); 2276 transaction.show(dividerLeash); 2277 transaction.setAlpha(dividerLeash, 0); 2278 transaction.setLayer(dividerLeash, Integer.MAX_VALUE); 2279 transaction.setPosition(dividerLeash, mTempRect1.left, mTempRect1.top); 2280 transaction.apply(); 2281 } 2282 2283 @Override 2284 public void onAnimationEnd(Animator animation) { 2285 if (dividerLeash != null && dividerLeash.isValid()) { 2286 transaction.setAlpha(dividerLeash, 1); 2287 transaction.apply(); 2288 } 2289 mTransactionPool.release(transaction); 2290 mDividerFadeInAnimator = null; 2291 } 2292 }); 2293 2294 mDividerFadeInAnimator.start(); 2295 } else { 2296 final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); 2297 transaction.hide(dividerLeash); 2298 transaction.apply(); 2299 mTransactionPool.release(transaction); 2300 } 2301 } 2302 2303 /** Callback when split roots have child or haven't under it. 2304 * NOTICE: This only be called on legacy transition. */ onStageHasChildrenChanged(StageListenerImpl stageListener)2305 private void onStageHasChildrenChanged(StageListenerImpl stageListener) { 2306 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onStageHasChildrenChanged: isMainStage=%b", 2307 stageListener == mMainStageListener); 2308 final boolean hasChildren = stageListener.mHasChildren; 2309 final boolean isSideStage = stageListener == mSideStageListener; 2310 if (!hasChildren && !mIsExiting && mMainStage.isActive()) { 2311 if (isSideStage && mMainStageListener.mVisible) { 2312 // Exit to main stage if side stage no longer has children. 2313 mSplitLayout.flingDividerToDismiss( 2314 mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT, 2315 EXIT_REASON_APP_FINISHED); 2316 } else if (!isSideStage && mSideStageListener.mVisible) { 2317 // Exit to side stage if main stage no longer has children. 2318 mSplitLayout.flingDividerToDismiss( 2319 mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT, 2320 EXIT_REASON_APP_FINISHED); 2321 } else if (!isSplitScreenVisible() && mSplitRequest == null) { 2322 // Dismiss split screen in the background once any sides of the split become empty. 2323 exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED); 2324 } 2325 } else if (isSideStage && hasChildren && !mMainStage.isActive()) { 2326 final WindowContainerTransaction wct = new WindowContainerTransaction(); 2327 prepareEnterSplitScreen(wct); 2328 2329 mSyncQueue.queue(wct); 2330 mSyncQueue.runInSync(t -> { 2331 if (mIsDropEntering) { 2332 updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); 2333 mIsDropEntering = false; 2334 mSkipEvictingMainStageChildren = false; 2335 } else { 2336 mShowDecorImmediately = true; 2337 mSplitLayout.flingDividerToCenter(/*finishCallback*/ null); 2338 } 2339 }); 2340 } 2341 if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) { 2342 mShouldUpdateRecents = true; 2343 clearRequestIfPresented(); 2344 updateRecentTasksSplitPair(); 2345 2346 if (!mLogger.hasStartedSession()) { 2347 if (!mLogger.hasValidEnterSessionId()) { 2348 mLogger.enterRequested(null /*enterSessionId*/, ENTER_REASON_MULTI_INSTANCE); 2349 } 2350 mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(), 2351 getMainStagePosition(), mMainStage.getTopChildTaskUid(), 2352 getSideStagePosition(), mSideStage.getTopChildTaskUid(), 2353 mSplitLayout.isLeftRightSplit()); 2354 } 2355 } 2356 } 2357 2358 @Override onSnappedToDismiss(boolean bottomOrRight, @ExitReason int exitReason)2359 public void onSnappedToDismiss(boolean bottomOrRight, @ExitReason int exitReason) { 2360 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onSnappedToDismiss: bottomOrRight=%b reason=%s", 2361 bottomOrRight, exitReasonToString(exitReason)); 2362 final boolean mainStageToTop = 2363 bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT 2364 : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; 2365 final StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage; 2366 if (!ENABLE_SHELL_TRANSITIONS) { 2367 exitSplitScreen(toTopStage, exitReason); 2368 return; 2369 } 2370 2371 final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; 2372 final WindowContainerTransaction wct = new WindowContainerTransaction(); 2373 toTopStage.resetBounds(wct); 2374 prepareExitSplitScreen(dismissTop, wct); 2375 if (mRootTaskInfo != null) { 2376 wct.setDoNotPip(mRootTaskInfo.token); 2377 } 2378 mSplitTransitions.startDismissTransition(wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER); 2379 } 2380 2381 @Override onDoubleTappedDivider()2382 public void onDoubleTappedDivider() { 2383 switchSplitPosition("double tap"); 2384 } 2385 2386 @Override onLayoutPositionChanging(SplitLayout layout)2387 public void onLayoutPositionChanging(SplitLayout layout) { 2388 final SurfaceControl.Transaction t = mTransactionPool.acquire(); 2389 t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); 2390 updateSurfaceBounds(layout, t, false /* applyResizingOffset */); 2391 t.apply(); 2392 mTransactionPool.release(t); 2393 } 2394 2395 @Override onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY, boolean shouldUseParallaxEffect)2396 public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY, 2397 boolean shouldUseParallaxEffect) { 2398 final SurfaceControl.Transaction t = mTransactionPool.acquire(); 2399 t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); 2400 updateSurfaceBounds(layout, t, shouldUseParallaxEffect); 2401 getMainStageBounds(mTempRect1); 2402 getSideStageBounds(mTempRect2); 2403 // TODO (b/307490004): "commonColor" below is a temporary fix to ensure the colors on both 2404 // sides match. When b/307490004 is fixed, this code can be reverted. 2405 float[] commonColor = getResizingBackgroundColor(mSideStage.mRootTaskInfo).getComponents(); 2406 mMainStage.onResizing( 2407 mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately, commonColor); 2408 mSideStage.onResizing( 2409 mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately, commonColor); 2410 t.apply(); 2411 mTransactionPool.release(t); 2412 } 2413 2414 @Override onLayoutSizeChanged(SplitLayout layout)2415 public void onLayoutSizeChanged(SplitLayout layout) { 2416 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onLayoutSizeChanged"); 2417 // Reset this flag every time onLayoutSizeChanged. 2418 mShowDecorImmediately = false; 2419 2420 final WindowContainerTransaction wct = new WindowContainerTransaction(); 2421 boolean sizeChanged = updateWindowBounds(layout, wct); 2422 if (!sizeChanged) { 2423 // We still need to resize on decor for ensure all current status clear. 2424 final SurfaceControl.Transaction t = mTransactionPool.acquire(); 2425 mMainStage.onResized(t); 2426 mSideStage.onResized(t); 2427 mTransactionPool.release(t); 2428 return; 2429 } 2430 2431 sendOnBoundsChanged(); 2432 if (ENABLE_SHELL_TRANSITIONS) { 2433 mSplitLayout.setDividerInteractive(false, false, "onSplitResizeStart"); 2434 mSplitTransitions.startResizeTransition(wct, this, (aborted) -> { 2435 mSplitLayout.setDividerInteractive(true, false, "onSplitResizeConsumed"); 2436 }, (finishWct, t) -> { 2437 mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish"); 2438 }, mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager()); 2439 } else { 2440 // Only need screenshot for legacy case because shell transition should screenshot 2441 // itself during transition. 2442 final SurfaceControl.Transaction startT = mTransactionPool.acquire(); 2443 mMainStage.screenshotIfNeeded(startT); 2444 mSideStage.screenshotIfNeeded(startT); 2445 mTransactionPool.release(startT); 2446 2447 mSyncQueue.queue(wct); 2448 mSyncQueue.runInSync(t -> { 2449 updateSurfaceBounds(layout, t, false /* applyResizingOffset */); 2450 mMainStage.onResized(t); 2451 mSideStage.onResized(t); 2452 }); 2453 } 2454 mLogger.logResize(mSplitLayout.getDividerPositionAsFraction()); 2455 } 2456 2457 /** 2458 * @return {@code true} if we should create a left-right split, {@code false} if we should 2459 * create a top-bottom split. 2460 */ isLeftRightSplit()2461 boolean isLeftRightSplit() { 2462 return mSplitLayout != null && mSplitLayout.isLeftRightSplit(); 2463 } 2464 2465 /** 2466 * Populates `wct` with operations that match the split windows to the current layout. 2467 * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied 2468 * 2469 * @return true if stage bounds actually . 2470 */ updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct)2471 private boolean updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) { 2472 final StageTaskListener topLeftStage = 2473 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; 2474 final StageTaskListener bottomRightStage = 2475 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; 2476 boolean updated = layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, 2477 bottomRightStage.mRootTaskInfo); 2478 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "updateWindowBounds: topLeftStage=%s bottomRightStage=%s", 2479 layout.getBounds1(), layout.getBounds2()); 2480 return updated; 2481 } 2482 updateSurfaceBounds(@ullable SplitLayout layout, @NonNull SurfaceControl.Transaction t, boolean applyResizingOffset)2483 void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t, 2484 boolean applyResizingOffset) { 2485 final StageTaskListener topLeftStage = 2486 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; 2487 final StageTaskListener bottomRightStage = 2488 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; 2489 (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash, 2490 bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer, 2491 applyResizingOffset); 2492 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 2493 "updateSurfaceBounds: topLeftStage=%s bottomRightStage=%s", 2494 layout.getBounds1(), layout.getBounds2()); 2495 } 2496 2497 @Override getSplitItemPosition(WindowContainerToken token)2498 public int getSplitItemPosition(WindowContainerToken token) { 2499 if (token == null) { 2500 return SPLIT_POSITION_UNDEFINED; 2501 } 2502 2503 if (mMainStage.containsToken(token)) { 2504 return getMainStagePosition(); 2505 } else if (mSideStage.containsToken(token)) { 2506 return getSideStagePosition(); 2507 } 2508 2509 return SPLIT_POSITION_UNDEFINED; 2510 } 2511 2512 /** 2513 * Returns the {@link StageType} where {@param token} is being used 2514 * {@link SplitScreen#STAGE_TYPE_UNDEFINED} otherwise 2515 */ 2516 @StageType getSplitItemStage(@ullable WindowContainerToken token)2517 public int getSplitItemStage(@Nullable WindowContainerToken token) { 2518 if (token == null) { 2519 return STAGE_TYPE_UNDEFINED; 2520 } 2521 2522 if (mMainStage.containsToken(token)) { 2523 return STAGE_TYPE_MAIN; 2524 } else if (mSideStage.containsToken(token)) { 2525 return STAGE_TYPE_SIDE; 2526 } 2527 2528 return STAGE_TYPE_UNDEFINED; 2529 } 2530 2531 @Override setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout)2532 public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) { 2533 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setLayoutOffsetTarget: x=%d y=%d", 2534 offsetX, offsetY); 2535 final StageTaskListener topLeftStage = 2536 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; 2537 final StageTaskListener bottomRightStage = 2538 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; 2539 final WindowContainerTransaction wct = new WindowContainerTransaction(); 2540 layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo, 2541 bottomRightStage.mRootTaskInfo); 2542 mTaskOrganizer.applyTransaction(wct); 2543 } 2544 onDisplayAdded(int displayId)2545 public void onDisplayAdded(int displayId) { 2546 if (displayId != DEFAULT_DISPLAY) { 2547 return; 2548 } 2549 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onDisplayAdded: display=%d", displayId); 2550 mDisplayController.addDisplayChangingController(this::onDisplayChange); 2551 } 2552 2553 /** 2554 * Update surfaces of the split screen layout based on the current state 2555 * @param transaction to write the updates to 2556 */ updateSurfaces(SurfaceControl.Transaction transaction)2557 public void updateSurfaces(SurfaceControl.Transaction transaction) { 2558 updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false); 2559 mSplitLayout.update(transaction, true /* resetImePosition */); 2560 } 2561 onDisplayChange(int displayId, int fromRotation, int toRotation, @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct)2562 private void onDisplayChange(int displayId, int fromRotation, int toRotation, 2563 @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) { 2564 if (displayId != DEFAULT_DISPLAY || !mMainStage.isActive()) { 2565 return; 2566 } 2567 2568 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 2569 "onDisplayChange: display=%d fromRot=%d toRot=%d config=%s", 2570 displayId, fromRotation, toRotation, 2571 newDisplayAreaInfo != null ? newDisplayAreaInfo.configuration : null); 2572 mSplitLayout.rotateTo(toRotation); 2573 if (newDisplayAreaInfo != null) { 2574 mSplitLayout.updateConfiguration(newDisplayAreaInfo.configuration); 2575 } 2576 updateWindowBounds(mSplitLayout, wct); 2577 sendOnBoundsChanged(); 2578 } 2579 2580 @VisibleForTesting onFoldedStateChanged(boolean folded)2581 void onFoldedStateChanged(boolean folded) { 2582 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFoldedStateChanged: folded=%b", folded); 2583 mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; 2584 if (!folded) return; 2585 2586 if (!isSplitActive() || !isSplitScreenVisible()) return; 2587 2588 // To avoid split dismiss when user fold the device and unfold to use later, we only 2589 // record the flag here and try to dismiss on wakeUp callback to ensure split dismiss 2590 // when user interact on phone folded. 2591 if (mMainStage.isFocused()) { 2592 mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN; 2593 } else if (mSideStage.isFocused()) { 2594 mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE; 2595 } 2596 } 2597 getSideStageBounds()2598 private Rect getSideStageBounds() { 2599 return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT 2600 ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2(); 2601 } 2602 getMainStageBounds()2603 private Rect getMainStageBounds() { 2604 return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT 2605 ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1(); 2606 } 2607 getSideStageBounds(Rect rect)2608 private void getSideStageBounds(Rect rect) { 2609 if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) { 2610 mSplitLayout.getBounds1(rect); 2611 } else { 2612 mSplitLayout.getBounds2(rect); 2613 } 2614 } 2615 getMainStageBounds(Rect rect)2616 private void getMainStageBounds(Rect rect) { 2617 if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) { 2618 mSplitLayout.getBounds2(rect); 2619 } else { 2620 mSplitLayout.getBounds1(rect); 2621 } 2622 } 2623 2624 /** 2625 * Get the stage that should contain this `taskInfo`. The stage doesn't necessarily contain 2626 * this task (yet) so this can also be used to identify which stage to put a task into. 2627 */ getStageOfTask(ActivityManager.RunningTaskInfo taskInfo)2628 private StageTaskListener getStageOfTask(ActivityManager.RunningTaskInfo taskInfo) { 2629 // TODO(b/184679596): Find a way to either include task-org information in the transition, 2630 // or synchronize task-org callbacks so we can use stage.containsTask 2631 if (mMainStage.mRootTaskInfo != null 2632 && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) { 2633 return mMainStage; 2634 } else if (mSideStage.mRootTaskInfo != null 2635 && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) { 2636 return mSideStage; 2637 } 2638 return null; 2639 } 2640 2641 @StageType getStageType(StageTaskListener stage)2642 private int getStageType(StageTaskListener stage) { 2643 if (stage == null) return STAGE_TYPE_UNDEFINED; 2644 return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; 2645 } 2646 2647 @Override handleRequest(@onNull IBinder transition, @Nullable TransitionRequestInfo request)2648 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 2649 @Nullable TransitionRequestInfo request) { 2650 final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); 2651 if (triggerTask == null) { 2652 if (isSplitActive()) { 2653 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d display rotation", 2654 request.getDebugId()); 2655 // Check if the display is rotating. 2656 final TransitionRequestInfo.DisplayChange displayChange = 2657 request.getDisplayChange(); 2658 if (request.getType() == TRANSIT_CHANGE && displayChange != null 2659 && displayChange.getStartRotation() != displayChange.getEndRotation()) { 2660 mSplitLayout.setFreezeDividerWindow(true); 2661 } 2662 if (request.getRemoteTransition() != null) { 2663 mSplitTransitions.setRemotePassThroughTransition(transition, 2664 request.getRemoteTransition()); 2665 } 2666 // Still want to monitor everything while in split-screen, so return non-null. 2667 return new WindowContainerTransaction(); 2668 } else { 2669 return null; 2670 } 2671 } else if (triggerTask.displayId != mDisplayId) { 2672 // Skip handling task on the other display. 2673 return null; 2674 } 2675 2676 WindowContainerTransaction out = null; 2677 final @WindowManager.TransitionType int type = request.getType(); 2678 final boolean isOpening = isOpeningType(type); 2679 final boolean inFullscreen = triggerTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN; 2680 2681 if (isOpening && inFullscreen) { 2682 // One task is opening into fullscreen mode, remove the corresponding split record. 2683 mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); 2684 } 2685 2686 if (isSplitActive()) { 2687 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d split active", 2688 request.getDebugId()); 2689 // Try to handle everything while in split-screen, so return a WCT even if it's empty. 2690 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split" 2691 + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d" 2692 + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type), 2693 mMainStage.getChildCount(), mSideStage.getChildCount()); 2694 out = new WindowContainerTransaction(); 2695 final StageTaskListener stage = getStageOfTask(triggerTask); 2696 if (stage != null) { 2697 if (isClosingType(type) && stage.getChildCount() == 1) { 2698 // Dismiss split if the last task in one of the stages is going away 2699 // The top should be the opposite side that is closing: 2700 int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN 2701 ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; 2702 prepareExitSplitScreen(dismissTop, out); 2703 mSplitTransitions.setDismissTransition(transition, dismissTop, 2704 EXIT_REASON_APP_FINISHED); 2705 } else if (isOpening && !mPausingTasks.isEmpty()) { 2706 // One of the splitting task is opening while animating the split pair in 2707 // recents, which means to dismiss the split pair to this task. 2708 int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN 2709 ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; 2710 prepareExitSplitScreen(dismissTop, out); 2711 mSplitTransitions.setDismissTransition(transition, dismissTop, 2712 EXIT_REASON_APP_FINISHED); 2713 } else if (!isSplitScreenVisible() && isOpening) { 2714 // If split is running in the background and the trigger task is appearing into 2715 // split, prepare to enter split screen. 2716 prepareEnterSplitScreen(out); 2717 mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), 2718 TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering); 2719 } else if (inFullscreen && isSplitScreenVisible()) { 2720 // If the trigger task is in fullscreen and in split, exit split and place 2721 // task on top 2722 final int stageType = getStageOfTask(triggerTask.taskId); 2723 prepareExitSplitScreen(stageType, out); 2724 mSplitTransitions.setDismissTransition(transition, stageType, 2725 EXIT_REASON_FULLSCREEN_REQUEST); 2726 } 2727 } else if (isOpening && inFullscreen) { 2728 final int activityType = triggerTask.getActivityType(); 2729 if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { 2730 // starting recents/home, so don't handle this and let it fall-through to 2731 // the remote handler. 2732 return null; 2733 } 2734 2735 if ((mMainStage.containsTask(triggerTask.taskId) 2736 && mMainStage.getChildCount() == 1) 2737 || (mSideStage.containsTask(triggerTask.taskId) 2738 && mSideStage.getChildCount() == 1)) { 2739 // A splitting task is opening to fullscreen causes one side of the split empty, 2740 // so appends operations to exit split. 2741 prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); 2742 } 2743 } else if (type == TRANSIT_KEYGUARD_OCCLUDE && triggerTask.topActivity != null 2744 && isSplitScreenVisible()) { 2745 // Split include show when lock activity case, check the top activity under which 2746 // stage and move it to the top. 2747 int top = triggerTask.topActivity.equals(mMainStage.mRootTaskInfo.topActivity) 2748 ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; 2749 prepareExitSplitScreen(top, out); 2750 mSplitTransitions.setDismissTransition(transition, top, 2751 EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); 2752 } 2753 2754 if (!out.isEmpty()) { 2755 // One of the cases above handled it 2756 return out; 2757 } else if (isSplitScreenVisible()) { 2758 // If split is visible, only defer handling this transition if it's launching 2759 // adjacent while there is already a split pair -- this may trigger PIP and 2760 // that should be handled by the mixed handler. 2761 final boolean deferTransition = requestHasLaunchAdjacentFlag(request) 2762 && mMainStage.getChildCount() != 0 && mSideStage.getChildCount() != 0; 2763 return !deferTransition ? out : null; 2764 } 2765 // Don't intercept the transition if we are not handling it as a part of one of the 2766 // cases above and it is not already visible 2767 return null; 2768 } else { 2769 if (triggerTask.parentTaskId == mMainStage.mRootTaskInfo.taskId 2770 || triggerTask.parentTaskId == mSideStage.mRootTaskInfo.taskId) { 2771 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d " 2772 + "restoring to split", request.getDebugId()); 2773 out = new WindowContainerTransaction(); 2774 mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), 2775 TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, false /* resizeAnim */); 2776 } 2777 if (isOpening && getStageOfTask(triggerTask) != null) { 2778 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d enter split", 2779 request.getDebugId()); 2780 // One task is appearing into split, prepare to enter split screen. 2781 out = new WindowContainerTransaction(); 2782 prepareEnterSplitScreen(out); 2783 mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), 2784 TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering); 2785 } 2786 return out; 2787 } 2788 } 2789 2790 /** 2791 * This is used for mixed scenarios. For such scenarios, just make sure to include exiting 2792 * split or entering split when appropriate. 2793 */ addEnterOrExitIfNeeded(@ullable TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT)2794 public void addEnterOrExitIfNeeded(@Nullable TransitionRequestInfo request, 2795 @NonNull WindowContainerTransaction outWCT) { 2796 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "addEnterOrExitIfNeeded: transition=%d", 2797 request.getDebugId()); 2798 final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); 2799 if (triggerTask != null && triggerTask.displayId != mDisplayId) { 2800 // Skip handling task on the other display. 2801 return; 2802 } 2803 final @WindowManager.TransitionType int type = request.getType(); 2804 if (isSplitActive() && !isOpeningType(type) 2805 && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) { 2806 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " One of the splits became " 2807 + "empty during a mixed transition (one not handled by split)," 2808 + " so make sure split-screen state is cleaned-up. " 2809 + "mainStageCount=%d sideStageCount=%d", mMainStage.getChildCount(), 2810 mSideStage.getChildCount()); 2811 if (triggerTask != null) { 2812 mRecentTasks.ifPresent( 2813 recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); 2814 } 2815 @StageType int topStage = STAGE_TYPE_UNDEFINED; 2816 if (isSplitScreenVisible()) { 2817 // Get the stage where a child exists to keep that stage onTop 2818 if (mMainStage.getChildCount() != 0 && mSideStage.getChildCount() == 0) { 2819 topStage = STAGE_TYPE_MAIN; 2820 } else if (mSideStage.getChildCount() != 0 && mMainStage.getChildCount() == 0) { 2821 topStage = STAGE_TYPE_SIDE; 2822 } 2823 } 2824 prepareExitSplitScreen(topStage, outWCT); 2825 } 2826 } 2827 2828 @Override mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback)2829 public void mergeAnimation(IBinder transition, TransitionInfo info, 2830 SurfaceControl.Transaction t, IBinder mergeTarget, 2831 Transitions.TransitionFinishCallback finishCallback) { 2832 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "mergeAnimation: transition=%d", info.getDebugId()); 2833 mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback); 2834 } 2835 2836 /** Jump the current transition animation to the end. */ end()2837 public boolean end() { 2838 return mSplitTransitions.end(); 2839 } 2840 2841 @Override onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)2842 public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, 2843 @Nullable SurfaceControl.Transaction finishT) { 2844 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed"); 2845 mSplitTransitions.onTransitionConsumed(transition, aborted, finishT); 2846 } 2847 2848 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)2849 public boolean startAnimation(@NonNull IBinder transition, 2850 @NonNull TransitionInfo info, 2851 @NonNull SurfaceControl.Transaction startTransaction, 2852 @NonNull SurfaceControl.Transaction finishTransaction, 2853 @NonNull Transitions.TransitionFinishCallback finishCallback) { 2854 if (!mSplitTransitions.isPendingTransition(transition)) { 2855 // Not entering or exiting, so just do some house-keeping and validation. 2856 2857 // If we're not in split-mode, just abort so something else can handle it. 2858 if (!mMainStage.isActive()) return false; 2859 2860 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startAnimation: transition=%d", info.getDebugId()); 2861 mSplitLayout.setFreezeDividerWindow(false); 2862 final StageChangeRecord record = new StageChangeRecord(); 2863 final int transitType = info.getType(); 2864 TransitionInfo.Change pipChange = null; 2865 for (int iC = 0; iC < info.getChanges().size(); ++iC) { 2866 final TransitionInfo.Change change = info.getChanges().get(iC); 2867 if (change.getMode() == TRANSIT_CHANGE 2868 && (change.getFlags() & FLAG_IS_DISPLAY) != 0) { 2869 // Don't reset the IME state since those updates are not in sync with the 2870 // display change transition 2871 mSplitLayout.update(startTransaction, false /* resetImePosition */); 2872 } 2873 2874 if (mMixedHandler.isEnteringPip(change, transitType)) { 2875 pipChange = change; 2876 } 2877 2878 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 2879 if (taskInfo == null) continue; 2880 if (taskInfo.token.equals(mRootTaskInfo.token)) { 2881 if (isOpeningType(change.getMode())) { 2882 // Split is opened by someone so set it as visible. 2883 setSplitsVisible(true); 2884 // TODO(b/275664132): Find a way to integrate this with finishWct. 2885 // This is setting the flag to a task and not interfering with the 2886 // transition. 2887 final WindowContainerTransaction wct = new WindowContainerTransaction(); 2888 wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, 2889 false /* reparentLeafTaskIfRelaunch */); 2890 mTaskOrganizer.applyTransaction(wct); 2891 } else if (isClosingType(change.getMode())) { 2892 // Split is closed by someone so set it as invisible. 2893 setSplitsVisible(false); 2894 // TODO(b/275664132): Find a way to integrate this with finishWct. 2895 // This is setting the flag to a task and not interfering with the 2896 // transition. 2897 final WindowContainerTransaction wct = new WindowContainerTransaction(); 2898 wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, 2899 true /* reparentLeafTaskIfRelaunch */); 2900 mTaskOrganizer.applyTransaction(wct); 2901 } 2902 continue; 2903 } 2904 final StageTaskListener stage = getStageOfTask(taskInfo); 2905 if (stage == null) { 2906 if (change.getParent() == null && !isClosingType(change.getMode()) 2907 && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { 2908 record.mContainShowFullscreenChange = true; 2909 } 2910 continue; 2911 } 2912 if (isOpeningType(change.getMode())) { 2913 if (!stage.containsTask(taskInfo.taskId)) { 2914 Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called" 2915 + " with " + taskInfo.taskId + " before startAnimation()."); 2916 record.addRecord(stage, true, taskInfo.taskId); 2917 } 2918 } else if (change.getMode() == TRANSIT_CLOSE) { 2919 if (stage.containsTask(taskInfo.taskId)) { 2920 record.addRecord(stage, false, taskInfo.taskId); 2921 Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called" 2922 + " with " + taskInfo.taskId + " before startAnimation()."); 2923 } 2924 } 2925 } 2926 2927 if (pipChange != null) { 2928 TransitionInfo.Change pipReplacingChange = getPipReplacingChange(info, pipChange, 2929 mMainStage.mRootTaskInfo.taskId, mSideStage.mRootTaskInfo.taskId, 2930 getSplitItemStage(pipChange.getLastParent())); 2931 if (pipReplacingChange != null) { 2932 // Set an enter transition for when startAnimation gets called again 2933 mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null, 2934 TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false); 2935 } 2936 2937 mMixedHandler.animatePendingEnterPipFromSplit(transition, info, 2938 startTransaction, finishTransaction, finishCallback, 2939 pipReplacingChange != null); 2940 notifySplitAnimationFinished(); 2941 return true; 2942 } 2943 2944 final ArraySet<StageTaskListener> dismissStages = record.getShouldDismissedStage(); 2945 if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0 2946 || dismissStages.size() == 1) { 2947 // If the size of dismissStages == 1, one of the task is closed without prepare 2948 // pending transition, which could happen if all activities were finished after 2949 // finish top activity in a task, so the trigger task is null when handleRequest. 2950 // Note if the size of dismissStages == 2, it's starting a new task, 2951 // so don't handle it. 2952 Log.e(TAG, "Somehow removed the last task in a stage outside of a proper " 2953 + "transition."); 2954 // This new transition would be merged to current one so we need to clear 2955 // tile manually here. 2956 clearSplitPairedInRecents(EXIT_REASON_APP_FINISHED); 2957 final WindowContainerTransaction wct = new WindowContainerTransaction(); 2958 final int dismissTop = (dismissStages.size() == 1 2959 && getStageType(dismissStages.valueAt(0)) == STAGE_TYPE_MAIN) 2960 || mMainStage.getChildCount() == 0 ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; 2961 // If there is a fullscreen opening change, we should not bring stage to top. 2962 prepareExitSplitScreen( 2963 !record.mContainShowFullscreenChange && isSplitScreenVisible() 2964 ? dismissTop : STAGE_TYPE_UNDEFINED, wct); 2965 mSplitTransitions.startDismissTransition(wct, this, dismissTop, 2966 EXIT_REASON_APP_FINISHED); 2967 // This can happen in some pathological cases. For example: 2968 // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C] 2969 // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time 2970 // In this case, the result *should* be that we leave split. 2971 // TODO(b/184679596): Find a way to either include task-org information in 2972 // the transition, or synchronize task-org callbacks. 2973 } 2974 // Use normal animations. 2975 notifySplitAnimationFinished(); 2976 return false; 2977 } else if (mMixedHandler != null && TransitionUtil.hasDisplayChange(info)) { 2978 // A display-change has been un-expectedly inserted into the transition. Redirect 2979 // handling to the mixed-handler to deal with splitting it up. 2980 if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info, 2981 startTransaction, finishTransaction, finishCallback)) { 2982 if (mSplitTransitions.isPendingResize(transition)) { 2983 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 2984 "startAnimation: transition=%d display change", info.getDebugId()); 2985 // Only need to update in resize because divider exist before transition. 2986 mSplitLayout.update(startTransaction, true /* resetImePosition */); 2987 startTransaction.apply(); 2988 } 2989 notifySplitAnimationFinished(); 2990 return true; 2991 } 2992 } else if (mSplitTransitions.isPendingPassThrough(transition)) { 2993 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 2994 "startAnimation: passThrough transition=%d", info.getDebugId()); 2995 mSplitTransitions.mPendingRemotePassthrough.mRemoteHandler.startAnimation(transition, 2996 info, startTransaction, finishTransaction, finishCallback); 2997 notifySplitAnimationFinished(); 2998 return true; 2999 } 3000 3001 return startPendingAnimation(transition, info, startTransaction, finishTransaction, 3002 finishCallback); 3003 } 3004 3005 static class StageChangeRecord { 3006 boolean mContainShowFullscreenChange = false; 3007 static class StageChange { 3008 final StageTaskListener mStageTaskListener; 3009 final IntArray mAddedTaskId = new IntArray(); 3010 final IntArray mRemovedTaskId = new IntArray(); StageChange(StageTaskListener stage)3011 StageChange(StageTaskListener stage) { 3012 mStageTaskListener = stage; 3013 } 3014 shouldDismissStage()3015 boolean shouldDismissStage() { 3016 if (mAddedTaskId.size() > 0 || mRemovedTaskId.size() == 0) { 3017 return false; 3018 } 3019 int removeChildTaskCount = 0; 3020 for (int i = mRemovedTaskId.size() - 1; i >= 0; --i) { 3021 if (mStageTaskListener.containsTask(mRemovedTaskId.get(i))) { 3022 ++removeChildTaskCount; 3023 } 3024 } 3025 return removeChildTaskCount == mStageTaskListener.getChildCount(); 3026 } 3027 } 3028 private final ArrayMap<StageTaskListener, StageChange> mChanges = new ArrayMap<>(); 3029 addRecord(StageTaskListener stage, boolean open, int taskId)3030 void addRecord(StageTaskListener stage, boolean open, int taskId) { 3031 final StageChange next; 3032 if (!mChanges.containsKey(stage)) { 3033 next = new StageChange(stage); 3034 mChanges.put(stage, next); 3035 } else { 3036 next = mChanges.get(stage); 3037 } 3038 if (open) { 3039 next.mAddedTaskId.add(taskId); 3040 } else { 3041 next.mRemovedTaskId.add(taskId); 3042 } 3043 } 3044 getShouldDismissedStage()3045 ArraySet<StageTaskListener> getShouldDismissedStage() { 3046 final ArraySet<StageTaskListener> dismissTarget = new ArraySet<>(); 3047 for (int i = mChanges.size() - 1; i >= 0; --i) { 3048 final StageChange change = mChanges.valueAt(i); 3049 if (change.shouldDismissStage()) { 3050 dismissTarget.add(change.mStageTaskListener); 3051 } 3052 } 3053 return dismissTarget; 3054 } 3055 } 3056 3057 /** Starts the pending transition animation. */ startPendingAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)3058 public boolean startPendingAnimation(@NonNull IBinder transition, 3059 @NonNull TransitionInfo info, 3060 @NonNull SurfaceControl.Transaction startTransaction, 3061 @NonNull SurfaceControl.Transaction finishTransaction, 3062 @NonNull Transitions.TransitionFinishCallback finishCallback) { 3063 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startPendingAnimation: transition=%d", 3064 info.getDebugId()); 3065 boolean shouldAnimate = true; 3066 if (mSplitTransitions.isPendingEnter(transition)) { 3067 shouldAnimate = startPendingEnterAnimation(transition, 3068 mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction); 3069 } else if (mSplitTransitions.isPendingDismiss(transition)) { 3070 final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss; 3071 shouldAnimate = startPendingDismissAnimation( 3072 dismiss, info, startTransaction, finishTransaction); 3073 if (shouldAnimate && dismiss.mReason == EXIT_REASON_DRAG_DIVIDER) { 3074 final StageTaskListener toTopStage = 3075 dismiss.mDismissTop == STAGE_TYPE_MAIN ? mMainStage : mSideStage; 3076 mSplitTransitions.playDragDismissAnimation(transition, info, startTransaction, 3077 finishTransaction, finishCallback, toTopStage.mRootTaskInfo.token, 3078 toTopStage.getSplitDecorManager(), mRootTaskInfo.token); 3079 return true; 3080 } 3081 } else if (mSplitTransitions.isPendingResize(transition)) { 3082 mSplitTransitions.playResizeAnimation(transition, info, startTransaction, 3083 finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token, 3084 mSideStage.mRootTaskInfo.token, mMainStage.getSplitDecorManager(), 3085 mSideStage.getSplitDecorManager()); 3086 return true; 3087 } 3088 if (!shouldAnimate) return false; 3089 3090 mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction, 3091 finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token, 3092 mRootTaskInfo.token); 3093 return true; 3094 } 3095 3096 /** Called to clean-up state and do house-keeping after the animation is done. */ onTransitionAnimationComplete()3097 public void onTransitionAnimationComplete() { 3098 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionAnimationComplete"); 3099 // If still playing, let it finish. 3100 if (!mMainStage.isActive() && !mIsExiting) { 3101 // Update divider state after animation so that it is still around and positioned 3102 // properly for the animation itself. 3103 mSplitLayout.release(); 3104 } 3105 } 3106 startPendingEnterAnimation(@onNull IBinder transition, @NonNull SplitScreenTransitions.EnterSession enterTransition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)3107 private boolean startPendingEnterAnimation(@NonNull IBinder transition, 3108 @NonNull SplitScreenTransitions.EnterSession enterTransition, 3109 @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, 3110 @NonNull SurfaceControl.Transaction finishT) { 3111 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startPendingEnterAnimation: enterTransition=%s", 3112 enterTransition); 3113 // First, verify that we actually have opened apps in both splits. 3114 TransitionInfo.Change mainChild = null; 3115 TransitionInfo.Change sideChild = null; 3116 final WindowContainerTransaction evictWct = new WindowContainerTransaction(); 3117 for (int iC = 0; iC < info.getChanges().size(); ++iC) { 3118 final TransitionInfo.Change change = info.getChanges().get(iC); 3119 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 3120 if (taskInfo == null || !taskInfo.hasParentTask()) continue; 3121 if (mPausingTasks.contains(taskInfo.taskId)) { 3122 continue; 3123 } 3124 final @StageType int stageType = getStageType(getStageOfTask(taskInfo)); 3125 if (mainChild == null && stageType == STAGE_TYPE_MAIN 3126 && (isOpeningType(change.getMode()) || change.getMode() == TRANSIT_CHANGE)) { 3127 // Includes TRANSIT_CHANGE to cover reparenting top-most task to split. 3128 mainChild = change; 3129 } else if (sideChild == null && stageType == STAGE_TYPE_SIDE 3130 && (isOpeningType(change.getMode()) || change.getMode() == TRANSIT_CHANGE)) { 3131 sideChild = change; 3132 } else if (stageType != STAGE_TYPE_UNDEFINED && change.getMode() == TRANSIT_TO_BACK) { 3133 // Collect all to back task's and evict them when transition finished. 3134 evictWct.reparent(taskInfo.token, null /* parent */, false /* onTop */); 3135 } 3136 } 3137 3138 SplitScreenTransitions.EnterSession pendingEnter = mSplitTransitions.mPendingEnter; 3139 if (pendingEnter.mExtraTransitType 3140 == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) { 3141 // Open to side should only be used when split already active and foregorund or when 3142 // app is restoring to split from fullscreen. 3143 if (mainChild == null && sideChild == null) { 3144 Log.w(TAG, splitFailureMessage("startPendingEnterAnimation", 3145 "Launched a task in split, but didn't receive any task in transition.")); 3146 // This should happen when the target app is already on front, so just cancel. 3147 pendingEnter.cancel(null); 3148 return true; 3149 } 3150 } else { 3151 if (mainChild == null || sideChild == null) { 3152 final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN : 3153 (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED); 3154 pendingEnter.cancel( 3155 (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct)); 3156 Log.w(TAG, splitFailureMessage("startPendingEnterAnimation", 3157 "launched 2 tasks in split, but didn't receive " 3158 + "2 tasks in transition. Possibly one of them failed to launch")); 3159 if (mRecentTasks.isPresent() && mainChild != null) { 3160 mRecentTasks.get().removeSplitPair(mainChild.getTaskInfo().taskId); 3161 } 3162 if (mRecentTasks.isPresent() && sideChild != null) { 3163 mRecentTasks.get().removeSplitPair(sideChild.getTaskInfo().taskId); 3164 } 3165 if (pendingEnter.mRemoteHandler != null) { 3166 // Pass false for aborted since WM didn't abort, business logic chose to 3167 // terminate/exit early 3168 pendingEnter.mRemoteHandler.onTransitionConsumed(transition, 3169 false /*aborted*/, finishT); 3170 } 3171 handleUnsupportedSplitStart(); 3172 return true; 3173 } 3174 } 3175 3176 // Make some noise if things aren't totally expected. These states shouldn't effect 3177 // transitions locally, but remotes (like Launcher) may get confused if they were 3178 // depending on listener callbacks. This can happen because task-organizer callbacks 3179 // aren't serialized with transition callbacks. 3180 // This usually occurred on app use trampoline launch new task and finish itself. 3181 // TODO(b/184679596): Find a way to either include task-org information in 3182 // the transition, or synchronize task-org callbacks. 3183 final boolean mainNotContainOpenTask = 3184 mainChild != null && !mMainStage.containsTask(mainChild.getTaskInfo().taskId); 3185 final boolean sideNotContainOpenTask = 3186 sideChild != null && !mSideStage.containsTask(sideChild.getTaskInfo().taskId); 3187 if (mainNotContainOpenTask) { 3188 Log.w(TAG, "Expected onTaskAppeared on " + mMainStage 3189 + " to have been called with " + mainChild.getTaskInfo().taskId 3190 + " before startAnimation()."); 3191 } 3192 if (sideNotContainOpenTask) { 3193 Log.w(TAG, "Expected onTaskAppeared on " + mSideStage 3194 + " to have been called with " + sideChild.getTaskInfo().taskId 3195 + " before startAnimation()."); 3196 } 3197 final TransitionInfo.Change finalMainChild = mainChild; 3198 final TransitionInfo.Change finalSideChild = sideChild; 3199 enterTransition.setFinishedCallback((callbackWct, callbackT) -> { 3200 if (!enterTransition.mResizeAnim) { 3201 // If resizing, we'll call notify at the end of the resizing animation (below) 3202 notifySplitAnimationFinished(); 3203 } 3204 if (finalMainChild != null) { 3205 if (!mainNotContainOpenTask) { 3206 mMainStage.evictOtherChildren(callbackWct, finalMainChild.getTaskInfo().taskId); 3207 } else { 3208 mMainStage.evictInvisibleChildren(callbackWct); 3209 } 3210 } 3211 if (finalSideChild != null) { 3212 if (!sideNotContainOpenTask) { 3213 mSideStage.evictOtherChildren(callbackWct, finalSideChild.getTaskInfo().taskId); 3214 } else { 3215 mSideStage.evictInvisibleChildren(callbackWct); 3216 } 3217 } 3218 if (!evictWct.isEmpty()) { 3219 callbackWct.merge(evictWct, true); 3220 } 3221 if (enterTransition.mResizeAnim) { 3222 mShowDecorImmediately = true; 3223 mSplitLayout.flingDividerToCenter(this::notifySplitAnimationFinished); 3224 } 3225 callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false); 3226 mPausingTasks.clear(); 3227 }); 3228 3229 if (info.getType() == TRANSIT_CHANGE && !isSplitActive() 3230 && pendingEnter.mExtraTransitType == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) { 3231 if (finalMainChild != null && finalSideChild == null) { 3232 requestEnterSplitSelect(finalMainChild.getTaskInfo(), 3233 new WindowContainerTransaction(), 3234 getMainStagePosition(), finalMainChild.getStartAbsBounds()); 3235 } else if (finalSideChild != null && finalMainChild == null) { 3236 requestEnterSplitSelect(finalSideChild.getTaskInfo(), 3237 new WindowContainerTransaction(), 3238 getSideStagePosition(), finalSideChild.getStartAbsBounds()); 3239 } else { 3240 throw new IllegalStateException( 3241 "Attempting to restore to split but reparenting change not found"); 3242 } 3243 } 3244 3245 finishEnterSplitScreen(finishT); 3246 addDividerBarToTransition(info, true /* show */); 3247 return true; 3248 } 3249 goToFullscreenFromSplit()3250 public void goToFullscreenFromSplit() { 3251 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "goToFullscreenFromSplit"); 3252 // If main stage is focused, toEnd = true if 3253 // mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT. Otherwise toEnd = false 3254 // If side stage is focused, toEnd = true if 3255 // mSideStagePosition = SPLIT_POSITION_TOP_OR_LEFT. Otherwise toEnd = false 3256 final boolean toEnd; 3257 if (mMainStage.isFocused()) { 3258 toEnd = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT); 3259 } else { 3260 toEnd = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT); 3261 } 3262 mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_FULLSCREEN_SHORTCUT); 3263 } 3264 3265 /** Move the specified task to fullscreen, regardless of focus state. */ moveTaskToFullscreen(int taskId, int exitReason)3266 public void moveTaskToFullscreen(int taskId, int exitReason) { 3267 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "moveTaskToFullscreen"); 3268 boolean leftOrTop; 3269 if (mMainStage.containsTask(taskId)) { 3270 leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT); 3271 } else if (mSideStage.containsTask(taskId)) { 3272 leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT); 3273 } else { 3274 return; 3275 } 3276 mSplitLayout.flingDividerToDismiss(!leftOrTop, exitReason); 3277 3278 } 3279 3280 /** 3281 * Performs previous child eviction and such to prepare for the pip task expending into one of 3282 * the split stages 3283 * 3284 * @param taskInfo TaskInfo of the pip task 3285 */ onPipExpandToSplit(WindowContainerTransaction wct, ActivityManager.RunningTaskInfo taskInfo)3286 public void onPipExpandToSplit(WindowContainerTransaction wct, 3287 ActivityManager.RunningTaskInfo taskInfo) { 3288 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onPipExpandToSplit: task=%s", taskInfo); 3289 prepareEnterSplitScreen(wct, taskInfo, getActivateSplitPosition(taskInfo), 3290 false /*resizeAnim*/); 3291 3292 if (!isSplitScreenVisible() || mSplitRequest == null) { 3293 return; 3294 } 3295 3296 boolean replacingMainStage = getMainStagePosition() == mSplitRequest.mActivatePosition; 3297 (replacingMainStage ? mMainStage : mSideStage).evictOtherChildren(wct, taskInfo.taskId); 3298 } 3299 isLaunchToSplit(TaskInfo taskInfo)3300 boolean isLaunchToSplit(TaskInfo taskInfo) { 3301 return getActivateSplitPosition(taskInfo) != SPLIT_POSITION_UNDEFINED; 3302 } 3303 getActivateSplitPosition(TaskInfo taskInfo)3304 int getActivateSplitPosition(TaskInfo taskInfo) { 3305 if (mSplitRequest == null || taskInfo == null) { 3306 return SPLIT_POSITION_UNDEFINED; 3307 } 3308 if (mSplitRequest.mActivateTaskId != 0 3309 && mSplitRequest.mActivateTaskId2 == taskInfo.taskId) { 3310 return mSplitRequest.mActivatePosition; 3311 } 3312 if (mSplitRequest.mActivateTaskId == taskInfo.taskId) { 3313 return mSplitRequest.mActivatePosition; 3314 } 3315 final String packageName1 = SplitScreenUtils.getPackageName(mSplitRequest.mStartIntent); 3316 final String basePackageName = SplitScreenUtils.getPackageName(taskInfo.baseIntent); 3317 if (packageName1 != null && packageName1.equals(basePackageName)) { 3318 return mSplitRequest.mActivatePosition; 3319 } 3320 final String packageName2 = SplitScreenUtils.getPackageName(mSplitRequest.mStartIntent2); 3321 if (packageName2 != null && packageName2.equals(basePackageName)) { 3322 return mSplitRequest.mActivatePosition; 3323 } 3324 return SPLIT_POSITION_UNDEFINED; 3325 } 3326 3327 /** 3328 * Synchronize split-screen state with transition and make appropriate preparations. 3329 * @param toStage The stage that will not be dismissed. If set to 3330 * {@link SplitScreen#STAGE_TYPE_UNDEFINED} then both stages will be dismissed 3331 */ prepareDismissAnimation(@tageType int toStage, @ExitReason int dismissReason, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)3332 public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason, 3333 @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, 3334 @NonNull SurfaceControl.Transaction finishT) { 3335 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 3336 "prepareDismissAnimation: transition=%d toStage=%d reason=%s", 3337 info.getDebugId(), toStage, exitReasonToString(dismissReason)); 3338 // Make some noise if things aren't totally expected. These states shouldn't effect 3339 // transitions locally, but remotes (like Launcher) may get confused if they were 3340 // depending on listener callbacks. This can happen because task-organizer callbacks 3341 // aren't serialized with transition callbacks. 3342 // TODO(b/184679596): Find a way to either include task-org information in 3343 // the transition, or synchronize task-org callbacks. 3344 if (toStage == STAGE_TYPE_UNDEFINED) { 3345 if (mMainStage.getChildCount() != 0) { 3346 final StringBuilder tasksLeft = new StringBuilder(); 3347 for (int i = 0; i < mMainStage.getChildCount(); ++i) { 3348 tasksLeft.append(i != 0 ? ", " : ""); 3349 tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i)); 3350 } 3351 Log.w(TAG, "Expected onTaskVanished on " + mMainStage 3352 + " to have been called with [" + tasksLeft.toString() 3353 + "] before startAnimation()."); 3354 } 3355 if (mSideStage.getChildCount() != 0) { 3356 final StringBuilder tasksLeft = new StringBuilder(); 3357 for (int i = 0; i < mSideStage.getChildCount(); ++i) { 3358 tasksLeft.append(i != 0 ? ", " : ""); 3359 tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i)); 3360 } 3361 Log.w(TAG, "Expected onTaskVanished on " + mSideStage 3362 + " to have been called with [" + tasksLeft.toString() 3363 + "] before startAnimation()."); 3364 } 3365 } 3366 3367 final ArrayMap<Integer, SurfaceControl> dismissingTasks = new ArrayMap<>(); 3368 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 3369 final TransitionInfo.Change change = info.getChanges().get(i); 3370 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 3371 if (taskInfo == null) continue; 3372 if (getStageOfTask(taskInfo) != null 3373 || getSplitItemPosition(change.getLastParent()) != SPLIT_POSITION_UNDEFINED) { 3374 dismissingTasks.put(taskInfo.taskId, change.getLeash()); 3375 } 3376 } 3377 3378 3379 if (shouldBreakPairedTaskInRecents(dismissReason)) { 3380 // Notify recents if we are exiting in a way that breaks the pair, and disable further 3381 // updates to splits in the recents until we enter split again 3382 mRecentTasks.ifPresent(recentTasks -> { 3383 for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) { 3384 recentTasks.removeSplitPair(dismissingTasks.keyAt(i)); 3385 } 3386 }); 3387 } 3388 mSplitRequest = null; 3389 3390 // Update local states. 3391 setSplitsVisible(false); 3392 // Wait until after animation to update divider 3393 3394 // Reset crops so they don't interfere with subsequent launches 3395 t.setCrop(mMainStage.mRootLeash, null); 3396 t.setCrop(mSideStage.mRootLeash, null); 3397 // Hide the non-top stage and set the top one to the fullscreen position. 3398 if (toStage != STAGE_TYPE_UNDEFINED) { 3399 t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash); 3400 t.setPosition(toStage == STAGE_TYPE_MAIN 3401 ? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0); 3402 } else { 3403 for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) { 3404 finishT.hide(dismissingTasks.valueAt(i)); 3405 } 3406 } 3407 3408 if (toStage == STAGE_TYPE_UNDEFINED) { 3409 logExit(dismissReason); 3410 } else { 3411 logExitToStage(dismissReason, toStage == STAGE_TYPE_MAIN); 3412 } 3413 3414 // Hide divider and dim layer on transition finished. 3415 setDividerVisibility(false, t); 3416 finishT.hide(mMainStage.mDimLayer); 3417 finishT.hide(mSideStage.mDimLayer); 3418 } 3419 startPendingDismissAnimation( @onNull SplitScreenTransitions.DismissSession dismissTransition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)3420 private boolean startPendingDismissAnimation( 3421 @NonNull SplitScreenTransitions.DismissSession dismissTransition, 3422 @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, 3423 @NonNull SurfaceControl.Transaction finishT) { 3424 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 3425 "startPendingDismissAnimation: transition=%d dismissTransition=%s", 3426 info.getDebugId(), dismissTransition); 3427 prepareDismissAnimation(dismissTransition.mDismissTop, dismissTransition.mReason, info, 3428 t, finishT); 3429 if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) { 3430 // TODO: Have a proper remote for this. Until then, though, reset state and use the 3431 // normal animation stuff (which falls back to the normal launcher remote). 3432 setDividerVisibility(false, t); 3433 mSplitLayout.release(t); 3434 mSplitTransitions.mPendingDismiss = null; 3435 return false; 3436 } 3437 dismissTransition.setFinishedCallback((callbackWct, callbackT) -> { 3438 mMainStage.getSplitDecorManager().release(callbackT); 3439 mSideStage.getSplitDecorManager().release(callbackT); 3440 callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false); 3441 }); 3442 return true; 3443 } 3444 3445 /** Call this when starting the open-recents animation while split-screen is active. */ onRecentsInSplitAnimationStart(TransitionInfo info)3446 public void onRecentsInSplitAnimationStart(TransitionInfo info) { 3447 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationStart: transition=%d", 3448 info.getDebugId()); 3449 if (isSplitScreenVisible()) { 3450 // Cache tasks on live tile. 3451 for (int i = 0; i < info.getChanges().size(); ++i) { 3452 final TransitionInfo.Change change = info.getChanges().get(i); 3453 if (TransitionUtil.isClosingType(change.getMode()) 3454 && change.getTaskInfo() != null) { 3455 final int taskId = change.getTaskInfo().taskId; 3456 if (mMainStage.getTopVisibleChildTaskId() == taskId 3457 || mSideStage.getTopVisibleChildTaskId() == taskId) { 3458 mPausingTasks.add(taskId); 3459 } 3460 } 3461 } 3462 } 3463 3464 addDividerBarToTransition(info, false /* show */); 3465 } 3466 3467 /** Call this when the recents animation canceled during split-screen. */ onRecentsInSplitAnimationCanceled()3468 public void onRecentsInSplitAnimationCanceled() { 3469 mPausingTasks.clear(); 3470 setSplitsVisible(false); 3471 3472 final WindowContainerTransaction wct = new WindowContainerTransaction(); 3473 wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, 3474 true /* reparentLeafTaskIfRelaunch */); 3475 mTaskOrganizer.applyTransaction(wct); 3476 } 3477 3478 /** Call this when the recents animation during split-screen finishes. */ onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT)3479 public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct, 3480 SurfaceControl.Transaction finishT) { 3481 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationFinish"); 3482 mPausingTasks.clear(); 3483 // Check if the recent transition is finished by returning to the current 3484 // split, so we can restore the divider bar. 3485 for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) { 3486 final WindowContainerTransaction.HierarchyOp op = 3487 finishWct.getHierarchyOps().get(i); 3488 final IBinder container = op.getContainer(); 3489 if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() 3490 && (mMainStage.containsContainer(container) 3491 || mSideStage.containsContainer(container))) { 3492 updateSurfaceBounds(mSplitLayout, finishT, 3493 false /* applyResizingOffset */); 3494 finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash); 3495 setDividerVisibility(true, finishT); 3496 return; 3497 } 3498 } 3499 3500 setSplitsVisible(false); 3501 finishWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, 3502 true /* reparentLeafTaskIfRelaunch */); 3503 } 3504 3505 /** Call this when the animation from split screen to desktop is started. */ onSplitToDesktop()3506 public void onSplitToDesktop() { 3507 setSplitsVisible(false); 3508 } 3509 3510 /** Call this when the recents animation finishes by doing pair-to-pair switch. */ onRecentsPairToPairAnimationFinish(WindowContainerTransaction finishWct)3511 public void onRecentsPairToPairAnimationFinish(WindowContainerTransaction finishWct) { 3512 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsPairToPairAnimationFinish"); 3513 // Pair-to-pair switch happened so here should evict the live tile from its stage. 3514 // Otherwise, the task will remain in stage, and occluding the new task when next time 3515 // user entering recents. 3516 for (int i = mPausingTasks.size() - 1; i >= 0; --i) { 3517 final int taskId = mPausingTasks.get(i); 3518 if (mMainStage.containsTask(taskId)) { 3519 mMainStage.evictChildren(finishWct, taskId); 3520 } else if (mSideStage.containsTask(taskId)) { 3521 mSideStage.evictChildren(finishWct, taskId); 3522 } 3523 } 3524 // If pending enter hasn't consumed, the mix handler will invoke start pending 3525 // animation within following transition. 3526 if (mSplitTransitions.mPendingEnter == null) { 3527 mPausingTasks.clear(); 3528 updateRecentTasksSplitPair(); 3529 } 3530 } 3531 addDividerBarToTransition(@onNull TransitionInfo info, boolean show)3532 private void addDividerBarToTransition(@NonNull TransitionInfo info, boolean show) { 3533 final SurfaceControl leash = mSplitLayout.getDividerLeash(); 3534 if (leash == null || !leash.isValid()) { 3535 Slog.w(TAG, "addDividerBarToTransition but leash was released or not be created"); 3536 return; 3537 } 3538 3539 final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash); 3540 mSplitLayout.getRefDividerBounds(mTempRect1); 3541 barChange.setParent(mRootTaskInfo.token); 3542 barChange.setStartAbsBounds(mTempRect1); 3543 barChange.setEndAbsBounds(mTempRect1); 3544 barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK); 3545 barChange.setFlags(FLAG_IS_DIVIDER_BAR); 3546 // Technically this should be order-0, but this is running after layer assignment 3547 // and it's a special case, so just add to end. 3548 info.addChange(barChange); 3549 } 3550 getDividerBarLegacyTarget()3551 RemoteAnimationTarget getDividerBarLegacyTarget() { 3552 final Rect bounds = mSplitLayout.getDividerBounds(); 3553 return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */, 3554 mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */, 3555 null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */, 3556 new android.graphics.Point(0, 0) /* position */, bounds, bounds, 3557 new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */, 3558 null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER); 3559 } 3560 3561 @NeverCompile 3562 @Override dump(@onNull PrintWriter pw, String prefix)3563 public void dump(@NonNull PrintWriter pw, String prefix) { 3564 final String innerPrefix = prefix + " "; 3565 final String childPrefix = innerPrefix + " "; 3566 pw.println(prefix + TAG + " mDisplayId=" + mDisplayId); 3567 pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible); 3568 pw.println(innerPrefix + "isSplitActive=" + isSplitActive()); 3569 pw.println(innerPrefix + "isSplitVisible=" + isSplitScreenVisible()); 3570 pw.println(innerPrefix + "isLeftRightSplit=" + mSplitLayout.isLeftRightSplit()); 3571 pw.println(innerPrefix + "MainStage"); 3572 pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition())); 3573 pw.println(childPrefix + "isActive=" + mMainStage.isActive()); 3574 mMainStage.dump(pw, childPrefix); 3575 pw.println(innerPrefix + "MainStageListener"); 3576 mMainStageListener.dump(pw, childPrefix); 3577 pw.println(innerPrefix + "SideStage"); 3578 pw.println(childPrefix + "stagePosition=" + splitPositionToString(getSideStagePosition())); 3579 mSideStage.dump(pw, childPrefix); 3580 pw.println(innerPrefix + "SideStageListener"); 3581 mSideStageListener.dump(pw, childPrefix); 3582 mSplitLayout.dump(pw, childPrefix); 3583 if (!mPausingTasks.isEmpty()) { 3584 pw.println(childPrefix + "mPausingTasks=" + mPausingTasks); 3585 } 3586 } 3587 3588 /** 3589 * Directly set the visibility of both splits. This assumes hasChildren matches visibility. 3590 * This is intended for batch use, so it assumes other state management logic is already 3591 * handled. 3592 */ setSplitsVisible(boolean visible)3593 private void setSplitsVisible(boolean visible) { 3594 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setSplitsVisible: visible=%b", visible); 3595 mMainStageListener.mVisible = mSideStageListener.mVisible = visible; 3596 mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible; 3597 } 3598 3599 /** 3600 * Sets drag info to be logged when splitscreen is next entered. 3601 */ onDroppedToSplit(@plitPosition int position, InstanceId dragSessionId)3602 public void onDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) { 3603 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onDroppedToSplit: position=%d", position); 3604 if (!isSplitScreenVisible()) { 3605 mIsDropEntering = true; 3606 mSkipEvictingMainStageChildren = true; 3607 } 3608 if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) { 3609 // If split running background, exit split first. 3610 // Skip this on shell transition due to we could evict existing tasks on transition 3611 // finished. 3612 exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); 3613 } 3614 mLogger.enterRequestedByDrag(position, dragSessionId); 3615 } 3616 3617 /** 3618 * Sets info to be logged when splitscreen is next entered. 3619 */ onRequestToSplit(InstanceId sessionId, @SplitEnterReason int enterReason)3620 public void onRequestToSplit(InstanceId sessionId, @SplitEnterReason int enterReason) { 3621 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRequestToSplit: reason=%d", enterReason); 3622 if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) { 3623 // If split running background, exit split first. 3624 // Skip this on shell transition due to we could evict existing tasks on transition 3625 // finished. 3626 exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); 3627 } 3628 mLogger.enterRequested(sessionId, enterReason); 3629 } 3630 3631 /** 3632 * Logs the exit of splitscreen. 3633 */ logExit(@xitReason int exitReason)3634 private void logExit(@ExitReason int exitReason) { 3635 mLogger.logExit(exitReason, 3636 SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */, 3637 SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */, 3638 mSplitLayout.isLeftRightSplit()); 3639 } 3640 handleUnsupportedSplitStart()3641 private void handleUnsupportedSplitStart() { 3642 mSplitUnsupportedToast.show(); 3643 notifySplitAnimationFinished(); 3644 } 3645 notifySplitAnimationFinished()3646 void notifySplitAnimationFinished() { 3647 if (mSplitInvocationListener == null || mSplitInvocationListenerExecutor == null) { 3648 return; 3649 } 3650 mSplitInvocationListenerExecutor.execute(() -> 3651 mSplitInvocationListener.onSplitAnimationInvoked(false /*animationRunning*/)); 3652 } 3653 3654 /** 3655 * Logs the exit of splitscreen to a specific stage. This must be called before the exit is 3656 * executed. 3657 */ logExitToStage(@xitReason int exitReason, boolean toMainStage)3658 private void logExitToStage(@ExitReason int exitReason, boolean toMainStage) { 3659 mLogger.logExit(exitReason, 3660 toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED, 3661 toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */, 3662 !toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED, 3663 !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */, 3664 mSplitLayout.isLeftRightSplit()); 3665 } 3666 3667 class StageListenerImpl implements StageTaskListener.StageListenerCallbacks { 3668 boolean mHasRootTask = false; 3669 boolean mVisible = false; 3670 boolean mHasChildren = false; 3671 3672 @Override onRootTaskAppeared()3673 public void onRootTaskAppeared() { 3674 mHasRootTask = true; 3675 StageCoordinator.this.onRootTaskAppeared(); 3676 } 3677 3678 @Override onChildTaskAppeared(int taskId)3679 public void onChildTaskAppeared(int taskId) { 3680 StageCoordinator.this.onChildTaskAppeared(this, taskId); 3681 } 3682 3683 @Override onStatusChanged(boolean visible, boolean hasChildren)3684 public void onStatusChanged(boolean visible, boolean hasChildren) { 3685 if (!mHasRootTask) return; 3686 3687 if (mHasChildren != hasChildren) { 3688 mHasChildren = hasChildren; 3689 StageCoordinator.this.onStageHasChildrenChanged(this); 3690 } 3691 if (mVisible != visible) { 3692 mVisible = visible; 3693 StageCoordinator.this.onStageVisibilityChanged(this); 3694 } 3695 } 3696 3697 @Override onChildTaskStatusChanged(int taskId, boolean present, boolean visible)3698 public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) { 3699 StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible); 3700 } 3701 3702 @Override onRootTaskVanished()3703 public void onRootTaskVanished() { 3704 reset(); 3705 StageCoordinator.this.onRootTaskVanished(); 3706 } 3707 3708 @Override onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo)3709 public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) { 3710 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onNoLongerSupportMultiWindow: task=%s", taskInfo); 3711 if (mMainStage.isActive()) { 3712 final boolean isMainStage = mMainStageListener == this; 3713 if (!ENABLE_SHELL_TRANSITIONS) { 3714 StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage, 3715 EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); 3716 handleUnsupportedSplitStart(); 3717 return; 3718 } 3719 3720 // If visible, we preserve the app and keep it running. If an app becomes 3721 // unsupported in the bg, break split without putting anything on top 3722 boolean splitScreenVisible = isSplitScreenVisible(); 3723 int stageType = STAGE_TYPE_UNDEFINED; 3724 if (splitScreenVisible) { 3725 stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; 3726 } 3727 final WindowContainerTransaction wct = new WindowContainerTransaction(); 3728 prepareExitSplitScreen(stageType, wct); 3729 clearSplitPairedInRecents(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); 3730 mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType, 3731 EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); 3732 Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow", 3733 "app package " + taskInfo.baseActivity.getPackageName() 3734 + " does not support splitscreen, or is a controlled activity type")); 3735 if (splitScreenVisible) { 3736 handleUnsupportedSplitStart(); 3737 } 3738 } 3739 } 3740 reset()3741 private void reset() { 3742 mHasRootTask = false; 3743 mVisible = false; 3744 mHasChildren = false; 3745 } 3746 dump(@onNull PrintWriter pw, String prefix)3747 public void dump(@NonNull PrintWriter pw, String prefix) { 3748 pw.println(prefix + "mHasRootTask=" + mHasRootTask); 3749 pw.println(prefix + "mVisible=" + mVisible); 3750 pw.println(prefix + "mHasChildren=" + mHasChildren); 3751 } 3752 } 3753 } 3754