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

Commit e2d2715c authored by dshivangi's avatar dshivangi
Browse files

Fix: Prevent boot crash on foldable devices with missing auto-rotate config

The logic introduced in ag/34327514 causes devices to crash during boot if they are foldable, have auto-rotate enabled, and lack the `config_perDeviceStateRotationLockDefaults` configuration.

The current behavior is problematic because `config_perDeviceStateRotationLockDefaults` is an optional setting for device state-based auto-rotate.
A device should boot and function normally regardless of whether this optional setting is present.

To resolve this, the logic is updated to initialize device state-based auto-rotate classes only when config_perDeviceStateRotationLockDefaults is present and not empty.

Test: atest DisplayRotationTests DeviceStateAutoRotateSettingManagerProviderTest
Fixes: 431045703
Flag: EXEMPT bugfix
Change-Id: I53ac476987af89608fd5e6a5724a4926612f7951
parent d917e7b4
Loading
Loading
Loading
Loading
+19 −1
Original line number Diff line number Diff line
@@ -15,8 +15,11 @@
 */
package com.android.settingslib.devicestate;

import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;

import android.annotation.NonNull;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;

import com.android.internal.R;

@@ -26,8 +29,23 @@ public final class DeviceStateAutoRotateSettingUtils {

    /** Returns true if device-state based rotation lock settings are enabled. */
    public static boolean isDeviceStateRotationLockEnabled(@NonNull Context context) {
        final DeviceStateManager deviceStateManager = context.getSystemService(
                DeviceStateManager.class);
        if (deviceStateManager == null) return false;

        return context.getResources().getStringArray(
                R.array.config_perDeviceStateRotationLockDefaults).length > 0;
                R.array.config_perDeviceStateRotationLockDefaults).length > 0
                && isAutoRotateSupported(context) && hasFoldedState(deviceStateManager);

    }

    private static boolean isAutoRotateSupported(@NonNull Context context) {
        return context.getResources().getBoolean(R.bool.config_supportAutoRotation);
    }

    private static boolean hasFoldedState(DeviceStateManager deviceStateManager) {
        return deviceStateManager.getSupportedDeviceStates().stream()
                .anyMatch(state -> state.hasProperty(
                        PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED));
    }
}
+8 −3
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import static com.android.server.wm.DisplayRotationReversionController.REVERSION
import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_NOSENSOR;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.settingslib.devicestate.DeviceStateAutoRotateSettingUtils.isDeviceStateRotationLockEnabled;

