diff options
7 files changed, 160 insertions, 34 deletions
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index aa5a2e091130..af2220933dae 100644 --- a/services/core/java/com/android/server/am/ActivityDisplay.java +++ b/services/core/java/com/android/server/am/ActivityDisplay.java @@ -291,7 +291,13 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> <T extends ActivityStack> T getOrCreateStack(@Nullable ActivityRecord r, @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, int activityType, boolean onTop) { - final int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType); + // First preference is the windowing mode in the activity options if set. + int windowingMode = (options != null) + ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED; + // Validate that our desired windowingMode will work under the current conditions. + // UNDEFINED windowing mode is a valid result and means that the new stack will inherit + // it's display's windowing mode. + windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType); return getOrCreateStack(windowingMode, activityType, onTop); } @@ -303,7 +309,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> * Creates a stack matching the input windowing mode and activity type on this display. * @param windowingMode The windowing mode the stack should be created in. If * {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will - * be created in {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}. + * inherit it's parent's windowing mode. * @param activityType The activityType the stack should be created in. If * {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will * be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}. @@ -549,7 +555,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> if (!otherStack.inSplitScreenSecondaryWindowingMode()) { continue; } - otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN, false /* animate */, + otherStack.setWindowingMode(WINDOWING_MODE_UNDEFINED, false /* animate */, false /* showRecents */, false /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */); } @@ -610,10 +616,14 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> return false; } + final int displayWindowingMode = getWindowingMode(); if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { return supportsSplitScreen - && WindowConfiguration.supportSplitScreenWindowingMode(activityType); + && WindowConfiguration.supportSplitScreenWindowingMode(activityType) + // Freeform windows and split-screen windows don't mix well, so prevent + // split windowing modes on freeform displays. + && displayWindowingMode != WINDOWING_MODE_FREEFORM; } if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) { @@ -626,6 +636,16 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> return true; } + /** + * Resolves the windowing mode that an {@link ActivityRecord} would be in if started on this + * display with the provided parameters. + * + * @param r The ActivityRecord in question. + * @param options Options to start with. + * @param task The task within-which the activity would start. + * @param activityType The type of activity to start. + * @return The resolved (not UNDEFINED) windowing-mode that the activity would be in. + */ int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options, @Nullable TaskRecord task, int activityType) { @@ -647,7 +667,9 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> windowingMode = getWindowingMode(); } } - return validateWindowingMode(windowingMode, r, task, activityType); + windowingMode = validateWindowingMode(windowingMode, r, task, activityType); + return windowingMode != WINDOWING_MODE_UNDEFINED + ? windowingMode : WINDOWING_MODE_FULLSCREEN; } /** @@ -684,23 +706,21 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> final boolean inSplitScreenMode = hasSplitScreenPrimaryStack(); if (!inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) { - // Switch to fullscreen windowing mode if we are not in split-screen mode and we are + // Switch to the display's windowing mode if we are not in split-screen mode and we are // trying to launch in split-screen secondary. - windowingMode = WINDOWING_MODE_FULLSCREEN; - } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN + windowingMode = WINDOWING_MODE_UNDEFINED; + } else if (inSplitScreenMode && (windowingMode == WINDOWING_MODE_FULLSCREEN + || windowingMode == WINDOWING_MODE_UNDEFINED) && supportsSplitScreen) { windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; } if (windowingMode != WINDOWING_MODE_UNDEFINED && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen, - supportsFreeform, supportsPip, activityType)) { + supportsFreeform, supportsPip, activityType)) { return windowingMode; } - // Try to use the display's windowing mode otherwise fallback to fullscreen. - windowingMode = getWindowingMode(); - return windowingMode != WINDOWING_MODE_UNDEFINED - ? windowingMode : WINDOWING_MODE_FULLSCREEN; + return WINDOWING_MODE_UNDEFINED; } /** diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 355d890f1bd1..1209bde535e9 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -347,6 +347,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai /** The attached Display's unique identifier, or -1 if detached */ int mDisplayId; + /** Stores the override windowing-mode from before a transient mode change (eg. split) */ + private int mRestoreOverrideWindowingMode = WINDOWING_MODE_UNDEFINED; + private final SparseArray<Rect> mTmpBounds = new SparseArray<>(); private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>(); private final Rect mTmpRect2 = new Rect(); @@ -531,16 +534,48 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */); } + /** + * A transient windowing mode is one which activities enter into temporarily. Examples of this + * are Split window modes and pip. Non-transient modes are modes that displays can adopt. + * + * @param windowingMode the windowingMode to test for transient-ness. + * @return {@code true} if the windowing mode is transient, {@code false} otherwise. + */ + private static boolean isTransientWindowingMode(int windowingMode) { + // TODO(b/114842032): add PIP if/when it uses mode transitions instead of task reparenting + return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + } + + /** + * Specialization of {@link #setWindowingMode(int)} for this subclass. + * + * @param preferredWindowingMode the preferred windowing mode. This may not be honored depending + * on the state of things. For example, WINDOWING_MODE_UNDEFINED will resolve to the + * previous non-transient mode if this stack is currently in a transient mode. + * @param animate Can be used to prevent animation. + * @param showRecents Controls whether recents is shown on the other side of a split while + * entering split mode. + * @param enteringSplitScreenMode {@code true} if entering split mode. + * @param deferEnsuringVisibility Whether visibility updates are deferred. This is set when + * many operations (which can effect visibility) are being performed in bulk. + */ void setWindowingMode(int preferredWindowingMode, boolean animate, boolean showRecents, boolean enteringSplitScreenMode, boolean deferEnsuringVisibility) { final boolean creating = mWindowContainerController == null; final int currentMode = getWindowingMode(); + final int currentOverrideMode = getOverrideWindowingMode(); final ActivityDisplay display = getDisplay(); final TaskRecord topTask = topTask(); final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack(); - mTmpOptions.setLaunchWindowingMode(preferredWindowingMode); - int windowingMode = preferredWindowingMode; + if (preferredWindowingMode == WINDOWING_MODE_UNDEFINED + && isTransientWindowingMode(currentMode)) { + // Leaving a transient mode. Interpret UNDEFINED as "restore" + windowingMode = mRestoreOverrideWindowingMode; + } + mTmpOptions.setLaunchWindowingMode(windowingMode); + // Need to make sure windowing mode is supported. If we in the process of creating the stack // no need to resolve the windowing mode again as it is already resolved to the right mode. if (!creating) { @@ -550,8 +585,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (splitScreenStack == this && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { // Resolution to split-screen secondary for the primary split-screen stack means - // we want to go fullscreen. - windowingMode = WINDOWING_MODE_FULLSCREEN; + // we want to leave split-screen mode. + windowingMode = mRestoreOverrideWindowingMode; } final boolean alreadyInSplitScreenMode = display.hasSplitScreenPrimaryStack(); @@ -570,21 +605,33 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // doesn't support split-screen mode, go ahead an dismiss split-screen and display a // warning toast about it. mService.getTaskChangeNotificationController().notifyActivityDismissingDockedStack(); - display.getSplitScreenPrimaryStack().setWindowingMode(WINDOWING_MODE_FULLSCREEN, + display.getSplitScreenPrimaryStack().setWindowingMode(WINDOWING_MODE_UNDEFINED, false /* animate */, false /* showRecents */, false /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */); } } if (currentMode == windowingMode) { - // You are already in the window mode silly... + // You are already in the window mode, so we can skip most of the work below. However, + // it's possible that we have inherited the current windowing mode from a parent. So, + // fulfill this method's contract by setting the override mode directly. + getOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); return; } final WindowManagerService wm = mService.mWindowManager; final ActivityRecord topActivity = getTopActivity(); - if (sendNonResizeableNotification && windowingMode != WINDOWING_MODE_FULLSCREEN + // For now, assume that the Stack's windowing mode is what will actually be used + // by it's activities. In the future, there may be situations where this doesn't + // happen; so at that point, this message will need to handle that. + int likelyResolvedMode = windowingMode; + if (windowingMode == WINDOWING_MODE_UNDEFINED) { + final ConfigurationContainer parent = getParent(); + likelyResolvedMode = parent != null ? parent.getWindowingMode() + : WINDOWING_MODE_FULLSCREEN; + } + if (sendNonResizeableNotification && likelyResolvedMode != WINDOWING_MODE_FULLSCREEN && topActivity != null && topActivity.isNonResizableOrForcedResizable() && !topActivity.noDisplay) { // Inform the user that they are starting an app that may not work correctly in @@ -625,6 +672,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai + " while there is already one isn't currently supported"); //return; } + if (isTransientWindowingMode(windowingMode) && !isTransientWindowingMode(currentMode)) { + mRestoreOverrideWindowingMode = currentOverrideMode; + } mTmpRect2.setEmpty(); if (windowingMode != WINDOWING_MODE_FULLSCREEN) { @@ -674,13 +724,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // display, so they should be considered compatible. activityType = ACTIVITY_TYPE_STANDARD; } - final ActivityDisplay display = getDisplay(); - if (display != null && activityType == ACTIVITY_TYPE_STANDARD - && windowingMode == WINDOWING_MODE_UNDEFINED) { - // Standard activity types will mostly take on the windowing mode of the display if one - // isn't specified, so look-up a compatible stack based on the display's windowing mode. - windowingMode = display.getWindowingMode(); - } return super.isCompatible(windowingMode, activityType); } @@ -1085,7 +1128,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai * behind the home stack. Exit split screen in this case. */ if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - setWindowingMode(WINDOWING_MODE_FULLSCREEN); + setWindowingMode(WINDOWING_MODE_UNDEFINED); } getDisplay().positionChildAtBottom(this); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 1ffdc6738c1d..869be482ad77 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -2817,8 +2817,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (!otherStack.inSplitScreenSecondaryWindowingMode()) { continue; } - resizeStackLocked(otherStack, null, null, null, PRESERVE_WINDOWS, - true /* allowResizeInDockedMode */, DEFER_RESUME); + otherStack.setWindowingMode(WINDOWING_MODE_UNDEFINED); } // Also disable docked stack resizing since we have manually adjusted the diff --git a/services/core/java/com/android/server/am/ActivityTaskManagerService.java b/services/core/java/com/android/server/am/ActivityTaskManagerService.java index 7eadcb307028..212844ab85ce 100644 --- a/services/core/java/com/android/server/am/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/am/ActivityTaskManagerService.java @@ -3338,7 +3338,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + stack.setWindowingMode(WINDOWING_MODE_UNDEFINED); } } finally { Binder.restoreCallingIdentity(ident); diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index f2ce63c23cb6..64553a8c7ade 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -29,6 +29,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECOND import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; + import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION; import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION; import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGURATION; @@ -295,6 +296,10 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return mFullConfiguration.windowConfiguration.getWindowingMode(); } + public int getOverrideWindowingMode() { + return mOverrideConfiguration.windowConfiguration.getWindowingMode(); + } + /** Sets the windowing mode for the configuration container. */ public void setWindowingMode(/*@WindowConfiguration.WindowingMode*/ int windowingMode) { mTmpConfig.setTo(getOverrideConfiguration()); @@ -513,7 +518,8 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { final String childPrefix = prefix + " "; pw.println(getName() + " type=" + activityTypeToString(getActivityType()) - + " mode=" + windowingModeToString(getWindowingMode())); + + " mode=" + windowingModeToString(getWindowingMode()) + + " override-mode=" + windowingModeToString(getOverrideWindowingMode())); for (int i = getChildCount() - 1; i >= 0; --i) { final E cc = getChildAt(i); pw.print(childPrefix + "#" + i + " "); diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java index 59b08909bb76..22de662138fe 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java @@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.server.am.ActivityStack.ActivityState.PAUSING; import static com.android.server.am.ActivityStack.ActivityState.RESUMED; @@ -73,7 +74,7 @@ public class ActivityStackTests extends ActivityTestsBase { mService = createActivityTaskManagerService(); mSupervisor = mService.mStackSupervisor; mDefaultDisplay = mService.mStackSupervisor.getDefaultDisplay(); - mStack = mDefaultDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mStack = mDefaultDisplay.createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); mTask = new TaskBuilder(mSupervisor).setStack(mStack).build(); } @@ -140,7 +141,7 @@ public class ActivityStackTests extends ActivityTestsBase { } @Test - public void testPrimarySplitScreenToFullscreenWhenMovedToBack() throws Exception { + public void testPrimarySplitScreenRestoresWhenMovedToBack() throws Exception { // Create primary splitscreen stack. This will create secondary stacks and places the // existing fullscreen stack on the bottom. final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( @@ -158,6 +159,60 @@ public class ActivityStackTests extends ActivityTestsBase { // Ensure no longer in splitscreen. assertEquals(primarySplitScreen.getWindowingMode(), WINDOWING_MODE_FULLSCREEN); + + // Ensure that the override mode is restored to undefined + assertEquals(primarySplitScreen.getOverrideWindowingMode(), WINDOWING_MODE_UNDEFINED); + } + + @Test + public void testPrimarySplitScreenRestoresPreviousWhenMovedToBack() throws Exception { + // This time, start with a fullscreen activitystack + final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + primarySplitScreen.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + + // Assert windowing mode. + assertEquals(primarySplitScreen.getWindowingMode(), WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + + // Move primary to back. + primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack", + null /* task */); + + // Assert that stack is at the bottom. + assertEquals(mDefaultDisplay.getIndexOf(primarySplitScreen), 0); + + // Ensure that the override mode is restored to what it was (fullscreen) + assertEquals(primarySplitScreen.getOverrideWindowingMode(), WINDOWING_MODE_FULLSCREEN); + } + + @Test + public void testStackInheritsDisplayWindowingMode() throws Exception { + final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); + assertEquals(WINDOWING_MODE_UNDEFINED, primarySplitScreen.getOverrideWindowingMode()); + + mDefaultDisplay.setWindowingMode(WINDOWING_MODE_FREEFORM); + assertEquals(WINDOWING_MODE_FREEFORM, primarySplitScreen.getWindowingMode()); + assertEquals(WINDOWING_MODE_UNDEFINED, primarySplitScreen.getOverrideWindowingMode()); + } + + @Test + public void testStackOverridesDisplayWindowingMode() throws Exception { + final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); + assertEquals(WINDOWING_MODE_UNDEFINED, primarySplitScreen.getOverrideWindowingMode()); + + primarySplitScreen.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + // setting windowing mode should still work even though resolved mode is already fullscreen + assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getOverrideWindowingMode()); + + mDefaultDisplay.setWindowingMode(WINDOWING_MODE_FREEFORM); + assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index aef5537badeb..bb8e5c555481 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -520,6 +520,9 @@ public class ActivityTestsBase { private final ActivityStackSupervisor mSupervisor; TestActivityDisplay(ActivityStackSupervisor supervisor, int displayId) { super(supervisor, displayId); + // Normally this comes from display-properties as exposed by WM. Without that, just + // hard-code to FULLSCREEN for tests. + setWindowingMode(WINDOWING_MODE_FULLSCREEN); mSupervisor = supervisor; } |