Merge "Make multi-instances split opt-in based" into tm-qpr-dev

This commit is contained in:
Jerry Chang
2022-11-14 13:29:46 +00:00
committed by Android (Google) Code Review
7 changed files with 287 additions and 142 deletions

View File

@@ -111,4 +111,8 @@
<!-- Whether to dim a split-screen task when the other is the IME target --> <!-- Whether to dim a split-screen task when the other is the IME target -->
<bool name="config_dimNonImeAttachedSide">true</bool> <bool name="config_dimNonImeAttachedSide">true</bool>
<!-- Components support to launch multiple instances into split-screen -->
<string-array name="config_componentsSupportMultiInstancesSplit">
</string-array>
</resources> </resources>

View File

@@ -601,7 +601,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
animator.start(); animator.start();
} }
/** Swich both surface position with animation. */ /** Switch both surface position with animation. */
public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1, public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, Consumer<Rect> finishCallback) { SurfaceControl leash2, Consumer<Rect> finishCallback) {
final boolean isLandscape = isLandscape(); final boolean isLandscape = isLandscape();

View File

@@ -86,8 +86,8 @@ interface ISplitScreen {
/** /**
* Starts a pair of intent and task in one transition. * Starts a pair of intent and task in one transition.
*/ */
oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent, oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId,
in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio, in Bundle options2, int sidePosition, float splitRatio,
in RemoteTransition remoteTransition, in InstanceId instanceId) = 16; in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
/** /**
@@ -108,9 +108,8 @@ interface ISplitScreen {
* Starts a pair of intent and task using legacy transition system. * Starts a pair of intent and task using legacy transition system.
*/ */
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent, oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2, in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12;
in InstanceId instanceId) = 12;
/** /**
* Starts a pair of shortcut and task using legacy transition system. * Starts a pair of shortcut and task using legacy transition system.

View File

@@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen;
import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT; 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_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.DEFAULT_DISPLAY;
@@ -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.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; 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.ActivityManager;
import android.app.ActivityOptions; import android.app.ActivityOptions;
import android.app.ActivityTaskManager; import android.app.ActivityTaskManager;
@@ -60,13 +63,12 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread; import androidx.annotation.BinderThread;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId; import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog; import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider; import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayController;
@@ -166,8 +168,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final IconProvider mIconProvider; private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional; private final Optional<RecentTasksController> mRecentTasksOptional;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler; 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 // 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 // outside the bounds of the roots by being reparented into a higher level fullscreen container
private SurfaceControl mGoingToRecentsTasksLayer; private SurfaceControl mGoingToRecentsTasksLayer;
@@ -210,6 +215,51 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) { if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
shellInit.addInitCallback(this::onInit, this); 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() { public SplitScreen asSplitScreen() {
@@ -471,62 +521,99 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
startIntent(intent, fillInIntent, position, options); 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 @Override
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) { @SplitPosition int position, @Nullable Bundle options) {
if (fillInIntent == null) { // Flag this as a no-user-action launch to prevent sending user leaving event to the current
fillInIntent = new Intent(); // 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 if (fillInIntent == null) fillInIntent = new Intent();
// 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.
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
// Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the if (launchSameComponentAdjacently(intent, position, INVALID_TASK_ID)) {
// split and there is no reusable background task. final ComponentName launching = intent.getIntent().getComponent();
if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) { if (supportMultiInstancesSplit(launching)) {
final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent() // To prevent accumulating large number of instances in the background, reuse task
? mRecentTasksOptional.get().findTaskInBackground( // in the background with priority.
intent.getIntent().getComponent()) final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
: null; .map(recentTasks -> recentTasks.findTaskInBackground(launching))
.orElse(null);
if (taskInfo != null) { if (taskInfo != null) {
startTask(taskInfo.taskId, position, options); startTask(taskInfo.taskId, position, options);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Start task in background");
return; return;
} }
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
}
if (!ENABLE_SHELL_TRANSITIONS) { // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options); // 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");
} else if (isSplitScreenVisible()) {
mStageCoordinator.switchSplitPosition("startIntent");
return; return;
} }
}
mStageCoordinator.startIntent(intent, fillInIntent, position, options); mStageCoordinator.startIntent(intent, fillInIntent, position, options);
} }
/** Returns {@code true} if it's launching the same component on both sides of the split. */ /** Returns {@code true} if it's launching the same component on both sides of the split. */
@VisibleForTesting private boolean launchSameComponentAdjacently(@Nullable PendingIntent pendingIntent,
boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) { @SplitPosition int position, int taskId) {
if (startIntent == null) { if (pendingIntent == null || pendingIntent.getIntent() == null) return false;
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; return false;
} }
final ComponentName launchingActivity = startIntent.getComponent(); if (!isSplitScreenVisible()) {
if (launchingActivity == null) { // 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; return false;
} }
if (isSplitScreenVisible()) { // Compare to the adjacent side of the split to determine if this is launching the same
// To prevent users from constantly dropping the same app to the same side resulting in // component adjacently.
// 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)) {
return false;
}
// Allow users to start a new instance the same to adjacent side.
final ActivityManager.RunningTaskInfo pairedTaskInfo = final ActivityManager.RunningTaskInfo pairedTaskInfo =
getTaskInfo(SplitLayout.reversePosition(position)); getTaskInfo(SplitLayout.reversePosition(position));
final ComponentName pairedActivity = pairedTaskInfo != null final ComponentName pairedActivity = pairedTaskInfo != null
@@ -534,9 +621,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return Objects.equals(launchingActivity, pairedActivity); return Objects.equals(launchingActivity, pairedActivity);
} }
final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo(); @VisibleForTesting
if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) { /** Returns {@code true} if the component supports multi-instances split. */
return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity); 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; return false;
@@ -839,14 +933,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override @Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
Intent fillInIntent, Bundle options1, int taskId, Bundle options2, Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio,
int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, RemoteAnimationAdapter adapter, InstanceId instanceId) {
InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) -> "startIntentAndTaskWithLegacyTransition", (controller) ->
controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition( controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
pendingIntent, fillInIntent, options1, taskId, options2, options1, taskId, options2, splitPosition, splitRatio, adapter,
splitPosition, splitRatio, adapter, instanceId)); instanceId));
} }
@Override @Override
@@ -872,14 +965,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
} }
@Override @Override
public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent, public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
@SplitPosition int splitPosition, float splitRatio, float splitRatio, @Nullable RemoteTransition remoteTransition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) { InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentAndTask", executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
(controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent, (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId,
fillInIntent, options1, taskId, options2, splitPosition, splitRatio, options2, splitPosition, splitRatio, remoteTransition, instanceId));
remoteTransition, instanceId));
} }
@Override @Override

