diff options
5 files changed, 238 insertions, 64 deletions
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 47034a6df8a7..7cc439138527 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1029,10 +1029,18 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { } /** + * Returns true if the activity has maximum or minimum aspect ratio. + * @hide + */ + public boolean hasFixedAspectRatio() { + return maxAspectRatio != 0 || minAspectRatio != 0; + } + + /** * Returns true if the activity's orientation is fixed. * @hide */ - boolean isFixedOrientation() { + public boolean isFixedOrientation() { return isFixedOrientationLandscape() || isFixedOrientationPortrait() || screenOrientation == SCREEN_ORIENTATION_LOCKED; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 4faf910f52ea..0251efb872bc 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -75,12 +75,12 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; import static android.content.pm.ActivityInfo.isFixedOrientationPortrait; import static android.content.res.Configuration.EMPTY; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.content.res.Configuration.UI_MODE_TYPE_MASK; import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET; import static android.os.Build.VERSION_CODES.HONEYCOMB; @@ -2610,10 +2610,6 @@ final class ActivityRecord extends ConfigurationContainer { } } - int getRequestedOrientation() { - return getOrientation(); - } - void setRequestedOrientation(int requestedOrientation) { setOrientation(requestedOrientation, mayFreezeScreenLocked(app)); mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged( @@ -2641,7 +2637,7 @@ final class ActivityRecord extends ConfigurationContainer { int getOrientation() { if (mAppWindowToken == null) { - return SCREEN_ORIENTATION_UNSPECIFIED; + return info.screenOrientation; } return mAppWindowToken.getOrientationIgnoreVisibility(); @@ -2677,25 +2673,92 @@ final class ActivityRecord extends ConfigurationContainer { mLastReportedConfiguration.setConfiguration(global, override); } + /** + * Get the configuration orientation by the requested screen orientation + * ({@link ActivityInfo.ScreenOrientation}) of this activity. + * + * @return orientation in ({@link #ORIENTATION_LANDSCAPE}, {@link #ORIENTATION_PORTRAIT}, + * {@link #ORIENTATION_UNDEFINED}). + */ + int getRequestedConfigurationOrientation() { + final int screenOrientation = getOrientation(); + if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) { + // NOSENSOR means the display's "natural" orientation, so return that. + final ActivityDisplay display = getDisplay(); + if (display != null && display.mDisplayContent != null) { + return display.mDisplayContent.getNaturalOrientation(); + } + } else if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) { + // LOCKED means the activity's orientation remains unchanged, so return existing value. + return getConfiguration().orientation; + } else if (isFixedOrientationLandscape(screenOrientation)) { + return ORIENTATION_LANDSCAPE; + } else if (isFixedOrientationPortrait(screenOrientation)) { + return ORIENTATION_PORTRAIT; + } + return ORIENTATION_UNDEFINED; + } + + /** + * Indicates the activity will keep the bounds and screen configuration when it was first + * launched, no matter how its parent changes. + * + * @return {@code true} if this activity is declared as non-resizable and fixed orientation or + * aspect ratio. + */ + private boolean inSizeCompatMode() { + return !isResizeable() && (info.isFixedOrientation() || info.hasFixedAspectRatio()) + // The configuration of non-standard type should be enforced by system. + && isActivityTypeStandard() + && !mAtmService.mForceResizableActivities; + } + // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. private void updateOverrideConfiguration() { + final boolean inSizeCompatMode = inSizeCompatMode(); + if (inSizeCompatMode) { + if (!matchParentBounds()) { + // The override configuration is set only once in size compatible mode. + return; + } + if (!hasProcess() && !isConfigurationCompatible(task.getConfiguration())) { + // Don't compute when launching in fullscreen and the fixed orientation is not the + // current orientation. It is more accurately to compute the override bounds from + // the updated configuration after the fixed orientation is applied. + return; + } + } + computeBounds(mTmpBounds); + if (inSizeCompatMode && mTmpBounds.isEmpty()) { + mTmpBounds.set(task.getWindowConfiguration().getBounds()); + } if (mTmpBounds.equals(getRequestedOverrideBounds())) { + // The bounds is not changed or the activity is resizable (both the 2 bounds are empty). return; } - setBounds(mTmpBounds); - - // Bounds changed...update configuration to match. - if (!matchParentBounds()) { - mTmpConfig.setTo(getRequestedOverrideConfiguration()); - task.computeConfigResourceOverrides(mTmpConfig, task.getParent().getConfiguration()); - } else { - mTmpConfig.unset(); + final Configuration overrideConfig = mTmpConfig; + overrideConfig.unset(); + if (!mTmpBounds.isEmpty()) { + overrideConfig.windowConfiguration.setBounds(mTmpBounds); + if (inSizeCompatMode) { + // Ensure the screen related fields are set. It is used to prevent activity relaunch + // when moving between displays. For screenWidthDp and screenWidthDp, because they + // are relative to bounds and density, they will be calculated in + // {@link TaskRecord#computeConfigResourceOverrides} and the result will also be + // relatively fixed. + final Configuration srcConfig = task.getConfiguration(); + overrideConfig.colorMode = srcConfig.colorMode; + overrideConfig.densityDpi = srcConfig.densityDpi; + overrideConfig.screenLayout = srcConfig.screenLayout; + // The smallest screen width is the short side of screen bounds. Because the bounds + // and density won't be changed, smallestScreenWidthDp is also fixed. + overrideConfig.smallestScreenWidthDp = srcConfig.smallestScreenWidthDp; + } } - - onRequestedOverrideConfigurationChanged(mTmpConfig); + onRequestedOverrideConfigurationChanged(overrideConfig); } @Override @@ -2707,6 +2770,86 @@ final class ActivityRecord extends ConfigurationContainer { // layout traversals. mConfigurationSeq = Math.max(++mConfigurationSeq, 1); getResolvedOverrideConfiguration().seq = mConfigurationSeq; + + if (matchParentBounds()) { + return; + } + + final Configuration resolvedConfig = getResolvedOverrideConfiguration(); + if (!inSizeCompatMode()) { + computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, + ORIENTATION_UNDEFINED, true /* insideParentBounds */); + return; + } + + final Configuration displayConfig = getDisplay().getConfiguration(); + int orientation = getConfiguration().orientation; + if (orientation != displayConfig.orientation && isConfigurationCompatible(displayConfig)) { + // The activity is compatible to apply the orientation change or it requests different + // fixed orientation. + orientation = displayConfig.orientation; + } else { + if (resolvedConfig.windowConfiguration.getAppBounds() != null) { + // Keep the computed resolved override configuration. + return; + } + final int requestedOrientation = getRequestedConfigurationOrientation(); + if (requestedOrientation != ORIENTATION_UNDEFINED) { + orientation = requestedOrientation; + } + } + + // Adjust the bounds to match the current orientation. + if (orientation != ORIENTATION_UNDEFINED) { + final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); + final int longSide = Math.max(resolvedBounds.height(), resolvedBounds.width()); + final int shortSide = Math.min(resolvedBounds.height(), resolvedBounds.width()); + final boolean toBeLandscape = orientation == ORIENTATION_LANDSCAPE; + final int width = toBeLandscape ? longSide : shortSide; + final int height = toBeLandscape ? shortSide : longSide; + // Assume the bounds is always started from zero because the size may be bigger than its + // parent (task ~ display). The actual letterboxing will be done by surface offset. + resolvedBounds.set(0, 0, width, height); + } + + // In size compatible mode, activity is allowed to have larger bounds than its parent. + computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, orientation, + false /* insideParentBounds */); + } + + private void computeConfigResourceOverrides(Configuration inOutConfig, + Configuration parentConfig, int orientation, boolean insideParentBounds) { + // Set the real orientation or undefined value to ensure the output orientation won't be the + // old value. Also reset app bounds so it will be updated according to bounds. + inOutConfig.orientation = orientation; + final Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + if (outAppBounds != null) { + outAppBounds.setEmpty(); + } + + // TODO(b/112288258): Remove below calculation because the position information in bounds + // will be replaced by the offset of surface. + final Rect appBounds = parentConfig.windowConfiguration.getAppBounds(); + if (appBounds != null) { + final Rect outBounds = inOutConfig.windowConfiguration.getBounds(); + final int activityWidth = outBounds.width(); + final int navBarPosition = mAtmService.mWindowManager.getNavBarPosition(getDisplayId()); + if (navBarPosition == NAV_BAR_LEFT) { + // Position the activity frame on the opposite side of the nav bar. + outBounds.left = appBounds.right - activityWidth; + outBounds.right = appBounds.right; + } else if (navBarPosition == NAV_BAR_RIGHT) { + // Position the activity frame on the opposite side of the nav bar. + outBounds.left = 0; + outBounds.right = activityWidth + appBounds.left; + } else if (appBounds.width() > activityWidth) { + // Horizontally center the frame. + outBounds.left = appBounds.left + (appBounds.width() - activityWidth) / 2; + outBounds.right = outBounds.left + activityWidth; + } + } + + task.computeConfigResourceOverrides(inOutConfig, parentConfig, insideParentBounds); } @Override @@ -2739,8 +2882,7 @@ final class ActivityRecord extends ConfigurationContainer { /** Returns true if the configuration is compatible with this activity. */ boolean isConfigurationCompatible(Configuration config) { - final int orientation = mAppWindowToken != null - ? getOrientation() : info.screenOrientation; + final int orientation = getOrientation(); if (isFixedOrientationPortrait(orientation) && config.orientation != ORIENTATION_PORTRAIT) { return false; @@ -2822,21 +2964,6 @@ final class ActivityRecord extends ConfigurationContainer { // away later in StackWindowController.adjustConfigurationForBounds(). Otherwise, the app // bounds would end up too small. outBounds.set(0, 0, activityWidth + appBounds.left, activityHeight + appBounds.top); - - final int navBarPosition = mAtmService.mWindowManager.getNavBarPosition(getDisplayId()); - if (navBarPosition == NAV_BAR_LEFT) { - // Position the activity frame on the opposite side of the nav bar. - outBounds.left = appBounds.right - activityWidth; - outBounds.right = appBounds.right; - } else if (navBarPosition == NAV_BAR_RIGHT) { - // Position the activity frame on the opposite side of the nav bar. - outBounds.left = 0; - outBounds.right = activityWidth + appBounds.left; - } else { - // Horizontally center the frame. - outBounds.left = appBounds.left + (containingAppWidth - activityWidth) / 2; - outBounds.right = outBounds.left + activityWidth; - } } /** diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 7e9979da80f7..c1c2b57873ee 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1720,7 +1720,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (r == null) { return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } - return r.getRequestedOrientation(); + return r.getOrientation(); } } diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 4aca9c8a1520..59d7560fc5b0 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -1289,22 +1289,7 @@ class TaskRecord extends ConfigurationContainer { || top == null) { return getRequestedOverrideConfiguration().orientation; } - int screenOrientation = top.getOrientation(); - if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) { - // NOSENSOR means the display's "natural" orientation, so return that. - ActivityDisplay display = mStack != null ? mStack.getDisplay() : null; - if (display != null && display.mDisplayContent != null) { - return mStack.getDisplay().mDisplayContent.getNaturalOrientation(); - } - } else if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) { - // LOCKED means the activity's orientation remains unchanged, so return existing value. - return top.getConfiguration().orientation; - } else if (ActivityInfo.isFixedOrientationLandscape(screenOrientation)) { - return ORIENTATION_LANDSCAPE; - } else if (ActivityInfo.isFixedOrientationPortrait(screenOrientation)) { - return ORIENTATION_PORTRAIT; - } - return ORIENTATION_UNDEFINED; + return top.getRequestedConfigurationOrientation(); } /** @@ -2085,6 +2070,11 @@ class TaskRecord extends ConfigurationContainer { return Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; } + void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, + @NonNull Configuration parentConfig) { + computeConfigResourceOverrides(inOutConfig, parentConfig, true /* insideParentBounds */); + } + /** * Calculates configuration values used by the client to get resources. This should be run * using app-facing bounds (bounds unmodified by animations or transient interactions). @@ -2094,7 +2084,7 @@ class TaskRecord extends ConfigurationContainer { * just be inherited from the parent configuration. **/ void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig) { + @NonNull Configuration parentConfig, boolean insideParentBounds) { int windowingMode = inOutConfig.windowConfiguration.getWindowingMode(); if (windowingMode == WINDOWING_MODE_UNDEFINED) { windowingMode = parentConfig.windowConfiguration.getWindowingMode(); @@ -2112,7 +2102,7 @@ class TaskRecord extends ConfigurationContainer { inOutConfig.windowConfiguration.setAppBounds(bounds); outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); } - if (windowingMode != WINDOWING_MODE_FREEFORM) { + if (insideParentBounds && windowingMode != WINDOWING_MODE_FREEFORM) { final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds(); if (parentAppBounds != null && !parentAppBounds.isEmpty()) { outAppBounds.intersect(parentAppBounds); @@ -2121,7 +2111,7 @@ class TaskRecord extends ConfigurationContainer { if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - if (mStack != null) { + if (insideParentBounds && mStack != null) { final DisplayInfo di = new DisplayInfo(); mStack.getDisplay().mDisplay.getDisplayInfo(di); @@ -2136,12 +2126,16 @@ class TaskRecord extends ConfigurationContainer { } if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { - inOutConfig.screenWidthDp = Math.min((int) (mTmpStableBounds.width() / density), - parentConfig.screenWidthDp); + final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density); + inOutConfig.screenWidthDp = insideParentBounds + ? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp) + : overrideScreenWidthDp; } if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - inOutConfig.screenHeightDp = Math.min((int) (mTmpStableBounds.height() / density), - parentConfig.screenHeightDp); + final int overrideScreenHeightDp = (int) (mTmpStableBounds.height() / density); + inOutConfig.screenHeightDp = insideParentBounds + ? Math.min(overrideScreenHeightDp, parentConfig.screenWidthDp) + : overrideScreenHeightDp; } if (inOutConfig.smallestScreenWidthDp @@ -2163,7 +2157,7 @@ class TaskRecord extends ConfigurationContainer { if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) - ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; + ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; } if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) { // For calculating screen layout, we need to use the non-decor inset screen area for the diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 4073ff106592..8c36905d8422 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -183,11 +183,14 @@ public class ActivityRecordTests extends ActivityTestsBase { .thenReturn(navBarPosition); mTask.getConfiguration().windowConfiguration.setAppBounds(taskBounds); mActivity.info.maxAspectRatio = aspectRatio; - mActivity.ensureActivityConfiguration( - 0 /* globalChanges */, false /* preserveWindow */); + ensureActivityConfiguration(); assertEquals(expectedActivityBounds, mActivity.getBounds()); } + private void ensureActivityConfiguration() { + mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); + } + @Test public void testCanBeLaunchedOnDisplay() { mService.mSupportsMultiWindow = true; @@ -281,7 +284,7 @@ public class ActivityRecordTests extends ActivityTestsBase { mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE; - mActivity.ensureActivityConfiguration(0, false, false); + ensureActivityConfiguration(); assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE, mActivity.mRelaunchReason); @@ -305,7 +308,7 @@ public class ActivityRecordTests extends ActivityTestsBase { mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE; - mActivity.ensureActivityConfiguration(0, false, false); + ensureActivityConfiguration(); assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE, mActivity.mRelaunchReason); @@ -327,7 +330,7 @@ public class ActivityRecordTests extends ActivityTestsBase { mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE; - mActivity.ensureActivityConfiguration(0, false, false); + ensureActivityConfiguration(); assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_NONE, mActivity.mRelaunchReason); @@ -433,4 +436,46 @@ public class ActivityRecordTests extends ActivityTestsBase { stack.getDisplay().removeChild(stack); } } + + @Test + public void testFixedScreenConfigurationWhenMovingToDisplay() { + // Initialize different bounds on a new display. + final ActivityDisplay newDisplay = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP); + newDisplay.setBounds(0, 0, 1000, 2000); + newDisplay.getConfiguration().densityDpi = 300; + + mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds()); + mTask.getConfiguration().densityDpi = 200; + when(mActivity.mAppWindowToken.getOrientationIgnoreVisibility()).thenReturn( + ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + mActivity.info.maxAspectRatio = 1.5f; + ensureActivityConfiguration(); + final Rect originalBounds = new Rect(mActivity.getBounds()); + final int originalDpi = mActivity.getConfiguration().densityDpi; + + // Move the non-resizable activity to the new display. + mStack.reparent(newDisplay, true /* onTop */, false /* displayRemoved */); + ensureActivityConfiguration(); + + assertEquals(originalBounds, mActivity.getBounds()); + assertEquals(originalDpi, mActivity.getConfiguration().densityDpi); + } + + @Test + public void testFixedScreenBoundsWhenDisplaySizeChanged() { + when(mActivity.mAppWindowToken.getOrientationIgnoreVisibility()).thenReturn( + ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds()); + mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + ensureActivityConfiguration(); + final Rect originalBounds = new Rect(mActivity.getBounds()); + + // Change the size of current display. + mStack.getDisplay().setBounds(0, 0, 1000, 2000); + ensureActivityConfiguration(); + + assertEquals(originalBounds, mActivity.getBounds()); + } } |