Loading core/java/android/content/res/CompatibilityInfo.java +0 −3 Original line number Diff line number Diff line Loading @@ -40,8 +40,6 @@ import android.view.Surface; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import com.android.aconfig.annotations.VisibleForTesting; /** * CompatibilityInfo class keeps the information about the screen compatibility mode that the * application is running under. Loading Loading @@ -763,7 +761,6 @@ public class CompatibilityInfo implements Parcelable { } /** @see #sOverrideDisplayRotation */ @VisibleForTesting public static int getOverrideDisplayRotation() { return sOverrideDisplayRotation; } Loading core/java/android/view/OrientationEventListener.java +53 −6 Original line number Diff line number Diff line Loading @@ -16,13 +16,19 @@ package android.view; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import android.annotation.NonNull; import android.content.Context; import android.content.res.CompatibilityInfo; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.util.Log; import com.android.window.flags.Flags; /** * Helper class for receiving notifications from the SensorManager when * the orientation of the device has changed. Loading Loading @@ -70,8 +76,10 @@ public abstract class OrientationEventListener { mRate = rate; mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (mSensor != null) { // Create listener only if sensors do exist mSensorEventListener = new SensorEventListenerImpl(); // Create listener only if sensors do exist. mSensorEventListener = Flags.enableCameraCompatForDesktopWindowing() ? new CompatSensorEventListenerImpl(new SensorEventListenerImpl()) : new SensorEventListenerImpl(); } } Loading Loading @@ -149,6 +157,45 @@ public abstract class OrientationEventListener { } } /** Decorator to the {@link SensorEventListenerImpl}, which provides compat values if needed. */ class CompatSensorEventListenerImpl implements SensorEventListener { // SensorEventListener without compatibility capabilities. final SensorEventListenerImpl mSensorEventListener; CompatSensorEventListenerImpl(@NonNull SensorEventListenerImpl sensorEventListener) { mSensorEventListener = sensorEventListener; } public void onSensorChanged(SensorEvent event) { // If the display rotation override is set, the same override should be applied to // this orientation too. This rotation override will only be set when an app has a // camera open and it is in camera compat mode for desktop windowing (freeform mode). // Values of this override is Surface.ROTATION_0/90/180/270, or // WindowConfiguration.ROTATION_UNDEFINED when not set. if (CompatibilityInfo.getOverrideDisplayRotation() != ROTATION_UNDEFINED) { // SensorEventListener reports the rotation in the opposite direction from the // display rotation. int orientation = (360 - CompatibilityInfo.getOverrideDisplayRotation() * 90) % 360; if (orientation != mOrientation) { mOrientation = orientation; onOrientationChanged(orientation); } // `mOldListener` is deprecated and returns 3D values, which are highly unlikely to // be used for orienting camera image. Thus this listener is not called here, as // opposed to extrapolating values from display rotation, from 1D->3D. } else { // Use the default implementation: calculate the orientation from event coordinates. // This method will call OrientationEventListener.onOrientationChanged(orientation) // if the orientation has changed. mSensorEventListener.onSensorChanged(event); } } public void onAccuracyChanged(Sensor sensor, int accuracy) { } } /* * Returns true if sensor is enabled and false otherwise */ Loading core/tests/coretests/src/android/view/OrientationEventListenerFrameworkTest.java 0 → 100644 +201 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.app.WindowConfiguration; import android.content.Context; import android.content.res.CompatibilityInfo; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.input.InputSensorInfo; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.util.ArrayList; /** * Test {@link OrientationEventListener}. * * <p>Build/Install/Run: * atest FrameworksCoreTests:OrientationEventListenerFrameworkTest */ @SmallTest @RunWith(AndroidJUnit4.class) public class OrientationEventListenerFrameworkTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private Context mContext; private SensorManager mSensorManager; @Before public void setup() { mContext = mock(Context.class); mSensorManager = mock(SensorManager.class); doReturn(mSensorManager).when(mContext).getSystemService(Context.SENSOR_SERVICE); } @After public void tearDown() { // Reset the override rotation for tests that use the default value. CompatibilityInfo.setOverrideDisplayRotation(WindowConfiguration.ROTATION_UNDEFINED); } @Test public void testConstructor() { new TestOrientationEventListener(mContext); new TestOrientationEventListener(mContext, SensorManager.SENSOR_DELAY_UI); } @Test public void testEnableAndDisable() { final TestOrientationEventListener listener = new TestOrientationEventListener(mContext); listener.enable(); listener.disable(); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testSensorOrientationUpdate() { final Sensor mockSensor = setupMockAccelerometerSensor(); final TestOrientationEventListener listener = new TestOrientationEventListener(mContext); listener.enable(); sendSensorEventWithOrientation270(mockSensor); assertEquals(1, listener.mReportedOrientations.size()); assertEquals(270, (int) listener.mReportedOrientations.get(0)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testSensorOrientationUpdate_overriddenDisplayRotationReportedWhenSet() { final Sensor mockSensor = setupMockAccelerometerSensor(); final TestOrientationEventListener listener = new TestOrientationEventListener(mContext); listener.enable(); // This should change the reported sensor rotation. CompatibilityInfo.setOverrideDisplayRotation(Surface.ROTATION_180); sendSensorEventWithOrientation270(mockSensor); assertEquals(1, listener.mReportedOrientations.size()); assertEquals(180, (int) listener.mReportedOrientations.get(0)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testSensorOrientationUpdate_overriddenDisplayRotationIsNegativeFromSensor() { final Sensor mockSensor = setupMockAccelerometerSensor(); final TestOrientationEventListener listener = new TestOrientationEventListener(mContext); listener.enable(); // Display rotation is counted in the opposite direction from the sensor orientation, thus // this call should change the reported sensor rotation to 90, as 360 - 270 = 90. CompatibilityInfo.setOverrideDisplayRotation(Surface.ROTATION_270); sendSensorEventWithOrientation270(mockSensor); assertEquals(1, listener.mReportedOrientations.size()); assertEquals(90, (int) listener.mReportedOrientations.get(0)); } @Test @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testSensorOrientationUpdate_notOverriddenWhenCameraFeatureDisabled() { final Sensor mockSensor = setupMockAccelerometerSensor(); final TestOrientationEventListener listener = new TestOrientationEventListener(mContext); listener.enable(); CompatibilityInfo.setOverrideDisplayRotation(Surface.ROTATION_180); sendSensorEventWithOrientation270(mockSensor); assertEquals(1, listener.mReportedOrientations.size()); // Sensor unchanged by override because the feature is disabled. assertEquals(270, (int) listener.mReportedOrientations.get(0)); } @NonNull private Sensor setupMockAccelerometerSensor() { final Sensor mockSensor = new Sensor(mock(InputSensorInfo.class)); doReturn(mockSensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_ACCELEROMETER); return mockSensor; } @NonNull private SensorEventListener getRegisteredEventListener() { // Get the SensorEventListenerImpl that has subscribed in `listener.enable()`. final ArgumentCaptor<SensorEventListener> listenerArgCaptor = ArgumentCaptor .forClass(SensorEventListener.class); verify(mSensorManager).registerListener(listenerArgCaptor.capture(), any(), anyInt()); return listenerArgCaptor.getValue(); } private void sendSensorEventWithOrientation270(@NonNull Sensor sensor) { final SensorEventListener sensorEventListener = getRegisteredEventListener(); // Arbitrary values that return orientation 270. final SensorEvent sensorEvent = new SensorEvent(sensor, 1, 1L, new float[]{1.0f, 0.0f, 0.0f}); sensorEventListener.onSensorChanged(sensorEvent); } private static class TestOrientationEventListener extends OrientationEventListener { final ArrayList<Integer> mReportedOrientations = new ArrayList<>(); TestOrientationEventListener(Context context) { super(context); } TestOrientationEventListener(Context context, int rate) { super(context, rate); } @Override public void onOrientationChanged(int orientation) { mReportedOrientations.add(orientation); } } } Loading
core/java/android/content/res/CompatibilityInfo.java +0 −3 Original line number Diff line number Diff line Loading @@ -40,8 +40,6 @@ import android.view.Surface; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import com.android.aconfig.annotations.VisibleForTesting; /** * CompatibilityInfo class keeps the information about the screen compatibility mode that the * application is running under. Loading Loading @@ -763,7 +761,6 @@ public class CompatibilityInfo implements Parcelable { } /** @see #sOverrideDisplayRotation */ @VisibleForTesting public static int getOverrideDisplayRotation() { return sOverrideDisplayRotation; } Loading
core/java/android/view/OrientationEventListener.java +53 −6 Original line number Diff line number Diff line Loading @@ -16,13 +16,19 @@ package android.view; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import android.annotation.NonNull; import android.content.Context; import android.content.res.CompatibilityInfo; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.util.Log; import com.android.window.flags.Flags; /** * Helper class for receiving notifications from the SensorManager when * the orientation of the device has changed. Loading Loading @@ -70,8 +76,10 @@ public abstract class OrientationEventListener { mRate = rate; mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (mSensor != null) { // Create listener only if sensors do exist mSensorEventListener = new SensorEventListenerImpl(); // Create listener only if sensors do exist. mSensorEventListener = Flags.enableCameraCompatForDesktopWindowing() ? new CompatSensorEventListenerImpl(new SensorEventListenerImpl()) : new SensorEventListenerImpl(); } } Loading Loading @@ -149,6 +157,45 @@ public abstract class OrientationEventListener { } } /** Decorator to the {@link SensorEventListenerImpl}, which provides compat values if needed. */ class CompatSensorEventListenerImpl implements SensorEventListener { // SensorEventListener without compatibility capabilities. final SensorEventListenerImpl mSensorEventListener; CompatSensorEventListenerImpl(@NonNull SensorEventListenerImpl sensorEventListener) { mSensorEventListener = sensorEventListener; } public void onSensorChanged(SensorEvent event) { // If the display rotation override is set, the same override should be applied to // this orientation too. This rotation override will only be set when an app has a // camera open and it is in camera compat mode for desktop windowing (freeform mode). // Values of this override is Surface.ROTATION_0/90/180/270, or // WindowConfiguration.ROTATION_UNDEFINED when not set. if (CompatibilityInfo.getOverrideDisplayRotation() != ROTATION_UNDEFINED) { // SensorEventListener reports the rotation in the opposite direction from the // display rotation. int orientation = (360 - CompatibilityInfo.getOverrideDisplayRotation() * 90) % 360; if (orientation != mOrientation) { mOrientation = orientation; onOrientationChanged(orientation); } // `mOldListener` is deprecated and returns 3D values, which are highly unlikely to // be used for orienting camera image. Thus this listener is not called here, as // opposed to extrapolating values from display rotation, from 1D->3D. } else { // Use the default implementation: calculate the orientation from event coordinates. // This method will call OrientationEventListener.onOrientationChanged(orientation) // if the orientation has changed. mSensorEventListener.onSensorChanged(event); } } public void onAccuracyChanged(Sensor sensor, int accuracy) { } } /* * Returns true if sensor is enabled and false otherwise */ Loading
core/tests/coretests/src/android/view/OrientationEventListenerFrameworkTest.java 0 → 100644 +201 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.app.WindowConfiguration; import android.content.Context; import android.content.res.CompatibilityInfo; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.input.InputSensorInfo; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.util.ArrayList; /** * Test {@link OrientationEventListener}. * * <p>Build/Install/Run: * atest FrameworksCoreTests:OrientationEventListenerFrameworkTest */ @SmallTest @RunWith(AndroidJUnit4.class) public class OrientationEventListenerFrameworkTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private Context mContext; private SensorManager mSensorManager; @Before public void setup() { mContext = mock(Context.class); mSensorManager = mock(SensorManager.class); doReturn(mSensorManager).when(mContext).getSystemService(Context.SENSOR_SERVICE); } @After public void tearDown() { // Reset the override rotation for tests that use the default value. CompatibilityInfo.setOverrideDisplayRotation(WindowConfiguration.ROTATION_UNDEFINED); } @Test public void testConstructor() { new TestOrientationEventListener(mContext); new TestOrientationEventListener(mContext, SensorManager.SENSOR_DELAY_UI); } @Test public void testEnableAndDisable() { final TestOrientationEventListener listener = new TestOrientationEventListener(mContext); listener.enable(); listener.disable(); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testSensorOrientationUpdate() { final Sensor mockSensor = setupMockAccelerometerSensor(); final TestOrientationEventListener listener = new TestOrientationEventListener(mContext); listener.enable(); sendSensorEventWithOrientation270(mockSensor); assertEquals(1, listener.mReportedOrientations.size()); assertEquals(270, (int) listener.mReportedOrientations.get(0)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testSensorOrientationUpdate_overriddenDisplayRotationReportedWhenSet() { final Sensor mockSensor = setupMockAccelerometerSensor(); final TestOrientationEventListener listener = new TestOrientationEventListener(mContext); listener.enable(); // This should change the reported sensor rotation. CompatibilityInfo.setOverrideDisplayRotation(Surface.ROTATION_180); sendSensorEventWithOrientation270(mockSensor); assertEquals(1, listener.mReportedOrientations.size()); assertEquals(180, (int) listener.mReportedOrientations.get(0)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testSensorOrientationUpdate_overriddenDisplayRotationIsNegativeFromSensor() { final Sensor mockSensor = setupMockAccelerometerSensor(); final TestOrientationEventListener listener = new TestOrientationEventListener(mContext); listener.enable(); // Display rotation is counted in the opposite direction from the sensor orientation, thus // this call should change the reported sensor rotation to 90, as 360 - 270 = 90. CompatibilityInfo.setOverrideDisplayRotation(Surface.ROTATION_270); sendSensorEventWithOrientation270(mockSensor); assertEquals(1, listener.mReportedOrientations.size()); assertEquals(90, (int) listener.mReportedOrientations.get(0)); } @Test @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testSensorOrientationUpdate_notOverriddenWhenCameraFeatureDisabled() { final Sensor mockSensor = setupMockAccelerometerSensor(); final TestOrientationEventListener listener = new TestOrientationEventListener(mContext); listener.enable(); CompatibilityInfo.setOverrideDisplayRotation(Surface.ROTATION_180); sendSensorEventWithOrientation270(mockSensor); assertEquals(1, listener.mReportedOrientations.size()); // Sensor unchanged by override because the feature is disabled. assertEquals(270, (int) listener.mReportedOrientations.get(0)); } @NonNull private Sensor setupMockAccelerometerSensor() { final Sensor mockSensor = new Sensor(mock(InputSensorInfo.class)); doReturn(mockSensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_ACCELEROMETER); return mockSensor; } @NonNull private SensorEventListener getRegisteredEventListener() { // Get the SensorEventListenerImpl that has subscribed in `listener.enable()`. final ArgumentCaptor<SensorEventListener> listenerArgCaptor = ArgumentCaptor .forClass(SensorEventListener.class); verify(mSensorManager).registerListener(listenerArgCaptor.capture(), any(), anyInt()); return listenerArgCaptor.getValue(); } private void sendSensorEventWithOrientation270(@NonNull Sensor sensor) { final SensorEventListener sensorEventListener = getRegisteredEventListener(); // Arbitrary values that return orientation 270. final SensorEvent sensorEvent = new SensorEvent(sensor, 1, 1L, new float[]{1.0f, 0.0f, 0.0f}); sensorEventListener.onSensorChanged(sensorEvent); } private static class TestOrientationEventListener extends OrientationEventListener { final ArrayList<Integer> mReportedOrientations = new ArrayList<>(); TestOrientationEventListener(Context context) { super(context); } TestOrientationEventListener(Context context, int rate) { super(context, rate); } @Override public void onOrientationChanged(int orientation) { mReportedOrientations.add(orientation); } } }