diff options
| author | 2024-03-09 22:38:44 +0000 | |
|---|---|---|
| committer | 2024-03-13 16:56:36 +0000 | |
| commit | b84f973f20d3929b1bc6a113815bbe0b655e9f81 (patch) | |
| tree | 2561e6245aef189a2b10d9833c1b892d8cc991ae | |
| parent | 670ec4a109fb0d0839e419d965027efadea28747 (diff) | |
Fix user rotation of display devices
Previously calling wm user-rotation adb command
for non-default display resulted into a
non-rotated shifted picture.
With this CL, DM will always take the rotation
supplied by WM and set it to the surface flinger
projection. Also to fix the shifted picture
after rotation, there is an additional call
to ContentRecorder.onConfigurationChanged.
VirtualDisplay surface size previously did
not take the possible rotation into account,
this CL fixes this - important for Chromecast.
Change-Id: I6f3a86a97ba81413aa480a763307ac9109585c6f
Bug: 302326003
Bug: 322480626
Test: adb shell setprop persist.demo.userrotation 1
Test: adb shell wm user-rotation -d ${DISPLAYID} lock 1
10 files changed, 141 insertions, 18 deletions
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index a7748f4fae98..0ebb2a30685c 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -158,8 +158,6 @@ abstract class DisplayDevice { @Nullable public Point getDisplaySurfaceDefaultSizeLocked() { DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked(); - final boolean isRotated = mCurrentOrientation == ROTATION_90 - || mCurrentOrientation == ROTATION_270; var width = displayDeviceInfo.width; var height = displayDeviceInfo.height; if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0 @@ -170,7 +168,7 @@ abstract class DisplayDevice { width = (int) (width * displayDeviceInfo.yDpi / displayDeviceInfo.xDpi + 0.5); } } - return isRotated ? new Point(height, width) : new Point(width, height); + return isRotatedLocked() ? new Point(height, width) : new Point(width, height); } /** @@ -394,8 +392,7 @@ abstract class DisplayDevice { viewport.physicalFrame.setEmpty(); } - boolean isRotated = (mCurrentOrientation == Surface.ROTATION_90 - || mCurrentOrientation == ROTATION_270); + final boolean isRotated = isRotatedLocked(); DisplayDeviceInfo info = getDisplayDeviceInfoLocked(); viewport.deviceWidth = isRotated ? info.height : info.width; viewport.deviceHeight = isRotated ? info.width : info.height; @@ -425,6 +422,13 @@ abstract class DisplayDevice { pw.println("mCurrentSurface=" + mCurrentSurface); } + /** + * @return whether the orientation is {@link ROTATION_90} or {@link ROTATION_270}. + */ + boolean isRotatedLocked() { + return mCurrentOrientation == ROTATION_90 || mCurrentOrientation == ROTATION_270; + } + private DisplayDeviceConfig loadDisplayDeviceConfig() { return DisplayDeviceConfig.create(mContext, /* useConfigXml= */ false, mDisplayAdapter.getFeatureFlags()); diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 5eaaf3504e85..22e4bc502131 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -204,6 +204,13 @@ final class LogicalDisplay { new SparseArray<>(); /** + * If enabled, will not check for {@link Display#FLAG_ROTATES_WITH_CONTENT} in LogicalDisplay + * and simply use the {@link DisplayInfo#rotation} supplied by WindowManager via + * {@link #setDisplayInfoOverrideFromWindowManagerLocked} + */ + private boolean mAlwaysRotateDisplayDeviceEnabled; + + /** * If the aspect ratio of the resolution of the display does not match the physical aspect * ratio of the display, then without this feature enabled, picture would appear stretched to * the user. This is because applications assume that they are rendered on square pixels @@ -220,11 +227,11 @@ final class LogicalDisplay { private final boolean mIsAnisotropyCorrectionEnabled; LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) { - this(displayId, layerStack, primaryDisplayDevice, false); + this(displayId, layerStack, primaryDisplayDevice, false, false); } LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice, - boolean isAnisotropyCorrectionEnabled) { + boolean isAnisotropyCorrectionEnabled, boolean isAlwaysRotateDisplayDeviceEnabled) { mDisplayId = displayId; mLayerStack = layerStack; mPrimaryDisplayDevice = primaryDisplayDevice; @@ -236,6 +243,7 @@ final class LogicalDisplay { mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID; mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId; mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled; + mAlwaysRotateDisplayDeviceEnabled = isAlwaysRotateDisplayDeviceEnabled; } public void setDevicePositionLocked(int position) { @@ -672,7 +680,12 @@ final class LogicalDisplay { // The orientation specifies how the physical coordinate system of the display // is rotated when the contents of the logical display are rendered. int orientation = Surface.ROTATION_0; - if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) { + + // FLAG_ROTATES_WITH_CONTENT is now handled in DisplayContent. When the flag + // mAlwaysRotateDisplayDeviceEnabled is removed, we should also remove this check for + // ROTATES_WITH_CONTENT here and always set the orientation. + if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0 + || mAlwaysRotateDisplayDeviceEnabled) { orientation = displayInfo.rotation; } diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index e092fdae7cc7..21f90d4aeb94 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -1152,7 +1152,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) { final int layerStack = assignLayerStackLocked(displayId); final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device, - mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled()); + mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled(), + mFlags.isAlwaysRotateDisplayDeviceEnabled()); display.updateLocked(mDisplayDeviceRepo); final DisplayInfo info = display.getDisplayInfoLocked(); diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index ec5ad7de11b3..bcdb442c3ad3 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -366,7 +366,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter { if (mSurface == null) { return null; } - return mSurface.getDefaultSize(); + final Point surfaceSize = mSurface.getDefaultSize(); + return isRotatedLocked() ? new Point(surfaceSize.y, surfaceSize.x) : surfaceSize; } @VisibleForTesting diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 15ee9372b46a..5f455db39dd4 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -116,6 +116,10 @@ public class DisplayManagerFlags { Flags.FLAG_FAST_HDR_TRANSITIONS, Flags::fastHdrTransitions); + private final FlagState mAlwaysRotateDisplayDevice = new FlagState( + Flags.FLAG_ALWAYS_ROTATE_DISPLAY_DEVICE, + Flags::alwaysRotateDisplayDevice); + private final FlagState mRefreshRateVotingTelemetry = new FlagState( Flags.FLAG_REFRESH_RATE_VOTING_TELEMETRY, Flags::refreshRateVotingTelemetry @@ -260,6 +264,10 @@ public class DisplayManagerFlags { return mFastHdrTransitions.isEnabled(); } + public boolean isAlwaysRotateDisplayDeviceEnabled() { + return mAlwaysRotateDisplayDevice.isEnabled(); + } + public boolean isRefreshRateVotingTelemetryEnabled() { return mRefreshRateVotingTelemetry.isEnabled(); } @@ -298,6 +306,7 @@ public class DisplayManagerFlags { pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState); pw.println(" " + mAutoBrightnessModesFlagState); pw.println(" " + mFastHdrTransitions); + pw.println(" " + mAlwaysRotateDisplayDevice); pw.println(" " + mRefreshRateVotingTelemetry); pw.println(" " + mPixelAnisotropyCorrectionEnabled); pw.println(" " + mSensorBasedBrightnessThrottling); diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 9bf36e4e605f..d2909b898704 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -178,6 +178,17 @@ flag { } flag { + name: "always_rotate_display_device" + namespace: "display_manager" + description: "Use rotation from WindowManager no matter whether FLAG_ROTATES_WITH_CONTENT is set or not" + bug: "302326003" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "refresh_rate_voting_telemetry" namespace: "display_manager" description: "Feature flag for enabling telemetry for refresh rate voting in DisplayManager" diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index b616d24cfebb..802051660c0e 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -157,6 +157,10 @@ final class ContentRecorder implements WindowContainerListener { } } + void onMirrorOutputSurfaceOrientationChanged() { + onConfigurationChanged(mLastOrientation, mLastWindowingMode); + } + /** * Handle a configuration change on the display content, and resize recording if needed. * @param lastOrientation the prior orientation of the configuration diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 837d08b33756..dc643453c8dd 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6835,6 +6835,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mContentRecorder; } + void onMirrorOutputSurfaceOrientationChanged() { + if (mContentRecorder != null) { + mContentRecorder.onMirrorOutputSurfaceOrientationChanged(); + } + } + /** * Pause the recording session. */ diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 384b91a07d4e..4a97128c27b6 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -19,6 +19,8 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.view.Display.TYPE_EXTERNAL; +import static android.view.Display.TYPE_OVERLAY; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; @@ -915,6 +917,11 @@ public class DisplayRotation { + " for " + mDisplayContent); userRotation = Surface.ROTATION_0; } + final int userRotationOverride = getUserRotationOverride(); + if (userRotationOverride != 0) { + userRotationMode = WindowManagerPolicy.USER_ROTATION_LOCKED; + userRotation = userRotationOverride; + } mUserRotationMode = userRotationMode; mUserRotation = userRotation; } @@ -965,6 +972,13 @@ public class DisplayRotation { if (changed) { mService.updateRotation(false /* alwaysSendConfiguration */, false /* forceRelayout */); + // ContentRecorder.onConfigurationChanged and Device.setProjectionLocked are called + // during updateRotation above. But onConfigurationChanged is called before + // Device.setProjectionLocked, which means that the onConfigurationChanged will + // not have the new rotation when it calls getDisplaySurfaceDefaultSize. + // To make sure that mirroring takes the new rotation of the output surface + // into account we need to call onConfigurationChanged again. + mDisplayContent.onMirrorOutputSurfaceOrientationChanged(); } } @@ -1780,6 +1794,23 @@ public class DisplayRotation { } } + @Surface.Rotation + private int getUserRotationOverride() { + final int userRotationOverride = SystemProperties.getInt("persist.demo.userrotation", + Surface.ROTATION_0); + if (userRotationOverride == Surface.ROTATION_0) { + return userRotationOverride; + } + + final var display = mDisplayContent.mDisplay; + if (display.getType() == TYPE_EXTERNAL || display.getType() == TYPE_OVERLAY) { + // TODO b/329442350 add chromecast virtual displays here + return userRotationOverride; + } + + return Surface.ROTATION_0; + } + @VisibleForTesting long uptimeMillis() { return SystemClock.uptimeMillis(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java index 549f0d74b67b..e798aa20f4bf 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java @@ -107,8 +107,7 @@ public class LogicalDisplayTest { @Test public void testLetterbox() { - mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, - /*isAnisotropyCorrectionEnabled=*/ false); + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice); mDisplayDeviceInfo.xDpi = 0.5f; mDisplayDeviceInfo.yDpi = 1.0f; @@ -146,7 +145,8 @@ public class LogicalDisplayTest { @Test public void testNoLetterbox_anisotropyCorrection() { mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, - /*isAnisotropyCorrectionEnabled=*/ true); + /*isAnisotropyCorrectionEnabled=*/ true, + /*isAlwaysRotateDisplayDeviceEnabled=*/ true); // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust // to using the whole screen. This is because display will rescale it back to fill the @@ -173,7 +173,8 @@ public class LogicalDisplayTest { @Test public void testLetterbox_anisotropyCorrectionYDpi() { mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, - /*isAnisotropyCorrectionEnabled=*/ true); + /*isAnisotropyCorrectionEnabled=*/ true, + /*isAlwaysRotateDisplayDeviceEnabled=*/ true); DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = DISPLAY_WIDTH; @@ -191,8 +192,7 @@ public class LogicalDisplayTest { @Test public void testPillarbox() { - mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, - /*isAnisotropyCorrectionEnabled=*/ false); + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice); mDisplayDeviceInfo.xDpi = 0.5f; mDisplayDeviceInfo.yDpi = 1.0f; @@ -230,7 +230,8 @@ public class LogicalDisplayTest { @Test public void testPillarbox_anisotropyCorrection() { mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, - /*isAnisotropyCorrectionEnabled=*/ true); + /*isAnisotropyCorrectionEnabled=*/ true, + /*isAlwaysRotateDisplayDeviceEnabled=*/ true); DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = DISPLAY_WIDTH; @@ -257,7 +258,8 @@ public class LogicalDisplayTest { @Test public void testNoPillarbox_anisotropyCorrectionYDpi() { mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, - /*isAnisotropyCorrectionEnabled=*/ true); + /*isAnisotropyCorrectionEnabled=*/ true, + /*isAlwaysRotateDisplayDeviceEnabled=*/ true); // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust // to using the whole screen. This is because display will rescale it back to fill the @@ -315,6 +317,47 @@ public class LogicalDisplayTest { } @Test + public void testGetDisplayPositionAlwaysRotateDisplayEnabled() { + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, + /*isAnisotropyCorrectionEnabled=*/ true, + /*isAlwaysRotateDisplayDeviceEnabled=*/ true); + mLogicalDisplay.updateLocked(mDeviceRepo); + Point expectedPosition = new Point(); + + SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); + + expectedPosition.set(20, 40); + mLogicalDisplay.setDisplayOffsetsLocked(20, 40); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); + + DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = DISPLAY_WIDTH; + displayInfo.logicalHeight = DISPLAY_HEIGHT; + // Rotation sent from WindowManager is always taken into account by LogicalDisplay + // not matter whether FLAG_ROTATES_WITH_CONTENT is set or not. + // This is because WindowManager takes care of rotation and expects that LogicalDisplay + // will follow the rotation supplied by WindowManager + expectedPosition.set(115, -20); + displayInfo.rotation = Surface.ROTATION_90; + mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); + + expectedPosition.set(40, -20); + mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT; + mLogicalDisplay.updateLocked(mDeviceRepo); + displayInfo.logicalWidth = DISPLAY_HEIGHT; + displayInfo.logicalHeight = DISPLAY_WIDTH; + displayInfo.rotation = Surface.ROTATION_90; + mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); + } + + @Test public void testDisplayInputFlags() { DisplayDevice displayDevice = new DisplayDevice(mDisplayAdapter, mDisplayToken, "unique_display_id", mContext) { |