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

Commit 25dc214a authored by Nick Chameyev's avatar Nick Chameyev
Browse files

Use DeviceStateManager to detect foldable device instead of config.xml

Some devices like goldfish emulator might not configure
correct device states, even though they provided
foldable-specific configurations.

Skip initializing foldable-specific dependencies in DisplayRotation
if there are no foldable device states in the DeviceStateManager.

Bug: 418414850
Test: atest DisplayRotationTests DeviceStateControllerTests
Test: check that per device autorotate works on a foldable
Test: check that goldfish emulator boots
Test: check that autorotate settings open
Flag: EXEMPT bugfix
Change-Id: I75f281e1abe18018d598c7fa2723bc79899b8039
parent a13e7291
Loading
Loading
Loading
Loading
+17 −6
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import android.app.settings.SettingsEnums;
import android.content.Context;

import androidx.annotation.VisibleForTesting;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
@@ -45,20 +47,29 @@ public class DeviceStateAutoRotateSettingController extends TogglePreferenceCont

    private TwoStatePreference mPreference;

    @NonNull
    private final DeviceStateAutoRotateSettingManager mAutoRotateSettingsManager;
    private final int mOrder;
    private final DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener
            mDeviceStateAutoRotateSettingListener = () -> updateState(mPreference);
    private final int mDeviceState;
    @NonNull
    private final String mDeviceStateDescription;
    @NonNull
    private final MetricsFeatureProvider mMetricsFeatureProvider;

    @VisibleForTesting
    DeviceStateAutoRotateSettingController(Context context, int deviceState,
            String deviceStateDescription, int order,
            MetricsFeatureProvider metricsFeatureProvider,
            DeviceStateAutoRotateSettingManager deviceStateAutoRotateSettingManager) {
    DeviceStateAutoRotateSettingController(@NonNull Context context, int deviceState,
            @NonNull String deviceStateDescription, int order,
            @NonNull MetricsFeatureProvider metricsFeatureProvider,
            @Nullable DeviceStateAutoRotateSettingManager deviceStateAutoRotateSettingManager) {
        super(context, getPreferenceKeyForDeviceState(deviceState));

        if (deviceStateAutoRotateSettingManager == null) {
            throw new IllegalStateException("DeviceStateAutoRotateSettingController should not be "
                    + "created when DeviceStateAutoRotateSettingManager is not present");
        }

        mMetricsFeatureProvider = metricsFeatureProvider;
        mDeviceState = deviceState;
        mDeviceStateDescription = deviceStateDescription;
@@ -66,8 +77,8 @@ public class DeviceStateAutoRotateSettingController extends TogglePreferenceCont
        mOrder = order;
    }

    public DeviceStateAutoRotateSettingController(Context context, int deviceState,
            String deviceStateDescription, int order) {
    public DeviceStateAutoRotateSettingController(@NonNull Context context, int deviceState,
            @NonNull String deviceStateDescription, int order) {
        this(context, deviceState, deviceStateDescription, order,
                FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(),
                DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(context));
+41 −13
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.settings.display

import android.content.Context
import android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED
import android.hardware.devicestate.DeviceStateManager
import android.os.Handler
import android.os.Looper
@@ -26,39 +27,66 @@ import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager
import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManagerProvider.createInstance
import com.android.settingslib.devicestate.PostureDeviceStateConverter
import com.android.settingslib.utils.ThreadUtils
import com.android.window.flags.Flags
import java.util.Optional

/**
 * Provides appropriate instance of [DeviceStateAutoRotateSettingManager], based on the value of
 * [Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR].
 */
object DeviceStateAutoRotateSettingManagerProvider {
    private var nullableSingletonSettingManager: DeviceStateAutoRotateSettingManager? = null
    /**
     * Will be:
     * - null if the singleton is not initialized yet
     * - empty optional if the current device is not supported by the manager
     * - non-empty optional if the current device is supported by the manager
     */
    private var singletonSettingManager: Optional<DeviceStateAutoRotateSettingManager>? =
        null

    /**
     * Provides a singleton instance of [DeviceStateAutoRotateSettingManager], based on the
     * value of[Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR]. It is supposed to
     * be used by apps that don't support dagger to provide and manager instance.
     * Returns null if the current device is not supported by the manager.
     */
    @JvmStatic
    fun getSingletonInstance(context: Context) =
        nullableSingletonSettingManager ?: createInstance(
    fun getSingletonInstance(context: Context): DeviceStateAutoRotateSettingManager? {
        val managerOptional = singletonSettingManager ?: createManager(context)
            .also {
                singletonSettingManager = it
            }

        return managerOptional.orElse(null)
    }

    private fun createManager(context: Context): Optional<DeviceStateAutoRotateSettingManager> {
        val deviceStateManager =
            context.getSystemService(DeviceStateManager::class.java) ?: return Optional.empty()
        if (!deviceStateManager.hasFoldedState()) return Optional.empty()

        return Optional.of(
            createInstance(
                context,
                ThreadUtils.getBackgroundExecutor(),
                AndroidSecureSettings(context.contentResolver),
                Handler(Looper.getMainLooper()),
                PostureDeviceStateConverter(
                    context,
                context.getSystemService(DeviceStateManager::class.java)
                    deviceStateManager
                )
            )
        )
        ).also {
            nullableSingletonSettingManager = it
    }

    private fun DeviceStateManager.hasFoldedState(): Boolean =
        supportedDeviceStates.any {
            it.hasProperty(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)
        }

    /** Resets the singleton instance of [DeviceStateAutoRotateSettingManager]. */
    @JvmStatic
    @VisibleForTesting
    fun resetInstance() {
        nullableSingletonSettingManager = null
        singletonSettingManager = null
    }
}
+14 −4
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.internal.view.RotationPolicy;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager;
import com.android.settingslib.devicestate.SettableDeviceState;
import com.android.settingslib.search.SearchIndexableRaw;

@@ -52,8 +53,13 @@ public class DeviceStateAutoRotationHelper {

    static ImmutableList<AbstractPreferenceController> createPreferenceControllers(
            Context context) {
        List<SettableDeviceState> settableDeviceStates = DeviceStateAutoRotateSettingManagerProvider
                .getSingletonInstance(context).getSettableDeviceStates();
        final DeviceStateAutoRotateSettingManager manager =
                DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(context);
        if (manager == null) {
            return ImmutableList.of();
        }

        List<SettableDeviceState> settableDeviceStates = manager.getSettableDeviceStates();
        int numDeviceStates = settableDeviceStates.size();
        if (numDeviceStates == 0) {
            return ImmutableList.of();
@@ -100,7 +106,9 @@ public class DeviceStateAutoRotationHelper {
    /** Returns whether the device state based auto-rotation settings are enabled. */
    public static boolean isDeviceStateRotationEnabled(Context context) {
        return RotationPolicy.isRotationLockToggleVisible(context)
                && isDeviceStateRotationLockEnabled(context);
                && isDeviceStateRotationLockEnabled(context)
                && DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(context)
                != null;
    }

    /**
@@ -109,6 +117,8 @@ public class DeviceStateAutoRotationHelper {
     */
    public static boolean isDeviceStateRotationEnabledForA11y(Context context) {
        return RotationPolicy.isRotationSupported(context)
                && isDeviceStateRotationLockEnabled(context);
                && isDeviceStateRotationLockEnabled(context)
                && DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(context)
                != null;
    }
}
+13 −6
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.service.rotationresolver.RotationResolverService;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
@@ -75,6 +76,7 @@ public class SmartAutoRotateController extends TogglePreferenceController implem
                }
            };

    @Nullable
    private final DeviceStateAutoRotateSettingManager mDeviceStateAutoRotateSettingsManager;
    private final DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener
            mDeviceStateAutoRotateSettingListener = () -> updateState(mPreference);
@@ -89,7 +91,7 @@ public class SmartAutoRotateController extends TogglePreferenceController implem
    public SmartAutoRotateController(
            @NonNull Context context,
            @NonNull String preferenceKey,
            @NonNull DeviceStateAutoRotateSettingManager deviceStateAutoRotateSettingManager) {
            @Nullable DeviceStateAutoRotateSettingManager deviceStateAutoRotateSettingManager) {
        super(context, preferenceKey);
        mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
        mPrivacyManager = SensorPrivacyManager.getInstance(context);
@@ -107,7 +109,8 @@ public class SmartAutoRotateController extends TogglePreferenceController implem
    }

    protected boolean isRotationLocked() {
        if (DeviceStateAutoRotationHelper.isDeviceStateRotationEnabled(mContext)) {
        if (DeviceStateAutoRotationHelper.isDeviceStateRotationEnabled(mContext)
                && mDeviceStateAutoRotateSettingsManager != null) {
            // It is highly unlikely to receive null value here. In the improbable event of null, a
            // non-null update will follow shortly, and the users will potentially see incorrect
            // state for a short time.
@@ -153,8 +156,10 @@ public class SmartAutoRotateController extends TogglePreferenceController implem
            };
        }
        RotationPolicy.registerRotationPolicyListener(mContext, mRotationPolicyListener);
        if (mDeviceStateAutoRotateSettingsManager != null) {
            mDeviceStateAutoRotateSettingsManager.registerListener(
                    mDeviceStateAutoRotateSettingListener);
        }
        mPrivacyManager.addSensorPrivacyListener(CAMERA, mPrivacyChangedListener);
    }

@@ -165,8 +170,10 @@ public class SmartAutoRotateController extends TogglePreferenceController implem
            RotationPolicy.unregisterRotationPolicyListener(mContext, mRotationPolicyListener);
            mRotationPolicyListener = null;
        }
        if (mDeviceStateAutoRotateSettingsManager != null) {
            mDeviceStateAutoRotateSettingsManager.unregisterListener(
                    mDeviceStateAutoRotateSettingListener);
        }
        mPrivacyManager.removeSensorPrivacyListener(CAMERA, mPrivacyChangedListener);
    }

+7 −2
Original line number Diff line number Diff line
@@ -20,10 +20,12 @@ import static com.android.settings.testutils.DeviceStateAutoRotateSettingTestUti

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.res.Resources;
import android.hardware.devicestate.DeviceStateManager;
import android.os.UserHandle;
import android.provider.Settings;

@@ -50,6 +52,8 @@ import org.robolectric.annotation.Config;
public class LockScreenRotationPreferenceControllerTest {
    @Mock
    private Resources mResources;
    @Mock
    private DeviceStateManager mDeviceStateManager;
    private Context mContext;
    private SwitchPreference mPreference;
    private LockScreenRotationPreferenceController mController;
@@ -60,6 +64,7 @@ public class LockScreenRotationPreferenceControllerTest {
        mContext = Mockito.spy(RuntimeEnvironment.application);
        mPreference = new SwitchPreference(mContext);
        when(mContext.getResources()).thenReturn(mResources);
        doReturn(mDeviceStateManager).when(mContext).getSystemService(DeviceStateManager.class);

        mController = new LockScreenRotationPreferenceController(mContext, "lock_screen");
    }
@@ -68,7 +73,7 @@ public class LockScreenRotationPreferenceControllerTest {
    @Config(shadows = {ShadowRotationPolicy.class})
    public void getAvailabilityStatus_supportedRotation_shouldReturnAvailable() {
        ShadowRotationPolicy.setRotationSupported(true /* supported */);
        setDeviceStateRotationLockEnabled(false, mResources);
        setDeviceStateRotationLockEnabled(/* enable= */ false, mResources, mDeviceStateManager);

        assertThat(mController.getAvailabilityStatus()).isEqualTo(
                BasePreferenceController.AVAILABLE);
@@ -78,7 +83,7 @@ public class LockScreenRotationPreferenceControllerTest {
    @Config(shadows = {ShadowRotationPolicy.class})
    public void getAvailabilityStatus_deviceStateRotationEnabled_returnsUnsupported() {
        ShadowRotationPolicy.setRotationSupported(true /* supported */);
        setDeviceStateRotationLockEnabled(true, mResources);
        setDeviceStateRotationLockEnabled(/* enable= */ true, mResources, mDeviceStateManager);

        assertThat(mController.getAvailabilityStatus()).isEqualTo(
                BasePreferenceController.UNSUPPORTED_ON_DEVICE);
Loading