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