• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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