View File

@@ -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_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 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.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -428,6 +427,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Launches an activity into split. */ /** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) { @Nullable Bundle options) {
if (!ENABLE_SHELL_TRANSITIONS) {
startIntentLegacy(intent, fillInIntent, position, options);
return;
}
final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction wct = new WindowContainerTransaction();
final WindowContainerTransaction evictWct = new WindowContainerTransaction(); final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct); prepareEvictChildTasks(position, evictWct);
@@ -441,13 +445,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
prepareEnterSplitScreen(wct, null /* taskInfo */, position); prepareEnterSplitScreen(wct, null /* taskInfo */, position);
mSplitTransitions.startEnterTransition(transitType, wct, null, this, mSplitTransitions.startEnterTransition(transitType, wct, null, this,
aborted -> { null /* consumedCallback */,
// Switch the split position if launching as MULTIPLE_TASK failed.
if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
setSideStagePositionAnimated(
SplitLayout.reversePosition(mSideStagePosition));
}
} /* consumedCallback */,
(finishWct, finishT) -> { (finishWct, finishT) -> {
if (!evictWct.isEmpty()) { if (!evictWct.isEmpty()) {
finishWct.merge(evictWct, true); finishWct.merge(evictWct, true);
@@ -457,7 +455,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Launches an activity into split by legacy transition. */ /** Launches an activity into split by legacy transition. */
void startIntentLegacy(PendingIntent intent, Intent fillInIntent, 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(); final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct); prepareEvictChildTasks(position, evictWct);
@@ -473,12 +471,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
exitSplitScreen(mMainStage.getChildCount() == 0 exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
mSplitUnsupportedToast.show(); 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. // Do nothing when the animation was cancelled.
@@ -771,9 +763,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.evictInvisibleChildren(wct); mSideStage.evictInvisibleChildren(wct);
} }
Bundle resolveStartStage(@StageType int stage, Bundle resolveStartStage(@StageType int stage, @SplitPosition int position,
@SplitPosition int position, @androidx.annotation.Nullable Bundle options, @Nullable Bundle options, @Nullable WindowContainerTransaction wct) {
@androidx.annotation.Nullable WindowContainerTransaction wct) {
switch (stage) { switch (stage) {
case STAGE_TYPE_UNDEFINED: { case STAGE_TYPE_UNDEFINED: {
if (position != SPLIT_POSITION_UNDEFINED) { if (position != SPLIT_POSITION_UNDEFINED) {
@@ -844,9 +835,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
: mMainStage.getTopVisibleChildTaskId(); : mMainStage.getTopVisibleChildTaskId();
} }
void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) { void switchSplitPosition(String reason) {
if (mSideStagePosition == sideStagePosition) return; final SurfaceControl.Transaction t = mTransactionPool.acquire();
SurfaceControl.Transaction t = mTransactionPool.acquire();
mTempRect1.setEmpty(); mTempRect1.setEmpty();
final StageTaskListener topLeftStage = final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
@@ -886,6 +876,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
va.start(); 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, void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -1617,10 +1612,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override @Override
public void onDoubleTappedDivider() { public void onDoubleTappedDivider() {
setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition)); switchSplitPosition("double tap");
mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
mSplitLayout.isLandscape());
} }
@Override @Override

View File

@@ -16,18 +16,24 @@
package com.android.wm.shell.splitscreen; package com.android.wm.shell.splitscreen;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
@@ -35,6 +41,8 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
@@ -65,11 +73,11 @@ import com.android.wm.shell.transition.Transitions;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.util.Optional;
/** /**
* Tests for {@link SplitScreenController} * Tests for {@link SplitScreenController}
*/ */
@@ -91,18 +99,21 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Mock Transitions mTransitions; @Mock Transitions mTransitions;
@Mock TransactionPool mTransactionPool; @Mock TransactionPool mTransactionPool;
@Mock IconProvider mIconProvider; @Mock IconProvider mIconProvider;
@Mock Optional<RecentTasksController> mRecentTasks; @Mock StageCoordinator mStageCoordinator;
@Mock RecentTasksController mRecentTasks;
@Captor ArgumentCaptor<Intent> mIntentCaptor;
private SplitScreenController mSplitScreenController; private SplitScreenController mSplitScreenController;
@Before @Before
public void setup() { public void setup() {
assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit, mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue, mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController, mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool, mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
mIconProvider, mRecentTasks, mMainExecutor)); mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
} }
@Test @Test
@@ -148,58 +159,100 @@ public class SplitScreenControllerTests extends ShellTestCase {
} }
@Test @Test
public void testShouldAddMultipleTaskFlag_notInSplitScreen() { public void testStartIntent_appendsNoUserActionFlag() {
doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
// Verify launching the same activity returns true.
Intent startIntent = createStartIntent("startActivity"); Intent startIntent = createStartIntent("startActivity");
ActivityManager.RunningTaskInfo focusTaskInfo = PendingIntent pendingIntent =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
// Verify launching different activity returns false. mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
Intent diffIntent = createStartIntent("diffActivity");
focusTaskInfo = verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent); eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo(); assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag( mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
} }
@Test @Test
public void testShouldAddMultipleTaskFlag_inSplitScreen() { public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
doReturn(true).when(mSplitScreenController).isSplitScreenVisible(); doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
Intent startIntent = createStartIntent("startActivity"); Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
// Put the same component into focus task
ActivityManager.RunningTaskInfo focusTaskInfo =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
}
@Test
public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() {
doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
// Put the same component into focus task
ActivityManager.RunningTaskInfo focusTaskInfo =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
// Put the same component into a task in the background
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
}
@Test
public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
// Put the same component into another side of the split
doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
ActivityManager.RunningTaskInfo sameTaskInfo = ActivityManager.RunningTaskInfo sameTaskInfo =
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent); createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
Intent diffIntent = createStartIntent("diffActivity"); doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
ActivityManager.RunningTaskInfo differentTaskInfo = SPLIT_POSITION_BOTTOM_OR_RIGHT);
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent); // Put the same component into a task in the background
doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
.findTaskInBackground(any());
// Verify launching the same activity return false. mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
doReturn(sameTaskInfo).when(mSplitScreenController)
.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
// Verify launching the same activity as adjacent returns true. verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
doReturn(differentTaskInfo).when(mSplitScreenController) isNull());
.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); }
doReturn(sameTaskInfo).when(mSplitScreenController)
.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
// Verify launching different activity from adjacent returns false. @Test
doReturn(differentTaskInfo).when(mSplitScreenController) public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any());
doReturn(differentTaskInfo).when(mSplitScreenController) Intent startIntent = createStartIntent("startActivity");
.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT); PendingIntent pendingIntent =
assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag( PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
startIntent, SPLIT_POSITION_TOP_OR_LEFT)); // Put the same component into another side of the split
doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
ActivityManager.RunningTaskInfo sameTaskInfo =
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
SPLIT_POSITION_BOTTOM_OR_RIGHT);
mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
verify(mStageCoordinator).switchSplitPosition(anyString());
} }
private Intent createStartIntent(String activityName) { private Intent createStartIntent(String activityName) {

View File

@@ -27,6 +27,7 @@ import static android.app.ActivityManager.START_FLAG_DEBUG;
import static android.app.ActivityManager.START_FLAG_NATIVE_DEBUGGING; import static android.app.ActivityManager.START_FLAG_NATIVE_DEBUGGING;
import static android.app.ActivityManager.START_FLAG_TRACK_ALLOCATION; import static android.app.ActivityManager.START_FLAG_TRACK_ALLOCATION;
import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
import static android.app.WaitResult.INVALID_DELAY; import static android.app.WaitResult.INVALID_DELAY;
@@ -2592,6 +2593,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// Apply options to prevent pendingOptions be taken when scheduling // Apply options to prevent pendingOptions be taken when scheduling
// activity lifecycle transaction to make sure the override pending app // activity lifecycle transaction to make sure the override pending app
// transition will be applied immediately. // transition will be applied immediately.
if (activityOptions.getAnimationType() == ANIM_REMOTE_ANIMATION) {
targetActivity.mPendingRemoteAnimation =
activityOptions.getRemoteAnimationAdapter();
}
targetActivity.applyOptionsAnimation(); targetActivity.applyOptionsAnimation();
if (activityOptions != null && activityOptions.getLaunchCookie() != null) { if (activityOptions != null && activityOptions.getLaunchCookie() != null) {
targetActivity.mLaunchCookie = activityOptions.getLaunchCookie(); targetActivity.mLaunchCookie = activityOptions.getLaunchCookie();