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