diff options
3 files changed, 254 insertions, 9 deletions
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index afcdcb0fbcad..5727fcf87144 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -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. @@ -763,7 +761,6 @@ public class CompatibilityInfo implements Parcelable { } /** @see #sOverrideDisplayRotation */ - @VisibleForTesting public static int getOverrideDisplayRotation() { return sOverrideDisplayRotation; } diff --git a/core/java/android/view/OrientationEventListener.java b/core/java/android/view/OrientationEventListener.java index cd48a4f884a8..2feb44ffc3ae 100644 --- a/core/java/android/view/OrientationEventListener.java +++ b/core/java/android/view/OrientationEventListener.java @@ -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. @@ -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(); } } @@ -114,13 +122,13 @@ public abstract class OrientationEventListener { private static final int _DATA_X = 0; private static final int _DATA_Y = 1; private static final int _DATA_Z = 2; - + public void onSensorChanged(SensorEvent event) { float[] values = event.values; int orientation = ORIENTATION_UNKNOWN; float X = -values[_DATA_X]; float Y = -values[_DATA_Y]; - float Z = -values[_DATA_Z]; + float Z = -values[_DATA_Z]; float magnitude = X*X + Y*Y; // Don't trust the angle if the magnitude is small compared to the y value if (magnitude * 4 >= Z*Z) { @@ -130,7 +138,7 @@ public abstract class OrientationEventListener { // normalize to 0 - 359 range while (orientation >= 360) { orientation -= 360; - } + } while (orientation < 0) { orientation += 360; } @@ -148,7 +156,46 @@ 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 */ diff --git a/core/tests/coretests/src/android/view/OrientationEventListenerFrameworkTest.java b/core/tests/coretests/src/android/view/OrientationEventListenerFrameworkTest.java new file mode 100644 index 000000000000..5f06a706b1c8 --- /dev/null +++ b/core/tests/coretests/src/android/view/OrientationEventListenerFrameworkTest.java @@ -0,0 +1,201 @@ +/* + * 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); + } + } +} |