import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -1703,7 +1704,9 @@ public class DisplayRotation {
    static DeviceStateAutoRotateSettingController createDeviceStateAutoRotateDependencies(
            @NonNull Context context, @NonNull DeviceStateController deviceStateController,
            @NonNull WindowManagerService wmService) {
        if (!deviceStateController.isFoldable() || !isAutoRotateSupported(context)) return null;
        if (!isDeviceStateRotationLockEnabled(context)) {
            return null;
        }
        if (!Flags.enableDeviceStateAutoRotateSettingLogging()
                && !Flags.enableDeviceStateAutoRotateSettingRefactor()) {
            return null;
@@ -1720,8 +1723,10 @@ public class DisplayRotation {
        }

        if (Flags.enableDeviceStateAutoRotateSettingRefactor()) {
            final DeviceStateManager deviceStateManager = context.getSystemService(
                    DeviceStateManager.class);
            final PostureDeviceStateConverter postureDeviceStateController =
                    new PostureDeviceStateConverter(context, new DeviceStateManager());
                    new PostureDeviceStateConverter(context, deviceStateManager);
            final DeviceStateAutoRotateSettingManager deviceStateAutoRotateSettingManager =
                    new DeviceStateAutoRotateSettingManagerImpl(
                            context, BackgroundThread.getExecutor(), secureSettings, wmService.mH,
+131 −1
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayCutout.NO_CUTOUT;
import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
@@ -47,6 +49,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.clearInvocations;

@@ -62,12 +66,16 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManagerInternal;
import android.os.SystemClock;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.Display;
import android.view.DisplayAddress;
@@ -78,6 +86,7 @@ import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;

import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.UiThread;
import com.android.server.policy.WindowManagerPolicy;
@@ -85,11 +94,13 @@ import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
import com.android.server.wm.DisplayContent.FixedRotationTransitionListener;
import com.android.window.flags.Flags;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

@@ -97,6 +108,8 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.IntConsumer;
@@ -111,6 +124,15 @@ import java.util.function.IntConsumer;
@Presubmit
public class DisplayRotationTests {
    private static final long UI_HANDLER_WAIT_TIMEOUT_MS = 50;
    private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(
            new DeviceState.Configuration.Builder(0, "DEFAULT").build());
    private static final DeviceState FOLDED_DEVICE_STATE = new DeviceState(
            new DeviceState.Configuration.Builder(1, "FOLDED").setPhysicalProperties(
                    Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED,
                            PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY)).build());

    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    private StatusBarManagerInternal mPreviousStatusBarManagerInternal;
    private static final OffsettableClock sClock = new OffsettableClock.Stopped();
@@ -155,9 +177,13 @@ public class DisplayRotationTests {
        sMockWm = mock(WindowManagerService.class);
        sMockWm.mPowerManagerInternal = mock(PowerManagerInternal.class);
        sMockWm.mPolicy = mock(WindowManagerPolicy.class);
        WindowTestsBase.setFieldValue(sMockWm, "mRoot", sMockRoot);
        sMockRoot.mDeviceStateAutoRotateSettingController = null;
        sHandler = new TestHandler(null, sClock);

        final WindowManagerService.H wmHandler = mock(WindowManagerService.H.class);
        when(wmHandler.getLooper()).thenReturn(sHandler.getLooper());
        WindowTestsBase.setFieldValue(sMockWm, "mRoot", sMockRoot);
        WindowTestsBase.setFieldValue(sMockWm, "mH", wmHandler);
    }

    @AfterClass
@@ -291,6 +317,56 @@ public class DisplayRotationTests {
                times(1)).requestDeviceStateAutoRotateSettingChange(eq(1), eq(false));
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR)
    public void createDeviceStateAutoRotateDependencies_flagEnabled_settingControllerNotNull() {
        final DeviceStateAutoRotateSettingController settingController =
                createDeviceStateAutoRotateDependencies(/* isFoldable= */ true,
                        /* autoRotateEnabled= */ true, /* isDeviceStateConfigNonEmpty= */ true);

        assertNotNull(settingController);
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR)
    public void createDeviceStateAutoRotateDependencies_flagDisabled_settingControllerNull() {
        final DeviceStateAutoRotateSettingController settingController =
                createDeviceStateAutoRotateDependencies(/* isFoldable= */ true,
                        /* autoRotateEnabled= */ true, /* isDeviceStateConfigNonEmpty= */ true);

        assertNull(settingController);
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR)
    public void createDeviceStateAutoRotateDependencies_notFoldable_settingControllerNull() {
        final DeviceStateAutoRotateSettingController settingController =
                createDeviceStateAutoRotateDependencies(/* isFoldable= */ false,
                        /* autoRotateEnabled= */ true, /* isDeviceStateConfigNonEmpty= */ true);

        assertNull(settingController);
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR)
    public void createDeviceStateAutoRotateDependencies_autoRotateDisabled_settingControllerNull() {
        final DeviceStateAutoRotateSettingController settingController =
                createDeviceStateAutoRotateDependencies(/* isFoldable= */ true,
                        /* autoRotateEnabled= */ false, /* isDeviceStateConfigNonEmpty= */ true);

        assertNull(settingController);
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR)
    public void createDeviceStateAutoRotateDependencies_dSAutoRotateConfigNull_settingControllerNull() {
        final DeviceStateAutoRotateSettingController settingController =
                createDeviceStateAutoRotateDependencies(/* isFoldable= */ true,
                        /* autoRotateEnabled= */ true, /* isDeviceStateConfigNonEmpty= */ false);

        assertNull(settingController);
    }

    @Test
    public void testUserRotationSystemProperty_NonDefault_InternalDisplay() throws Exception {
        mBuilder.setIsDefaultDisplay(false).build();
@@ -1414,6 +1490,60 @@ public class DisplayRotationTests {
        assertFalse("Display rotation should respect app requested orientation if"
                + " fixed to user rotation if no auto rotation.", mTarget.isFixedToUserRotation());
    }
    private DeviceStateAutoRotateSettingController createDeviceStateAutoRotateDependencies(
            boolean isFoldable, boolean autoRotateEnabled, boolean isDeviceStateConfigNonEmpty) {
        // init
        final Context context = mock(Context.class);
        final Resources resources = mock(Resources.class);
        final DeviceStateController deviceStateController = mock(DeviceStateController.class);
        final ContentResolver resolver = mock(ContentResolver.class);
        final DeviceStateManager deviceStateManager = mock(DeviceStateManager.class);
        // setup context
        when(context.getContentResolver()).thenReturn(resolver);
        when(context.getResources()).thenReturn(resources);
        when(context.getSystemService(eq(DeviceStateManager.class))).thenReturn(deviceStateManager);
        WindowTestsBase.setFieldValue(sMockWm, "mContext", context);

        setDeviceTypeFoldable(isFoldable, deviceStateManager, resources);
        setAutoRotateEnabled(autoRotateEnabled, resources);
        setDeviceStateAutoRotateConfig(isDeviceStateConfigNonEmpty, resources);

        return DisplayRotation.createDeviceStateAutoRotateDependencies(context,
                deviceStateController, sMockWm);
    }
    private void setAutoRotateEnabled(boolean isEnabled, Resources mockResources) {
        when(mockResources.getBoolean(R.bool.config_supportAutoRotation)).thenReturn(isEnabled);
    }

    private void setDeviceTypeFoldable(boolean isFoldable,
            DeviceStateManager mockDeviceStateManager, Resources mockResources) {
        if (mockDeviceStateManager != null) {
            List<DeviceState> deviceStates;
            if (isFoldable) {
                deviceStates = List.of(DEFAULT_DEVICE_STATE, FOLDED_DEVICE_STATE);
            } else {
                deviceStates = List.of(DEFAULT_DEVICE_STATE);
            }
            when(mockDeviceStateManager.getSupportedDeviceStates()).thenReturn(deviceStates);
        }

        if (mockResources != null) {
            when(mockResources.getIntArray(R.array.config_foldedDeviceStates)).thenReturn(
                    new int[]{PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
                            PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED});
        }
    }

    private void setDeviceStateAutoRotateConfig(boolean isNonEmpty, Resources mockResources) {
        if (isNonEmpty) {
            when(mockResources.getStringArray(
                    R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
                        new String[]{"0:1"});
        } else {
            when(mockResources.getStringArray(
                    R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(new String[]{});
        }
    }

    private void setImmersiveAppCompatRotationLockEnforced(boolean isRotationLockEnforced,
            int proposedRotation) {