Loading libs/WindowManager/Shell/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -111,4 +111,8 @@ <!-- Whether to dim a split-screen task when the other is the IME target --> <bool name="config_dimNonImeAttachedSide">true</bool> <!-- Components support to launch multiple instances into split-screen --> <string-array name="config_componentsSupportMultiInstancesSplit"> </string-array> </resources> libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +1 −1 Original line number Diff line number Diff line Loading @@ -601,7 +601,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange animator.start(); } /** Swich both surface position with animation. */ /** Switch both surface position with animation. */ public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1, SurfaceControl leash2, Consumer<Rect> finishCallback) { final boolean isLandscape = isLandscape(); Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl +4 −5 Original line number Diff line number Diff line Loading @@ -86,8 +86,8 @@ interface ISplitScreen { /** * Starts a pair of intent and task in one transition. */ oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio, oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 16; /** Loading @@ -108,9 +108,8 @@ interface ISplitScreen { * Starts a pair of intent and task using legacy transition system. */ oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent, in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12; in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12; /** * Starts a pair of shortcut and task using legacy transition system. Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +154 −62 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; import static android.view.Display.DEFAULT_DISPLAY; Loading @@ -32,6 +33,8 @@ import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; Loading Loading @@ -60,13 +63,12 @@ import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; Loading Loading @@ -166,8 +168,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private final IconProvider mIconProvider; private final Optional<RecentTasksController> mRecentTasksOptional; private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler; private final String[] mMultiInstancesComponents; @VisibleForTesting StageCoordinator mStageCoordinator; private StageCoordinator mStageCoordinator; // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated // outside the bounds of the roots by being reparented into a higher level fullscreen container private SurfaceControl mGoingToRecentsTasksLayer; Loading Loading @@ -210,6 +215,51 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) { shellInit.addInitCallback(this::onInit, this); } // TODO(255224696): Remove the config once having a way for client apps to opt-in // multi-instances split. mMultiInstancesComponents = mContext.getResources() .getStringArray(R.array.config_componentsSupportMultiInstancesSplit); } @VisibleForTesting SplitScreenController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTDAOrganizer, DisplayController displayController, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, DragAndDropController dragAndDropController, Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider, RecentTasksController recentTasks, ShellExecutor mainExecutor, StageCoordinator stageCoordinator) { mShellCommandHandler = shellCommandHandler; mShellController = shellController; mTaskOrganizer = shellTaskOrganizer; mSyncQueue = syncQueue; mContext = context; mRootTDAOrganizer = rootTDAOrganizer; mMainExecutor = mainExecutor; mDisplayController = displayController; mDisplayImeController = displayImeController; mDisplayInsetsController = displayInsetsController; mDragAndDropController = dragAndDropController; mTransitions = transitions; mTransactionPool = transactionPool; mIconProvider = iconProvider; mRecentTasksOptional = Optional.of(recentTasks); mStageCoordinator = stageCoordinator; mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this); shellInit.addInitCallback(this::onInit, this); mMultiInstancesComponents = mContext.getResources() .getStringArray(R.array.config_componentsSupportMultiInstancesSplit); } public SplitScreen asSplitScreen() { Loading Loading @@ -471,62 +521,99 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, startIntent(intent, fillInIntent, position, options); } private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { Intent fillInIntent = null; if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId) && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) { fillInIntent = new Intent(); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); } mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent, options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId); } private void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { Intent fillInIntent = null; if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId) && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) { fillInIntent = new Intent(); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); } mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId, options2, splitPosition, splitRatio, remoteTransition, instanceId); } @Override public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { if (fillInIntent == null) { fillInIntent = new Intent(); } // Flag this as a no-user-action launch to prevent sending user leaving event to the // current top activity since it's going to be put into another side of the split. This // prevents the current top activity from going into pip mode due to user leaving event. // Flag this as a no-user-action launch to prevent sending user leaving event to the current // top activity since it's going to be put into another side of the split. This prevents the // current top activity from going into pip mode due to user leaving event. if (fillInIntent == null) fillInIntent = new Intent(); fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the // split and there is no reusable background task. if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) { final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent() ? mRecentTasksOptional.get().findTaskInBackground( intent.getIntent().getComponent()) : null; if (launchSameComponentAdjacently(intent, position, INVALID_TASK_ID)) { final ComponentName launching = intent.getIntent().getComponent(); if (supportMultiInstancesSplit(launching)) { // To prevent accumulating large number of instances in the background, reuse task // in the background with priority. final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional .map(recentTasks -> recentTasks.findTaskInBackground(launching)) .orElse(null); if (taskInfo != null) { startTask(taskInfo.taskId, position, options); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background"); return; } // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of // the split and there is no reusable background task. fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } if (!ENABLE_SHELL_TRANSITIONS) { mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options); } else if (isSplitScreenVisible()) { mStageCoordinator.switchSplitPosition("startIntent"); return; } } mStageCoordinator.startIntent(intent, fillInIntent, position, options); } /** Returns {@code true} if it's launching the same component on both sides of the split. */ @VisibleForTesting boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) { if (startIntent == null) { return false; } private boolean launchSameComponentAdjacently(@Nullable PendingIntent pendingIntent, @SplitPosition int position, int taskId) { if (pendingIntent == null || pendingIntent.getIntent() == null) return false; final ComponentName launchingActivity = startIntent.getComponent(); if (launchingActivity == null) { final ComponentName launchingActivity = pendingIntent.getIntent().getComponent(); if (launchingActivity == null) return false; if (taskId != INVALID_TASK_ID) { final ActivityManager.RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(taskId); if (taskInfo != null) { return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity); } return false; } if (isSplitScreenVisible()) { // To prevent users from constantly dropping the same app to the same side resulting in // a large number of instances in the background. final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position); final ComponentName targetActivity = targetTaskInfo != null ? targetTaskInfo.baseIntent.getComponent() : null; if (Objects.equals(launchingActivity, targetActivity)) { if (!isSplitScreenVisible()) { // Split screen is not yet activated, check if the current top running task is valid to // split together. final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo(); if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) { return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity); } return false; } // Allow users to start a new instance the same to adjacent side. // Compare to the adjacent side of the split to determine if this is launching the same // component adjacently. final ActivityManager.RunningTaskInfo pairedTaskInfo = getTaskInfo(SplitLayout.reversePosition(position)); final ComponentName pairedActivity = pairedTaskInfo != null Loading @@ -534,9 +621,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return Objects.equals(launchingActivity, pairedActivity); } final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo(); if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) { return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity); @VisibleForTesting /** Returns {@code true} if the component supports multi-instances split. */ boolean supportMultiInstancesSplit(@Nullable ComponentName launching) { if (launching == null) return false; final String componentName = launching.flattenToString(); for (int i = 0; i < mMultiInstancesComponents.length; i++) { if (mMultiInstancesComponents[i].equals(componentName)) { return true; } } return false; Loading Loading @@ -839,14 +933,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent, Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntentAndTaskWithLegacyTransition", (controller) -> controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition( pendingIntent, fillInIntent, options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId)); controller.startIntentAndTaskWithLegacyTransition(pendingIntent, options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId)); } @Override Loading @@ -872,14 +965,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntentAndTask", (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId, options2, splitPosition, splitRatio, remoteTransition, instanceId)); (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId, options2, splitPosition, splitRatio, remoteTransition, instanceId)); } @Override Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +17 −25 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; Loading Loading @@ -428,6 +427,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Launches an activity into split. */ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { if (!ENABLE_SHELL_TRANSITIONS) { startIntentLegacy(intent, fillInIntent, position, options); return; } final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction evictWct = new WindowContainerTransaction(); prepareEvictChildTasks(position, evictWct); Loading @@ -441,13 +445,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareEnterSplitScreen(wct, null /* taskInfo */, position); mSplitTransitions.startEnterTransition(transitType, wct, null, this, aborted -> { // Switch the split position if launching as MULTIPLE_TASK failed. if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { setSideStagePositionAnimated( SplitLayout.reversePosition(mSideStagePosition)); } } /* consumedCallback */, null /* consumedCallback */, (finishWct, finishT) -> { if (!evictWct.isEmpty()) { finishWct.merge(evictWct, true); Loading @@ -457,7 +455,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Launches an activity into split by legacy transition. */ void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @androidx.annotation.Nullable Bundle options) { @SplitPosition int position, @Nullable Bundle options) { final WindowContainerTransaction evictWct = new WindowContainerTransaction(); prepareEvictChildTasks(position, evictWct); Loading @@ -473,12 +471,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, exitSplitScreen(mMainStage.getChildCount() == 0 ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); mSplitUnsupportedToast.show(); } else { // Switch the split position if launching as MULTIPLE_TASK failed. if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { setSideStagePosition(SplitLayout.reversePosition( getSideStagePosition()), null); } } // Do nothing when the animation was cancelled. Loading Loading @@ -771,9 +763,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.evictInvisibleChildren(wct); } Bundle resolveStartStage(@StageType int stage, @SplitPosition int position, @androidx.annotation.Nullable Bundle options, @androidx.annotation.Nullable WindowContainerTransaction wct) { Bundle resolveStartStage(@StageType int stage, @SplitPosition int position, @Nullable Bundle options, @Nullable WindowContainerTransaction wct) { switch (stage) { case STAGE_TYPE_UNDEFINED: { if (position != SPLIT_POSITION_UNDEFINED) { Loading Loading @@ -844,9 +835,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, : mMainStage.getTopVisibleChildTaskId(); } void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) { if (mSideStagePosition == sideStagePosition) return; SurfaceControl.Transaction t = mTransactionPool.acquire(); void switchSplitPosition(String reason) { final SurfaceControl.Transaction t = mTransactionPool.acquire(); mTempRect1.setEmpty(); final StageTaskListener topLeftStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; Loading Loading @@ -886,6 +876,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, va.start(); }); }); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason); mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(), getSideStagePosition(), mSideStage.getTopChildTaskUid(), mSplitLayout.isLandscape()); } void setSideStagePosition(@SplitPosition int sideStagePosition, Loading Loading @@ -1617,10 +1612,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onDoubleTappedDivider() { setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition)); mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(), getSideStagePosition(), mSideStage.getTopChildTaskUid(), mSplitLayout.isLandscape()); switchSplitPosition("double tap"); } @Override Loading Loading
libs/WindowManager/Shell/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -111,4 +111,8 @@ <!-- Whether to dim a split-screen task when the other is the IME target --> <bool name="config_dimNonImeAttachedSide">true</bool> <!-- Components support to launch multiple instances into split-screen --> <string-array name="config_componentsSupportMultiInstancesSplit"> </string-array> </resources>
libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +1 −1 Original line number Diff line number Diff line Loading @@ -601,7 +601,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange animator.start(); } /** Swich both surface position with animation. */ /** Switch both surface position with animation. */ public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1, SurfaceControl leash2, Consumer<Rect> finishCallback) { final boolean isLandscape = isLandscape(); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl +4 −5 Original line number Diff line number Diff line Loading @@ -86,8 +86,8 @@ interface ISplitScreen { /** * Starts a pair of intent and task in one transition. */ oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio, oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 16; /** Loading @@ -108,9 +108,8 @@ interface ISplitScreen { * Starts a pair of intent and task using legacy transition system. */ oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent, in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12; in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12; /** * Starts a pair of shortcut and task using legacy transition system. Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +154 −62 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; import static android.view.Display.DEFAULT_DISPLAY; Loading @@ -32,6 +33,8 @@ import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; Loading Loading @@ -60,13 +63,12 @@ import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; Loading Loading @@ -166,8 +168,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private final IconProvider mIconProvider; private final Optional<RecentTasksController> mRecentTasksOptional; private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler; private final String[] mMultiInstancesComponents; @VisibleForTesting StageCoordinator mStageCoordinator; private StageCoordinator mStageCoordinator; // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated // outside the bounds of the roots by being reparented into a higher level fullscreen container private SurfaceControl mGoingToRecentsTasksLayer; Loading Loading @@ -210,6 +215,51 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) { shellInit.addInitCallback(this::onInit, this); } // TODO(255224696): Remove the config once having a way for client apps to opt-in // multi-instances split. mMultiInstancesComponents = mContext.getResources() .getStringArray(R.array.config_componentsSupportMultiInstancesSplit); } @VisibleForTesting SplitScreenController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTDAOrganizer, DisplayController displayController, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, DragAndDropController dragAndDropController, Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider, RecentTasksController recentTasks, ShellExecutor mainExecutor, StageCoordinator stageCoordinator) { mShellCommandHandler = shellCommandHandler; mShellController = shellController; mTaskOrganizer = shellTaskOrganizer; mSyncQueue = syncQueue; mContext = context; mRootTDAOrganizer = rootTDAOrganizer; mMainExecutor = mainExecutor; mDisplayController = displayController; mDisplayImeController = displayImeController; mDisplayInsetsController = displayInsetsController; mDragAndDropController = dragAndDropController; mTransitions = transitions; mTransactionPool = transactionPool; mIconProvider = iconProvider; mRecentTasksOptional = Optional.of(recentTasks); mStageCoordinator = stageCoordinator; mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this); shellInit.addInitCallback(this::onInit, this); mMultiInstancesComponents = mContext.getResources() .getStringArray(R.array.config_componentsSupportMultiInstancesSplit); } public SplitScreen asSplitScreen() { Loading Loading @@ -471,62 +521,99 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, startIntent(intent, fillInIntent, position, options); } private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { Intent fillInIntent = null; if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId) && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) { fillInIntent = new Intent(); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); } mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent, options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId); } private void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { Intent fillInIntent = null; if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId) && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) { fillInIntent = new Intent(); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); } mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId, options2, splitPosition, splitRatio, remoteTransition, instanceId); } @Override public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { if (fillInIntent == null) { fillInIntent = new Intent(); } // Flag this as a no-user-action launch to prevent sending user leaving event to the // current top activity since it's going to be put into another side of the split. This // prevents the current top activity from going into pip mode due to user leaving event. // Flag this as a no-user-action launch to prevent sending user leaving event to the current // top activity since it's going to be put into another side of the split. This prevents the // current top activity from going into pip mode due to user leaving event. if (fillInIntent == null) fillInIntent = new Intent(); fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the // split and there is no reusable background task. if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) { final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent() ? mRecentTasksOptional.get().findTaskInBackground( intent.getIntent().getComponent()) : null; if (launchSameComponentAdjacently(intent, position, INVALID_TASK_ID)) { final ComponentName launching = intent.getIntent().getComponent(); if (supportMultiInstancesSplit(launching)) { // To prevent accumulating large number of instances in the background, reuse task // in the background with priority. final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional .map(recentTasks -> recentTasks.findTaskInBackground(launching)) .orElse(null); if (taskInfo != null) { startTask(taskInfo.taskId, position, options); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background"); return; } // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of // the split and there is no reusable background task. fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } if (!ENABLE_SHELL_TRANSITIONS) { mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options); } else if (isSplitScreenVisible()) { mStageCoordinator.switchSplitPosition("startIntent"); return; } } mStageCoordinator.startIntent(intent, fillInIntent, position, options); } /** Returns {@code true} if it's launching the same component on both sides of the split. */ @VisibleForTesting boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) { if (startIntent == null) { return false; } private boolean launchSameComponentAdjacently(@Nullable PendingIntent pendingIntent, @SplitPosition int position, int taskId) { if (pendingIntent == null || pendingIntent.getIntent() == null) return false; final ComponentName launchingActivity = startIntent.getComponent(); if (launchingActivity == null) { final ComponentName launchingActivity = pendingIntent.getIntent().getComponent(); if (launchingActivity == null) return false; if (taskId != INVALID_TASK_ID) { final ActivityManager.RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(taskId); if (taskInfo != null) { return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity); } return false; } if (isSplitScreenVisible()) { // To prevent users from constantly dropping the same app to the same side resulting in // a large number of instances in the background. final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position); final ComponentName targetActivity = targetTaskInfo != null ? targetTaskInfo.baseIntent.getComponent() : null; if (Objects.equals(launchingActivity, targetActivity)) { if (!isSplitScreenVisible()) { // Split screen is not yet activated, check if the current top running task is valid to // split together. final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo(); if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) { return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity); } return false; } // Allow users to start a new instance the same to adjacent side. // Compare to the adjacent side of the split to determine if this is launching the same // component adjacently. final ActivityManager.RunningTaskInfo pairedTaskInfo = getTaskInfo(SplitLayout.reversePosition(position)); final ComponentName pairedActivity = pairedTaskInfo != null Loading @@ -534,9 +621,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return Objects.equals(launchingActivity, pairedActivity); } final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo(); if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) { return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity); @VisibleForTesting /** Returns {@code true} if the component supports multi-instances split. */ boolean supportMultiInstancesSplit(@Nullable ComponentName launching) { if (launching == null) return false; final String componentName = launching.flattenToString(); for (int i = 0; i < mMultiInstancesComponents.length; i++) { if (mMultiInstancesComponents[i].equals(componentName)) { return true; } } return false; Loading Loading @@ -839,14 +933,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent, Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntentAndTaskWithLegacyTransition", (controller) -> controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition( pendingIntent, fillInIntent, options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId)); controller.startIntentAndTaskWithLegacyTransition(pendingIntent, options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId)); } @Override Loading @@ -872,14 +965,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntentAndTask", (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId, options2, splitPosition, splitRatio, remoteTransition, instanceId)); (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId, options2, splitPosition, splitRatio, remoteTransition, instanceId)); } @Override Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +17 −25 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; Loading Loading @@ -428,6 +427,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Launches an activity into split. */ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { if (!ENABLE_SHELL_TRANSITIONS) { startIntentLegacy(intent, fillInIntent, position, options); return; } final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction evictWct = new WindowContainerTransaction(); prepareEvictChildTasks(position, evictWct); Loading @@ -441,13 +445,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareEnterSplitScreen(wct, null /* taskInfo */, position); mSplitTransitions.startEnterTransition(transitType, wct, null, this, aborted -> { // Switch the split position if launching as MULTIPLE_TASK failed. if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { setSideStagePositionAnimated( SplitLayout.reversePosition(mSideStagePosition)); } } /* consumedCallback */, null /* consumedCallback */, (finishWct, finishT) -> { if (!evictWct.isEmpty()) { finishWct.merge(evictWct, true); Loading @@ -457,7 +455,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Launches an activity into split by legacy transition. */ void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @androidx.annotation.Nullable Bundle options) { @SplitPosition int position, @Nullable Bundle options) { final WindowContainerTransaction evictWct = new WindowContainerTransaction(); prepareEvictChildTasks(position, evictWct); Loading @@ -473,12 +471,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, exitSplitScreen(mMainStage.getChildCount() == 0 ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); mSplitUnsupportedToast.show(); } else { // Switch the split position if launching as MULTIPLE_TASK failed. if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { setSideStagePosition(SplitLayout.reversePosition( getSideStagePosition()), null); } } // Do nothing when the animation was cancelled. Loading Loading @@ -771,9 +763,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.evictInvisibleChildren(wct); } Bundle resolveStartStage(@StageType int stage, @SplitPosition int position, @androidx.annotation.Nullable Bundle options, @androidx.annotation.Nullable WindowContainerTransaction wct) { Bundle resolveStartStage(@StageType int stage, @SplitPosition int position, @Nullable Bundle options, @Nullable WindowContainerTransaction wct) { switch (stage) { case STAGE_TYPE_UNDEFINED: { if (position != SPLIT_POSITION_UNDEFINED) { Loading Loading @@ -844,9 +835,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, : mMainStage.getTopVisibleChildTaskId(); } void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) { if (mSideStagePosition == sideStagePosition) return; SurfaceControl.Transaction t = mTransactionPool.acquire(); void switchSplitPosition(String reason) { final SurfaceControl.Transaction t = mTransactionPool.acquire(); mTempRect1.setEmpty(); final StageTaskListener topLeftStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; Loading Loading @@ -886,6 +876,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, va.start(); }); }); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason); mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(), getSideStagePosition(), mSideStage.getTopChildTaskUid(), mSplitLayout.isLandscape()); } void setSideStagePosition(@SplitPosition int sideStagePosition, Loading Loading @@ -1617,10 +1612,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onDoubleTappedDivider() { setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition)); mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(), getSideStagePosition(), mSideStage.getTopChildTaskUid(), mSplitLayout.isLandscape()); switchSplitPosition("double tap"); } @Override Loading