Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a0b7a030 authored by Mina Granic's avatar Mina Granic Committed by Android (Google) Code Review
Browse files

Merge "Sandbox OrientationEventListener orientation when sandboxing Display rotation." into main

parents 543337c7 0f436120
Loading
Loading
Loading
Loading
+0 −3
Original line number Diff line number Diff line
@@ -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;
    }
+53 −6
Original line number Diff line number Diff line
@@ -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();
        }
    }
    
@@ -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
     */
+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);
        }
    }
}