Merge "Make multi-instances split opt-in based" into tm-qpr-dev
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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() {
|
||||
@@ -471,72 +521,116 @@ 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 (taskInfo != null) {
|
||||
startTask(taskInfo.taskId, position, options);
|
||||
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");
|
||||
} else if (isSplitScreenVisible()) {
|
||||
mStageCoordinator.switchSplitPosition("startIntent");
|
||||
return;
|
||||
}
|
||||
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);
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
final ComponentName launchingActivity = pendingIntent.getIntent().getComponent();
|
||||
if (launchingActivity == null) 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)) {
|
||||
return false;
|
||||
if (taskId != INVALID_TASK_ID) {
|
||||
final ActivityManager.RunningTaskInfo taskInfo =
|
||||
mTaskOrganizer.getRunningTaskInfo(taskId);
|
||||
if (taskInfo != null) {
|
||||
return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
|
||||
}
|
||||
|
||||
// Allow users to start a new instance the same to adjacent side.
|
||||
final ActivityManager.RunningTaskInfo pairedTaskInfo =
|
||||
getTaskInfo(SplitLayout.reversePosition(position));
|
||||
final ComponentName pairedActivity = pairedTaskInfo != null
|
||||
? pairedTaskInfo.baseIntent.getComponent() : null;
|
||||
return Objects.equals(launchingActivity, pairedActivity);
|
||||
return false;
|
||||
}
|
||||
|
||||
final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
|
||||
if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
|
||||
return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
? pairedTaskInfo.baseIntent.getComponent() : null;
|
||||
return Objects.equals(launchingActivity, pairedActivity);
|
||||
}
|
||||
|
||||
@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;
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
@@ -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) {
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
@@ -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
|
||||
|
||||
@@ -16,18 +16,24 @@
|
||||
|
||||
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.WINDOWING_MODE_FULLSCREEN;
|
||||
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_TOP_OR_LEFT;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
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.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -35,6 +41,8 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
@@ -65,11 +73,11 @@ import com.android.wm.shell.transition.Transitions;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Tests for {@link SplitScreenController}
|
||||
*/
|
||||
@@ -91,18 +99,21 @@ public class SplitScreenControllerTests extends ShellTestCase {
|
||||
@Mock Transitions mTransitions;
|
||||
@Mock TransactionPool mTransactionPool;
|
||||
@Mock IconProvider mIconProvider;
|
||||
@Mock Optional<RecentTasksController> mRecentTasks;
|
||||
@Mock StageCoordinator mStageCoordinator;
|
||||
@Mock RecentTasksController mRecentTasks;
|
||||
@Captor ArgumentCaptor<Intent> mIntentCaptor;
|
||||
|
||||
private SplitScreenController mSplitScreenController;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
|
||||
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
|
||||
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
|
||||
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
|
||||
mIconProvider, mRecentTasks, mMainExecutor));
|
||||
mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -148,58 +159,100 @@ public class SplitScreenControllerTests extends ShellTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
|
||||
doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
|
||||
doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
|
||||
|
||||
// Verify launching the same activity returns true.
|
||||
public void testStartIntent_appendsNoUserActionFlag() {
|
||||
Intent startIntent = createStartIntent("startActivity");
|
||||
ActivityManager.RunningTaskInfo focusTaskInfo =
|
||||
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
|
||||
doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
|
||||
assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
|
||||
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
|
||||
PendingIntent pendingIntent =
|
||||
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
|
||||
|
||||
// Verify launching different activity returns false.
|
||||
Intent diffIntent = createStartIntent("diffActivity");
|
||||
focusTaskInfo =
|
||||
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent);
|
||||
doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
|
||||
assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
|
||||
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
|
||||
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_NO_USER_ACTION,
|
||||
mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldAddMultipleTaskFlag_inSplitScreen() {
|
||||
doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
|
||||
public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
|
||||
doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(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());
|
||||
|
||||
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 =
|
||||
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
|
||||
Intent diffIntent = createStartIntent("diffActivity");
|
||||
ActivityManager.RunningTaskInfo differentTaskInfo =
|
||||
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent);
|
||||
doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
|
||||
SPLIT_POSITION_BOTTOM_OR_RIGHT);
|
||||
// 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.
|
||||
doReturn(sameTaskInfo).when(mSplitScreenController)
|
||||
.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
|
||||
assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
|
||||
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
|
||||
mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
|
||||
|
||||
// Verify launching the same activity as adjacent returns true.
|
||||
doReturn(differentTaskInfo).when(mSplitScreenController)
|
||||
.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(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
|
||||
isNull());
|
||||
}
|
||||
|
||||
// Verify launching different activity from adjacent returns false.
|
||||
doReturn(differentTaskInfo).when(mSplitScreenController)
|
||||
.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
|
||||
doReturn(differentTaskInfo).when(mSplitScreenController)
|
||||
.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
|
||||
assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
|
||||
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
|
||||
@Test
|
||||
public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
|
||||
doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(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 =
|
||||
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) {
|
||||
|
||||
@@ -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_TRACK_ALLOCATION;
|
||||
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_SPLIT_SCREEN;
|
||||
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
|
||||
// activity lifecycle transaction to make sure the override pending app
|
||||
// transition will be applied immediately.
|
||||
if (activityOptions.getAnimationType() == ANIM_REMOTE_ANIMATION) {
|
||||
targetActivity.mPendingRemoteAnimation =
|
||||
activityOptions.getRemoteAnimationAdapter();
|
||||
}
|
||||
targetActivity.applyOptionsAnimation();
|
||||
if (activityOptions != null && activityOptions.getLaunchCookie() != null) {
|
||||
targetActivity.mLaunchCookie = activityOptions.getLaunchCookie();
|
||||
|
||||
Reference in New Issue
Block a user