Loading services/core/java/com/android/server/wm/ActivityRecord.java +10 −7 Original line number Diff line number Diff line Loading @@ -728,9 +728,10 @@ final class ActivityRecord extends ConfigurationContainer { mLastReportedMultiWindowMode = inPictureInPictureMode; final Configuration newConfig = new Configuration(); if (targetStackBounds != null && !targetStackBounds.isEmpty()) { task.computeResolvedOverrideConfiguration(newConfig, task.getParent().getConfiguration(), task.getRequestedOverrideConfiguration()); newConfig.setTo(task.getRequestedOverrideConfiguration()); Rect outBounds = newConfig.windowConfiguration.getBounds(); task.adjustForMinimalTaskDimensions(outBounds, outBounds); task.computeConfigResourceOverrides(newConfig, task.getParent().getConfiguration()); } schedulePictureInPictureModeChanged(newConfig); scheduleMultiWindowModeChanged(newConfig); Loading Loading @@ -2503,7 +2504,8 @@ final class ActivityRecord extends ConfigurationContainer { return; } final IBinder binder = freezeScreenIfNeeded ? appToken.asBinder() : null; final IBinder binder = (freezeScreenIfNeeded && appToken != null) ? appToken.asBinder() : null; mAppWindowToken.setOrientation(requestedOrientation, binder, this); } Loading Loading @@ -2547,7 +2549,6 @@ final class ActivityRecord extends ConfigurationContainer { // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. private void updateOverrideConfiguration() { mTmpConfig.unset(); computeBounds(mTmpBounds); if (mTmpBounds.equals(getRequestedOverrideBounds())) { Loading @@ -2558,8 +2559,10 @@ final class ActivityRecord extends ConfigurationContainer { // Bounds changed...update configuration to match. if (!matchParentBounds()) { task.computeResolvedOverrideConfiguration(mTmpConfig, task.getParent().getConfiguration(), getRequestedOverrideConfiguration()); mTmpConfig.setTo(getRequestedOverrideConfiguration()); task.computeConfigResourceOverrides(mTmpConfig, task.getParent().getConfiguration()); } else { mTmpConfig.unset(); } onRequestedOverrideConfigurationChanged(mTmpConfig); Loading services/core/java/com/android/server/wm/DisplayContent.java +6 −0 Original line number Diff line number Diff line Loading @@ -3656,6 +3656,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } /** @returns the orientation of the display when it's rotation is ROTATION_0. */ int getNaturalOrientation() { return mBaseDisplayWidth < mBaseDisplayHeight ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; } void performLayout(boolean initial, boolean updateInputWindows) { if (!isLayoutNeeded()) { return; Loading services/core/java/com/android/server/wm/TaskRecord.java +99 −34 Original line number Diff line number Diff line Loading @@ -44,6 +44,9 @@ 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_AND_PIPABLE_DEPRECATED; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; 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.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.view.Display.DEFAULT_DISPLAY; Loading Loading @@ -1259,10 +1262,6 @@ class TaskRecord extends ConfigurationContainer { setFrontOfTask(); } void addActivityAtBottom(ActivityRecord r) { addActivityAtIndex(0, r); } void addActivityToTop(ActivityRecord r) { addActivityAtIndex(mActivities.size(), r); } Loading @@ -1277,6 +1276,34 @@ class TaskRecord extends ConfigurationContainer { return mActivities.get(0).getActivityType(); } /** * Checks if the root activity requires a particular orientation (either by override or * activityInfo) and returns that. Otherwise, this returns ORIENTATION_UNDEFINED. */ private int getRootActivityRequestedOrientation() { ActivityRecord root = getRootActivity(); if (getRequestedOverrideConfiguration().orientation != ORIENTATION_UNDEFINED || root == null) { return getRequestedOverrideConfiguration().orientation; } int rootScreenOrientation = root.getOrientation(); if (rootScreenOrientation == 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 (rootScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) { // LOCKED means the activity's orientation remains unchanged, so return existing value. return root.getConfiguration().orientation; } else if (ActivityInfo.isFixedOrientationLandscape(rootScreenOrientation)) { return ORIENTATION_LANDSCAPE; } else if (ActivityInfo.isFixedOrientationPortrait(rootScreenOrientation)) { return ORIENTATION_PORTRAIT; } return ORIENTATION_UNDEFINED; } /** * Adds an activity {@param r} at the given {@param index}. The activity {@param r} must either * be in the current task or unparented to any task. Loading Loading @@ -1741,7 +1768,7 @@ class TaskRecord extends ConfigurationContainer { updateTaskDescription(); } private void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) { void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) { if (bounds == null) { return; } Loading Loading @@ -1853,11 +1880,27 @@ class TaskRecord extends ConfigurationContainer { @Override public void onConfigurationChanged(Configuration newParentConfig) { // Check if the new configuration supports persistent bounds (eg. is Freeform) and if so // restore the last recorded non-fullscreen bounds. final boolean prevPersistTaskBounds = getWindowConfiguration().persistTaskBounds(); final boolean nextPersistTaskBounds = getRequestedOverrideConfiguration().windowConfiguration.persistTaskBounds() || newParentConfig.windowConfiguration.persistTaskBounds(); if (!prevPersistTaskBounds && nextPersistTaskBounds && mLastNonFullscreenBounds != null && !mLastNonFullscreenBounds.isEmpty()) { // Bypass onRequestedOverrideConfigurationChanged here to avoid infinite loop. getRequestedOverrideConfiguration().windowConfiguration .setBounds(mLastNonFullscreenBounds); } final boolean wasInMultiWindowMode = inMultiWindowMode(); super.onConfigurationChanged(newParentConfig); if (wasInMultiWindowMode != inMultiWindowMode()) { mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this); } // If the configuration supports persistent bounds (eg. Freeform), keep track of the // current (non-fullscreen) bounds for persistence. if (getWindowConfiguration().persistTaskBounds()) { final Rect currentBounds = getRequestedOverrideBounds(); if (!currentBounds.isEmpty()) { Loading Loading @@ -2047,7 +2090,7 @@ class TaskRecord extends ConfigurationContainer { * configuring an "inherit-bounds" window which means that all configuration settings would * just be inherited from the parent configuration. **/ void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @NonNull Rect bounds, void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @NonNull Configuration parentConfig) { int windowingMode = inOutConfig.windowConfiguration.getWindowingMode(); if (windowingMode == WINDOWING_MODE_UNDEFINED) { Loading @@ -2060,6 +2103,7 @@ class TaskRecord extends ConfigurationContainer { } density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; final Rect bounds = inOutConfig.windowConfiguration.getBounds(); Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); if (outAppBounds == null || outAppBounds.isEmpty()) { inOutConfig.windowConfiguration.setAppBounds(bounds); Loading Loading @@ -2107,13 +2151,14 @@ class TaskRecord extends ConfigurationContainer { // Iterating across all screen orientations, and return the minimum of the task // width taking into account that the bounds might change because the snap // algorithm snaps to a different value inOutConfig.smallestScreenWidthDp = getSmallestScreenWidthDpForDockedBounds(bounds); } // otherwise, it will just inherit } } if (inOutConfig.orientation == Configuration.ORIENTATION_UNDEFINED) { if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; } Loading @@ -2134,36 +2179,56 @@ class TaskRecord extends ConfigurationContainer { } } // TODO(b/113900640): remove this once ActivityRecord is changed to not need it anymore. void computeResolvedOverrideConfiguration(Configuration inOutConfig, Configuration parentConfig, Configuration overrideConfig) { // Save previous bounds because adjustForMinimalTaskDimensions uses that to determine if it // changes left bound vs. right bound, or top bound vs. bottom bound. mTmpBounds.set(inOutConfig.windowConfiguration.getBounds()); inOutConfig.setTo(overrideConfig); Rect outOverrideBounds = inOutConfig.windowConfiguration.getBounds(); if (outOverrideBounds != null && !outOverrideBounds.isEmpty()) { adjustForMinimalTaskDimensions(outOverrideBounds, mTmpBounds); int windowingMode = overrideConfig.windowConfiguration.getWindowingMode(); @Override void resolveOverrideConfiguration(Configuration newParentConfig) { mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds()); super.resolveOverrideConfiguration(newParentConfig); int windowingMode = getRequestedOverrideConfiguration().windowConfiguration.getWindowingMode(); if (windowingMode == WINDOWING_MODE_UNDEFINED) { windowingMode = parentConfig.windowConfiguration.getWindowingMode(); windowingMode = newParentConfig.windowConfiguration.getWindowingMode(); } Rect outOverrideBounds = getResolvedOverrideConfiguration().windowConfiguration.getBounds(); if (windowingMode == WINDOWING_MODE_FULLSCREEN) { // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent" outOverrideBounds.setEmpty(); // If the task or its root activity require a different orientation, make it fit the // available bounds by scaling down its bounds. int forcedOrientation = getRootActivityRequestedOrientation(); if (forcedOrientation != ORIENTATION_UNDEFINED && forcedOrientation != newParentConfig.orientation) { final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); final int parentWidth = parentBounds.width(); final int parentHeight = parentBounds.height(); final float aspect = ((float) parentHeight) / parentWidth; if (forcedOrientation == ORIENTATION_LANDSCAPE) { final int height = (int) (parentWidth / aspect); final int top = parentBounds.centerY() - height / 2; outOverrideBounds.set( parentBounds.left, top, parentBounds.right, top + height); } else { final int width = (int) (parentHeight * aspect); final int left = parentBounds.centerX() - width / 2; outOverrideBounds.set( left, parentBounds.top, left + width, parentBounds.bottom); } if (windowingMode == WINDOWING_MODE_FREEFORM) { // by policy, make sure the window remains within parent fitWithinBounds(outOverrideBounds, parentConfig.windowConfiguration.getBounds()); } computeConfigResourceOverrides(inOutConfig, outOverrideBounds, parentConfig); } if (outOverrideBounds.isEmpty()) { // If the task fills the parent, just inherit all the other configs from parent. return; } @Override void resolveOverrideConfiguration(Configuration newParentConfig) { computeResolvedOverrideConfiguration(getResolvedOverrideConfiguration(), newParentConfig, getRequestedOverrideConfiguration()); adjustForMinimalTaskDimensions(outOverrideBounds, mTmpBounds); if (windowingMode == WINDOWING_MODE_FREEFORM) { // by policy, make sure the window remains within parent somewhere fitWithinBounds(outOverrideBounds, newParentConfig.windowConfiguration.getBounds()); } computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig); } Rect updateOverrideConfigurationFromLaunchBounds() { Loading services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +20 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; Loading @@ -38,6 +39,9 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doCallRealMethod; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; import android.app.IApplicationThread; Loading Loading @@ -144,6 +148,13 @@ class ActivityTestsBase { return display; } /** Creates and adds a {@link TestActivityDisplay} to supervisor at the given position. */ TestActivityDisplay addNewActivityDisplayAt(DisplayInfo info, int position) { final TestActivityDisplay display = createNewActivityDisplay(info); mRootActivityContainer.addChild(display, position); return display; } /** * Builder for creating new activities. */ Loading Loading @@ -234,6 +245,10 @@ class ActivityTestsBase { mService.mStackSupervisor, null /* options */, null /* sourceRecord */); spyOn(activity); activity.mAppWindowToken = mock(AppWindowToken.class); doCallRealMethod().when(activity.mAppWindowToken).getOrientationIgnoreVisibility(); doCallRealMethod().when(activity.mAppWindowToken) .setOrientation(anyInt(), any(), any()); doCallRealMethod().when(activity.mAppWindowToken).setOrientation(anyInt()); doNothing().when(activity).removeWindowContainer(); if (mTaskRecord != null) { Loading Loading @@ -346,6 +361,7 @@ class ActivityTestsBase { mStack.addTask(task, true, "creating test task"); task.setStack(mStack); task.setTask(); mStack.getWindowContainerController().mContainer.addChild(task.mTask, 0); } task.touchActiveTime(); Loading @@ -365,7 +381,10 @@ class ActivityTestsBase { setTask(); } private void setTask() { void setTask() { Task mockTask = mock(Task.class); mockTask.mTaskRecord = this; doCallRealMethod().when(mockTask).onDescendantOrientationChanged(any(), any()); setTask(mock(Task.class)); } } Loading services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +100 −16 Original line number Diff line number Diff line Loading @@ -21,6 +21,11 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.sameInstance; Loading Loading @@ -133,17 +138,6 @@ public class TaskRecordTests extends ActivityTestsBase { assertTrue(task.returnsToHomeStack()); } /** Ensures that bounds are clipped to their parent. */ @Test public void testAppBounds_BoundsClipping() { final Rect shiftedBounds = new Rect(mParentBounds); shiftedBounds.offset(10, 10); final Rect expectedBounds = new Rect(mParentBounds); expectedBounds.intersect(shiftedBounds); testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds, expectedBounds); } /** Ensures that empty bounds are not propagated to the configuration. */ @Test public void testAppBounds_EmptyBounds() { Loading @@ -167,18 +161,108 @@ public class TaskRecordTests extends ActivityTestsBase { final Rect insetBounds = new Rect(mParentBounds); insetBounds.inset(5, 5, 5, 5); testStackBoundsConfiguration( WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds); WINDOWING_MODE_FREEFORM, mParentBounds, insetBounds, insetBounds); } /** Ensures that full screen free form bounds are clipped */ /** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */ @Test public void testAppBounds_FullScreenFreeFormBounds() { public void testBoundsOnModeChangeFreeformToFullscreen() { ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay(); ActivityStack stack = new StackBuilder(mRootActivityContainer).setDisplay(display) .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); TaskRecord task = stack.getChildAt(0); task.getRootActivity().mAppWindowToken.setOrientation(SCREEN_ORIENTATION_UNSPECIFIED); DisplayInfo info = new DisplayInfo(); display.mDisplay.getDisplayInfo(info); final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight); testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds, mParentBounds); final Rect freeformBounds = new Rect(fullScreenBounds); freeformBounds.inset((int) (freeformBounds.width() * 0.2), (int) (freeformBounds.height() * 0.2)); task.setBounds(freeformBounds); assertEquals(freeformBounds, task.getBounds()); // FULLSCREEN inherits bounds stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); assertEquals(fullScreenBounds, task.getBounds()); assertEquals(freeformBounds, task.mLastNonFullscreenBounds); // FREEFORM restores bounds stack.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(freeformBounds, task.getBounds()); } /** * This is a temporary hack to trigger an onConfigurationChange at the task level after an * orientation is requested. Normally this is done by the onDescendentOrientationChanged call * up the WM hierarchy, but since the WM hierarchy is mocked out, it doesn't happen here. * TODO: remove this when we either get a WM hierarchy or when hierarchies are merged. */ private void setActivityRequestedOrientation(ActivityRecord activity, int orientation) { activity.setRequestedOrientation(orientation); ConfigurationContainer taskRecord = activity.getParent(); taskRecord.onConfigurationChanged(taskRecord.getParent().getConfiguration()); } /** * Tests that a task with forced orientation has orientation-consistent bounds within the * parent. */ @Test public void testFullscreenBoundsForcedOrientation() { final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080); final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920); DisplayInfo info = new DisplayInfo(); info.logicalWidth = fullScreenBounds.width(); info.logicalHeight = fullScreenBounds.height(); ActivityDisplay display = addNewActivityDisplayAt(info, POSITION_TOP); assertTrue(mRootActivityContainer.getActivityDisplay(display.mDisplayId) != null); ActivityStack stack = new StackBuilder(mRootActivityContainer) .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); TaskRecord task = stack.getChildAt(0); ActivityRecord root = task.getRootActivity(); ActivityRecord top = new ActivityBuilder(mService).setTask(task).setStack(stack).build(); assertEquals(root, task.getRootActivity()); assertEquals(fullScreenBounds, task.getBounds()); // Setting app to fixed portrait fits within parent setActivityRequestedOrientation(root, SCREEN_ORIENTATION_PORTRAIT); assertEquals(root, task.getRootActivity()); assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation()); assertTrue(task.getBounds().width() < task.getBounds().height()); assertEquals(fullScreenBounds.height(), task.getBounds().height()); // Setting non-root app has no effect setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE); assertTrue(task.getBounds().width() < task.getBounds().height()); // Setting app to unspecified restores setActivityRequestedOrientation(root, SCREEN_ORIENTATION_UNSPECIFIED); assertEquals(fullScreenBounds, task.getBounds()); // Setting app to fixed landscape and changing display setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE); display.setBounds(fullScreenBoundsPort); assertTrue(task.getBounds().width() > task.getBounds().height()); assertEquals(fullScreenBoundsPort.width(), task.getBounds().width()); // in FREEFORM, no constraint final Rect freeformBounds = new Rect(display.getBounds()); freeformBounds.inset((int) (freeformBounds.width() * 0.2), (int) (freeformBounds.height() * 0.2)); stack.setWindowingMode(WINDOWING_MODE_FREEFORM); task.setBounds(freeformBounds); assertEquals(freeformBounds, task.getBounds()); // FULLSCREEN letterboxes bounds stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); assertTrue(task.getBounds().width() > task.getBounds().height()); assertEquals(fullScreenBoundsPort.width(), task.getBounds().width()); // FREEFORM restores bounds as before stack.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(freeformBounds, task.getBounds()); } /** Ensures that the alias intent won't have target component resolved. */ Loading Loading
services/core/java/com/android/server/wm/ActivityRecord.java +10 −7 Original line number Diff line number Diff line Loading @@ -728,9 +728,10 @@ final class ActivityRecord extends ConfigurationContainer { mLastReportedMultiWindowMode = inPictureInPictureMode; final Configuration newConfig = new Configuration(); if (targetStackBounds != null && !targetStackBounds.isEmpty()) { task.computeResolvedOverrideConfiguration(newConfig, task.getParent().getConfiguration(), task.getRequestedOverrideConfiguration()); newConfig.setTo(task.getRequestedOverrideConfiguration()); Rect outBounds = newConfig.windowConfiguration.getBounds(); task.adjustForMinimalTaskDimensions(outBounds, outBounds); task.computeConfigResourceOverrides(newConfig, task.getParent().getConfiguration()); } schedulePictureInPictureModeChanged(newConfig); scheduleMultiWindowModeChanged(newConfig); Loading Loading @@ -2503,7 +2504,8 @@ final class ActivityRecord extends ConfigurationContainer { return; } final IBinder binder = freezeScreenIfNeeded ? appToken.asBinder() : null; final IBinder binder = (freezeScreenIfNeeded && appToken != null) ? appToken.asBinder() : null; mAppWindowToken.setOrientation(requestedOrientation, binder, this); } Loading Loading @@ -2547,7 +2549,6 @@ final class ActivityRecord extends ConfigurationContainer { // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. private void updateOverrideConfiguration() { mTmpConfig.unset(); computeBounds(mTmpBounds); if (mTmpBounds.equals(getRequestedOverrideBounds())) { Loading @@ -2558,8 +2559,10 @@ final class ActivityRecord extends ConfigurationContainer { // Bounds changed...update configuration to match. if (!matchParentBounds()) { task.computeResolvedOverrideConfiguration(mTmpConfig, task.getParent().getConfiguration(), getRequestedOverrideConfiguration()); mTmpConfig.setTo(getRequestedOverrideConfiguration()); task.computeConfigResourceOverrides(mTmpConfig, task.getParent().getConfiguration()); } else { mTmpConfig.unset(); } onRequestedOverrideConfigurationChanged(mTmpConfig); Loading
services/core/java/com/android/server/wm/DisplayContent.java +6 −0 Original line number Diff line number Diff line Loading @@ -3656,6 +3656,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } /** @returns the orientation of the display when it's rotation is ROTATION_0. */ int getNaturalOrientation() { return mBaseDisplayWidth < mBaseDisplayHeight ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; } void performLayout(boolean initial, boolean updateInputWindows) { if (!isLayoutNeeded()) { return; Loading
services/core/java/com/android/server/wm/TaskRecord.java +99 −34 Original line number Diff line number Diff line Loading @@ -44,6 +44,9 @@ 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_AND_PIPABLE_DEPRECATED; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; 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.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.view.Display.DEFAULT_DISPLAY; Loading Loading @@ -1259,10 +1262,6 @@ class TaskRecord extends ConfigurationContainer { setFrontOfTask(); } void addActivityAtBottom(ActivityRecord r) { addActivityAtIndex(0, r); } void addActivityToTop(ActivityRecord r) { addActivityAtIndex(mActivities.size(), r); } Loading @@ -1277,6 +1276,34 @@ class TaskRecord extends ConfigurationContainer { return mActivities.get(0).getActivityType(); } /** * Checks if the root activity requires a particular orientation (either by override or * activityInfo) and returns that. Otherwise, this returns ORIENTATION_UNDEFINED. */ private int getRootActivityRequestedOrientation() { ActivityRecord root = getRootActivity(); if (getRequestedOverrideConfiguration().orientation != ORIENTATION_UNDEFINED || root == null) { return getRequestedOverrideConfiguration().orientation; } int rootScreenOrientation = root.getOrientation(); if (rootScreenOrientation == 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 (rootScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) { // LOCKED means the activity's orientation remains unchanged, so return existing value. return root.getConfiguration().orientation; } else if (ActivityInfo.isFixedOrientationLandscape(rootScreenOrientation)) { return ORIENTATION_LANDSCAPE; } else if (ActivityInfo.isFixedOrientationPortrait(rootScreenOrientation)) { return ORIENTATION_PORTRAIT; } return ORIENTATION_UNDEFINED; } /** * Adds an activity {@param r} at the given {@param index}. The activity {@param r} must either * be in the current task or unparented to any task. Loading Loading @@ -1741,7 +1768,7 @@ class TaskRecord extends ConfigurationContainer { updateTaskDescription(); } private void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) { void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) { if (bounds == null) { return; } Loading Loading @@ -1853,11 +1880,27 @@ class TaskRecord extends ConfigurationContainer { @Override public void onConfigurationChanged(Configuration newParentConfig) { // Check if the new configuration supports persistent bounds (eg. is Freeform) and if so // restore the last recorded non-fullscreen bounds. final boolean prevPersistTaskBounds = getWindowConfiguration().persistTaskBounds(); final boolean nextPersistTaskBounds = getRequestedOverrideConfiguration().windowConfiguration.persistTaskBounds() || newParentConfig.windowConfiguration.persistTaskBounds(); if (!prevPersistTaskBounds && nextPersistTaskBounds && mLastNonFullscreenBounds != null && !mLastNonFullscreenBounds.isEmpty()) { // Bypass onRequestedOverrideConfigurationChanged here to avoid infinite loop. getRequestedOverrideConfiguration().windowConfiguration .setBounds(mLastNonFullscreenBounds); } final boolean wasInMultiWindowMode = inMultiWindowMode(); super.onConfigurationChanged(newParentConfig); if (wasInMultiWindowMode != inMultiWindowMode()) { mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this); } // If the configuration supports persistent bounds (eg. Freeform), keep track of the // current (non-fullscreen) bounds for persistence. if (getWindowConfiguration().persistTaskBounds()) { final Rect currentBounds = getRequestedOverrideBounds(); if (!currentBounds.isEmpty()) { Loading Loading @@ -2047,7 +2090,7 @@ class TaskRecord extends ConfigurationContainer { * configuring an "inherit-bounds" window which means that all configuration settings would * just be inherited from the parent configuration. **/ void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @NonNull Rect bounds, void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @NonNull Configuration parentConfig) { int windowingMode = inOutConfig.windowConfiguration.getWindowingMode(); if (windowingMode == WINDOWING_MODE_UNDEFINED) { Loading @@ -2060,6 +2103,7 @@ class TaskRecord extends ConfigurationContainer { } density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; final Rect bounds = inOutConfig.windowConfiguration.getBounds(); Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); if (outAppBounds == null || outAppBounds.isEmpty()) { inOutConfig.windowConfiguration.setAppBounds(bounds); Loading Loading @@ -2107,13 +2151,14 @@ class TaskRecord extends ConfigurationContainer { // Iterating across all screen orientations, and return the minimum of the task // width taking into account that the bounds might change because the snap // algorithm snaps to a different value inOutConfig.smallestScreenWidthDp = getSmallestScreenWidthDpForDockedBounds(bounds); } // otherwise, it will just inherit } } if (inOutConfig.orientation == Configuration.ORIENTATION_UNDEFINED) { if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; } Loading @@ -2134,36 +2179,56 @@ class TaskRecord extends ConfigurationContainer { } } // TODO(b/113900640): remove this once ActivityRecord is changed to not need it anymore. void computeResolvedOverrideConfiguration(Configuration inOutConfig, Configuration parentConfig, Configuration overrideConfig) { // Save previous bounds because adjustForMinimalTaskDimensions uses that to determine if it // changes left bound vs. right bound, or top bound vs. bottom bound. mTmpBounds.set(inOutConfig.windowConfiguration.getBounds()); inOutConfig.setTo(overrideConfig); Rect outOverrideBounds = inOutConfig.windowConfiguration.getBounds(); if (outOverrideBounds != null && !outOverrideBounds.isEmpty()) { adjustForMinimalTaskDimensions(outOverrideBounds, mTmpBounds); int windowingMode = overrideConfig.windowConfiguration.getWindowingMode(); @Override void resolveOverrideConfiguration(Configuration newParentConfig) { mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds()); super.resolveOverrideConfiguration(newParentConfig); int windowingMode = getRequestedOverrideConfiguration().windowConfiguration.getWindowingMode(); if (windowingMode == WINDOWING_MODE_UNDEFINED) { windowingMode = parentConfig.windowConfiguration.getWindowingMode(); windowingMode = newParentConfig.windowConfiguration.getWindowingMode(); } Rect outOverrideBounds = getResolvedOverrideConfiguration().windowConfiguration.getBounds(); if (windowingMode == WINDOWING_MODE_FULLSCREEN) { // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent" outOverrideBounds.setEmpty(); // If the task or its root activity require a different orientation, make it fit the // available bounds by scaling down its bounds. int forcedOrientation = getRootActivityRequestedOrientation(); if (forcedOrientation != ORIENTATION_UNDEFINED && forcedOrientation != newParentConfig.orientation) { final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); final int parentWidth = parentBounds.width(); final int parentHeight = parentBounds.height(); final float aspect = ((float) parentHeight) / parentWidth; if (forcedOrientation == ORIENTATION_LANDSCAPE) { final int height = (int) (parentWidth / aspect); final int top = parentBounds.centerY() - height / 2; outOverrideBounds.set( parentBounds.left, top, parentBounds.right, top + height); } else { final int width = (int) (parentHeight * aspect); final int left = parentBounds.centerX() - width / 2; outOverrideBounds.set( left, parentBounds.top, left + width, parentBounds.bottom); } if (windowingMode == WINDOWING_MODE_FREEFORM) { // by policy, make sure the window remains within parent fitWithinBounds(outOverrideBounds, parentConfig.windowConfiguration.getBounds()); } computeConfigResourceOverrides(inOutConfig, outOverrideBounds, parentConfig); } if (outOverrideBounds.isEmpty()) { // If the task fills the parent, just inherit all the other configs from parent. return; } @Override void resolveOverrideConfiguration(Configuration newParentConfig) { computeResolvedOverrideConfiguration(getResolvedOverrideConfiguration(), newParentConfig, getRequestedOverrideConfiguration()); adjustForMinimalTaskDimensions(outOverrideBounds, mTmpBounds); if (windowingMode == WINDOWING_MODE_FREEFORM) { // by policy, make sure the window remains within parent somewhere fitWithinBounds(outOverrideBounds, newParentConfig.windowConfiguration.getBounds()); } computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig); } Rect updateOverrideConfigurationFromLaunchBounds() { Loading
services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +20 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; Loading @@ -38,6 +39,9 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doCallRealMethod; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; import android.app.IApplicationThread; Loading Loading @@ -144,6 +148,13 @@ class ActivityTestsBase { return display; } /** Creates and adds a {@link TestActivityDisplay} to supervisor at the given position. */ TestActivityDisplay addNewActivityDisplayAt(DisplayInfo info, int position) { final TestActivityDisplay display = createNewActivityDisplay(info); mRootActivityContainer.addChild(display, position); return display; } /** * Builder for creating new activities. */ Loading Loading @@ -234,6 +245,10 @@ class ActivityTestsBase { mService.mStackSupervisor, null /* options */, null /* sourceRecord */); spyOn(activity); activity.mAppWindowToken = mock(AppWindowToken.class); doCallRealMethod().when(activity.mAppWindowToken).getOrientationIgnoreVisibility(); doCallRealMethod().when(activity.mAppWindowToken) .setOrientation(anyInt(), any(), any()); doCallRealMethod().when(activity.mAppWindowToken).setOrientation(anyInt()); doNothing().when(activity).removeWindowContainer(); if (mTaskRecord != null) { Loading Loading @@ -346,6 +361,7 @@ class ActivityTestsBase { mStack.addTask(task, true, "creating test task"); task.setStack(mStack); task.setTask(); mStack.getWindowContainerController().mContainer.addChild(task.mTask, 0); } task.touchActiveTime(); Loading @@ -365,7 +381,10 @@ class ActivityTestsBase { setTask(); } private void setTask() { void setTask() { Task mockTask = mock(Task.class); mockTask.mTaskRecord = this; doCallRealMethod().when(mockTask).onDescendantOrientationChanged(any(), any()); setTask(mock(Task.class)); } } Loading
services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +100 −16 Original line number Diff line number Diff line Loading @@ -21,6 +21,11 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.sameInstance; Loading Loading @@ -133,17 +138,6 @@ public class TaskRecordTests extends ActivityTestsBase { assertTrue(task.returnsToHomeStack()); } /** Ensures that bounds are clipped to their parent. */ @Test public void testAppBounds_BoundsClipping() { final Rect shiftedBounds = new Rect(mParentBounds); shiftedBounds.offset(10, 10); final Rect expectedBounds = new Rect(mParentBounds); expectedBounds.intersect(shiftedBounds); testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds, expectedBounds); } /** Ensures that empty bounds are not propagated to the configuration. */ @Test public void testAppBounds_EmptyBounds() { Loading @@ -167,18 +161,108 @@ public class TaskRecordTests extends ActivityTestsBase { final Rect insetBounds = new Rect(mParentBounds); insetBounds.inset(5, 5, 5, 5); testStackBoundsConfiguration( WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds); WINDOWING_MODE_FREEFORM, mParentBounds, insetBounds, insetBounds); } /** Ensures that full screen free form bounds are clipped */ /** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */ @Test public void testAppBounds_FullScreenFreeFormBounds() { public void testBoundsOnModeChangeFreeformToFullscreen() { ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay(); ActivityStack stack = new StackBuilder(mRootActivityContainer).setDisplay(display) .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); TaskRecord task = stack.getChildAt(0); task.getRootActivity().mAppWindowToken.setOrientation(SCREEN_ORIENTATION_UNSPECIFIED); DisplayInfo info = new DisplayInfo(); display.mDisplay.getDisplayInfo(info); final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight); testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds, mParentBounds); final Rect freeformBounds = new Rect(fullScreenBounds); freeformBounds.inset((int) (freeformBounds.width() * 0.2), (int) (freeformBounds.height() * 0.2)); task.setBounds(freeformBounds); assertEquals(freeformBounds, task.getBounds()); // FULLSCREEN inherits bounds stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); assertEquals(fullScreenBounds, task.getBounds()); assertEquals(freeformBounds, task.mLastNonFullscreenBounds); // FREEFORM restores bounds stack.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(freeformBounds, task.getBounds()); } /** * This is a temporary hack to trigger an onConfigurationChange at the task level after an * orientation is requested. Normally this is done by the onDescendentOrientationChanged call * up the WM hierarchy, but since the WM hierarchy is mocked out, it doesn't happen here. * TODO: remove this when we either get a WM hierarchy or when hierarchies are merged. */ private void setActivityRequestedOrientation(ActivityRecord activity, int orientation) { activity.setRequestedOrientation(orientation); ConfigurationContainer taskRecord = activity.getParent(); taskRecord.onConfigurationChanged(taskRecord.getParent().getConfiguration()); } /** * Tests that a task with forced orientation has orientation-consistent bounds within the * parent. */ @Test public void testFullscreenBoundsForcedOrientation() { final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080); final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920); DisplayInfo info = new DisplayInfo(); info.logicalWidth = fullScreenBounds.width(); info.logicalHeight = fullScreenBounds.height(); ActivityDisplay display = addNewActivityDisplayAt(info, POSITION_TOP); assertTrue(mRootActivityContainer.getActivityDisplay(display.mDisplayId) != null); ActivityStack stack = new StackBuilder(mRootActivityContainer) .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); TaskRecord task = stack.getChildAt(0); ActivityRecord root = task.getRootActivity(); ActivityRecord top = new ActivityBuilder(mService).setTask(task).setStack(stack).build(); assertEquals(root, task.getRootActivity()); assertEquals(fullScreenBounds, task.getBounds()); // Setting app to fixed portrait fits within parent setActivityRequestedOrientation(root, SCREEN_ORIENTATION_PORTRAIT); assertEquals(root, task.getRootActivity()); assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation()); assertTrue(task.getBounds().width() < task.getBounds().height()); assertEquals(fullScreenBounds.height(), task.getBounds().height()); // Setting non-root app has no effect setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE); assertTrue(task.getBounds().width() < task.getBounds().height()); // Setting app to unspecified restores setActivityRequestedOrientation(root, SCREEN_ORIENTATION_UNSPECIFIED); assertEquals(fullScreenBounds, task.getBounds()); // Setting app to fixed landscape and changing display setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE); display.setBounds(fullScreenBoundsPort); assertTrue(task.getBounds().width() > task.getBounds().height()); assertEquals(fullScreenBoundsPort.width(), task.getBounds().width()); // in FREEFORM, no constraint final Rect freeformBounds = new Rect(display.getBounds()); freeformBounds.inset((int) (freeformBounds.width() * 0.2), (int) (freeformBounds.height() * 0.2)); stack.setWindowingMode(WINDOWING_MODE_FREEFORM); task.setBounds(freeformBounds); assertEquals(freeformBounds, task.getBounds()); // FULLSCREEN letterboxes bounds stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); assertTrue(task.getBounds().width() > task.getBounds().height()); assertEquals(fullScreenBoundsPort.width(), task.getBounds().width()); // FREEFORM restores bounds as before stack.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(freeformBounds, task.getBounds()); } /** Ensures that the alias intent won't have target component resolved. */ Loading