Loading packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt +46 −7 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settingslib.devicestate import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK import android.util.Dumpable import android.util.SparseIntArray /** * Interface for managing [DEVICE_STATE_ROTATION_LOCK] setting. Loading @@ -42,21 +43,59 @@ interface DeviceStateAutoRotateSettingManager : Dumpable { fun unregisterListener(settingListener: DeviceStateAutoRotateSettingListener) /** * Write [deviceState]'s setting value as [autoRotate], for [DEVICE_STATE_ROTATION_LOCK] setting. * Write [deviceState]'s setting value as [autoRotate], for [DEVICE_STATE_ROTATION_LOCK] * setting. */ fun updateSetting(deviceState: Int, autoRotate: Boolean) /** Get [DEVICE_STATE_ROTATION_LOCK] setting value for [deviceState]. */ fun getRotationLockSetting(deviceState: Int): Int /** * Get [DEVICE_STATE_ROTATION_LOCK] setting value for [deviceState]. Returns null if string * value of [DEVICE_STATE_ROTATION_LOCK] is corrupted. * * If the value is null, system_server will shortly reset the value of * [DEVICE_STATE_ROTATION_LOCK]. Clients can either subscribe to setting changes or query this * API again after a brief delay. */ fun getRotationLockSetting(deviceState: Int): Int? /** * Get [DEVICE_STATE_ROTATION_LOCK] setting value in form of integer to integer map. Returns * null if string value of [DEVICE_STATE_ROTATION_LOCK] is corrupted. * * If the value is null, system_server will shortly reset the value of * [DEVICE_STATE_ROTATION_LOCK]. Clients can either subscribe to setting changes or query this * API again after a brief delay. */ fun getRotationLockSetting(): SparseIntArray? /** Returns true if auto-rotate setting is OFF for [deviceState]. */ fun isRotationLocked(deviceState: Int): Boolean /** * Returns true if auto-rotate setting is OFF for [deviceState]. Returns null if string value * of [DEVICE_STATE_ROTATION_LOCK] is corrupted. * * If the value is null, system_server will shortly reset the value of * [DEVICE_STATE_ROTATION_LOCK]. Clients can either subscribe to setting changes or query this * API again after a brief delay. */ fun isRotationLocked(deviceState: Int): Boolean? /** Returns true if the auto-rotate setting value for all device states is OFF. */ fun isRotationLockedForAllStates(): Boolean /** * Returns true if the auto-rotate setting value for all device states is OFF. Returns null if * string value of [DEVICE_STATE_ROTATION_LOCK] is corrupted. * * If the value is null, system_server will shortly reset the value of * [DEVICE_STATE_ROTATION_LOCK]. Clients can either subscribe to setting changes or query this * API again after a brief delay. */ fun isRotationLockedForAllStates(): Boolean? /** Returns a list of device states and their respective auto rotate setting availability. */ fun getSettableDeviceStates(): List<SettableDeviceState> /** * Returns default value of [DEVICE_STATE_ROTATION_LOCK] setting from config, in form of integer * to integer map. */ fun getDefaultRotationLockSetting(): SparseIntArray } /** Represents a device state and whether it has an auto-rotation setting. */ Loading packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt +81 −42 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import android.content.Context import android.database.ContentObserver import android.os.Handler import android.os.UserHandle import android.provider.Settings import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED Loading Loading @@ -48,6 +49,7 @@ class DeviceStateAutoRotateSettingManagerImpl( private val settingListeners: MutableList<DeviceStateAutoRotateSettingListener> = mutableListOf() private val fallbackPostureMap = SparseIntArray() private val defaultDeviceStateAutoRotateSetting = SparseIntArray() private val settableDeviceState: MutableList<SettableDeviceState> = mutableListOf() private val autoRotateSettingValue: String Loading Loading @@ -76,28 +78,47 @@ class DeviceStateAutoRotateSettingManagerImpl( } } override fun getRotationLockSetting(deviceState: Int): Int { @Settings.Secure.DeviceStateRotationLockSetting override fun getRotationLockSetting(deviceState: Int): Int? { val devicePosture = posturesHelper.deviceStateToPosture(deviceState) val serializedSetting = autoRotateSettingValue val autoRotateSetting = extractSettingForDevicePosture(devicePosture, serializedSetting) val deviceStateAutoRotateSetting = getRotationLockSetting() val autoRotateSettingValue = extractSettingForDevicePosture(devicePosture, deviceStateAutoRotateSetting) // If the setting is ignored for this posture, check the fallback posture. if (autoRotateSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { if (autoRotateSettingValue == DEVICE_STATE_ROTATION_LOCK_IGNORED) { val fallbackPosture = fallbackPostureMap.get(devicePosture, DEVICE_STATE_ROTATION_LOCK_IGNORED) return extractSettingForDevicePosture(fallbackPosture, serializedSetting) return extractSettingForDevicePosture(fallbackPosture, deviceStateAutoRotateSetting) } return autoRotateSettingValue } return autoRotateSetting override fun getRotationLockSetting(): SparseIntArray? { val serializedSetting = autoRotateSettingValue if (serializedSetting.isEmpty()) return null return try { serializedSetting .split(SEPARATOR_REGEX) .hasEvenSize() .chunked(2) .map(::parsePostureSettingPair) .toSparseIntArray() } catch (e: Exception) { Log.w( TAG, "Invalid format in serializedSetting=$serializedSetting: ${e.message}" ) return null } } override fun isRotationLocked(deviceState: Int) = getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED getRotationLockSetting(deviceState)?.let { it == DEVICE_STATE_ROTATION_LOCK_LOCKED } override fun isRotationLockedForAllStates(): Boolean = convertSerializedSettingToMap(autoRotateSettingValue).all { (_, value) -> value == DEVICE_STATE_ROTATION_LOCK_LOCKED } override fun isRotationLockedForAllStates(): Boolean? = getRotationLockSetting()?.allSettingValuesLocked() override fun getSettableDeviceStates(): List<SettableDeviceState> = settableDeviceState Loading @@ -115,39 +136,40 @@ class DeviceStateAutoRotateSettingManagerImpl( indentingWriter.decreaseIndent() } override fun getDefaultRotationLockSetting() = defaultDeviceStateAutoRotateSetting.clone() private fun notifyListeners() = settingListeners.forEach { listener -> listener.onSettingsChanged() } /** * Loads the [R.array.config_perDeviceStateRotationLockDefaults] array and populates the * [fallbackPostureMap], [settableDeviceState], and [defaultDeviceStateAutoRotateSetting] * fields. */ private fun loadAutoRotateDeviceStates(context: Context) { val perDeviceStateAutoRotateDefaults = context.resources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults) for (entry in perDeviceStateAutoRotateDefaults) { entry.parsePostureEntry()?.let { (posture, autoRotate, fallbackPosture) -> if (autoRotate == DEVICE_STATE_ROTATION_LOCK_IGNORED && fallbackPosture != null) { fallbackPostureMap.put(posture, fallbackPosture) posturesHelper.postureToDeviceState(posture).also { if (it == null) { Log.wtf(TAG, "No matching device state for posture: $posture") return@also } settableDeviceState.add( SettableDeviceState(posture, autoRotate != DEVICE_STATE_ROTATION_LOCK_IGNORED) SettableDeviceState( it, autoRotate != DEVICE_STATE_ROTATION_LOCK_IGNORED ) ) } if (autoRotate == DEVICE_STATE_ROTATION_LOCK_IGNORED && fallbackPosture != null) { fallbackPostureMap.put(posture, fallbackPosture) } else if (autoRotate == DEVICE_STATE_ROTATION_LOCK_IGNORED) { Log.w(TAG, "Auto rotate setting is IGNORED, but no fallback-posture defined") } defaultDeviceStateAutoRotateSetting.put(posture, autoRotate) } private fun convertSerializedSettingToMap(serializedSetting: String): Map<Int, Int> { if (serializedSetting.isEmpty()) return emptyMap() return try { serializedSetting .split(SEPARATOR_REGEX) .hasEvenSize() .chunked(2) .mapNotNull(::parsePostureSettingPair) .toMap() } catch (e: Exception) { Log.w( TAG, "Invalid format in serializedSetting=$serializedSetting: ${e.message}" ) return emptyMap() } } Loading @@ -158,30 +180,30 @@ class DeviceStateAutoRotateSettingManagerImpl( return this } private fun parsePostureSettingPair(settingPair: List<String>): Pair<Int, Int>? { private fun parsePostureSettingPair(settingPair: List<String>): Pair<Int, Int> { return settingPair.let { (keyStr, valueStr) -> val key = keyStr.toIntOrNull() val value = valueStr.toIntOrNull() if (key != null && value != null && value in 0..2) { key to value } else { Log.w(TAG, "Invalid key or value in pair: $keyStr, $valueStr") null // Invalid pair, skip it throw IllegalStateException("Invalid key or value in pair: $keyStr, $valueStr") } } } private fun extractSettingForDevicePosture( devicePosture: Int, serializedSetting: String ): Int = convertSerializedSettingToMap(serializedSetting)[devicePosture] ?: DEVICE_STATE_ROTATION_LOCK_IGNORED deviceStateAutoRotateSetting: SparseIntArray? ): Int? = deviceStateAutoRotateSetting?.let { it[devicePosture] ?: DEVICE_STATE_ROTATION_LOCK_IGNORED } private fun String.parsePostureEntry(): Triple<Int, Int, Int?>? { val values = split(SEPARATOR_REGEX) if (values.size !in 2..3) { // It should contain 2 or 3 values. Log.w(TAG, "Invalid number of values in entry: '$this'") Log.wtf(TAG, "Invalid number of values in entry: '$this'") return null } return try { Loading @@ -190,13 +212,30 @@ class DeviceStateAutoRotateSettingManagerImpl( val fallbackPosture = if (values.size == 3) values[2].toIntOrNull() else null Triple(posture, rotationLockSetting, fallbackPosture) } catch (e: NumberFormatException) { Log.w(TAG, "Invalid number format in '$this': ${e.message}") Log.wtf(TAG, "Invalid number format in '$this': ${e.message}") null } } private fun List<Pair<Int, Int>>.toSparseIntArray(): SparseIntArray { val sparseArray = SparseIntArray() forEach { (key, value) -> sparseArray.put(key, value) } return sparseArray } private fun SparseIntArray.allSettingValuesLocked(): Boolean { for (i in 0 until size()) { if (valueAt(i) != DEVICE_STATE_ROTATION_LOCK_LOCKED) { return false } } return true } companion object { private const val TAG = "DeviceStateAutoRotate" private const val TAG = "DSAutoRotateMngr" private const val SEPARATOR_REGEX = ":" } } packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java +16 −5 Original line number Diff line number Diff line Loading @@ -42,8 +42,10 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** Loading @@ -56,8 +58,6 @@ public final class DeviceStateRotationLockSettingsManager implements private static final String TAG = "DSRotLockSettingsMngr"; private static final String SEPARATOR_REGEX = ":"; private static DeviceStateRotationLockSettingsManager sSingleton; private final Handler mMainHandler = new Handler(Looper.getMainLooper()); private final Set<DeviceStateAutoRotateSettingListener> mListeners = new HashSet<>(); private final SecureSettings mSecureSettings; Loading Loading @@ -140,6 +140,11 @@ public final class DeviceStateRotationLockSettingsManager implements persistSettings(); } @Override public SparseIntArray getRotationLockSetting() { return mPostureRotationLockSettings.clone(); } /** * Returns the {@link Settings.Secure.DeviceStateRotationLockSetting} for the given device * state. Loading @@ -152,7 +157,7 @@ public final class DeviceStateRotationLockSettingsManager implements */ @Settings.Secure.DeviceStateRotationLockSetting @Override public int getRotationLockSetting(int deviceState) { public Integer getRotationLockSetting(int deviceState) { int devicePosture = mPosturesHelper.deviceStateToPosture(deviceState); int rotationLockSetting = mPostureRotationLockSettings.get( devicePosture, /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED); Loading @@ -176,7 +181,7 @@ public final class DeviceStateRotationLockSettingsManager implements /** Returns true if the rotation is locked for the current device state */ @Override public boolean isRotationLocked(int deviceState) { public Boolean isRotationLocked(int deviceState) { return getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED; } Loading @@ -185,7 +190,7 @@ public final class DeviceStateRotationLockSettingsManager implements * DEVICE_STATE_ROTATION_LOCK_UNLOCKED}. */ @Override public boolean isRotationLockedForAllStates() { public Boolean isRotationLockedForAllStates() { for (int i = 0; i < mPostureRotationLockSettings.size(); i++) { if (mPostureRotationLockSettings.valueAt(i) == DEVICE_STATE_ROTATION_LOCK_UNLOCKED) { Loading @@ -203,6 +208,12 @@ public final class DeviceStateRotationLockSettingsManager implements return new ArrayList<>(mSettableDeviceStates); } @NonNull @Override public SparseIntArray getDefaultRotationLockSetting() { return mPostureDefaultRotationLockSettings.clone(); } private void initializeInMemoryMap() { String serializedSetting = getPersistedSettingValue(); if (TextUtils.isEmpty(serializedSetting)) { Loading packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt +89 −20 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.never Loading Loading @@ -99,16 +100,7 @@ class DeviceStateAutoRotateSettingManagerImplTest { } whenever(mockContext.getSystemService(DeviceStateManager::class.java)) .thenReturn(mockDeviceStateManager) whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_UNFOLDED)) .thenReturn(DEVICE_STATE_ROTATION_KEY_UNFOLDED) whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_FOLDED)) .thenReturn(DEVICE_STATE_ROTATION_KEY_FOLDED) whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_HALF_FOLDED)) .thenReturn(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED) whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_INVALID)) .thenReturn(DEVICE_STATE_ROTATION_LOCK_IGNORED) whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_REAR_DISPLAY)) .thenReturn(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY) setUpMockPostureHelper() settingManager = DeviceStateAutoRotateSettingManagerImpl( Loading Loading @@ -173,10 +165,10 @@ class DeviceStateAutoRotateSettingManagerImplTest { } @Test fun getAutoRotateSetting_forInvalidPostureWithNoFallback_returnsIgnored() { fun getAutoRotateSetting_forInvalidPostureWithNoFallback_returnsNull() { val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_INVALID) assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED) assertThat(autoRotateSetting).isNull() } @Test Loading @@ -192,21 +184,21 @@ class DeviceStateAutoRotateSettingManagerImplTest { } @Test fun getAutoRotateSetting_invalidFormat_returnsIgnored() { fun getAutoRotateSetting_invalidFormat_returnsNull() { persistSettings("invalid_format") val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED) assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED) assertThat(autoRotateSetting).isNull() } @Test fun getAutoRotateSetting_invalidNumberFormat_returnsIgnored() { fun getAutoRotateSetting_invalidNumberFormat_returnsNull() { persistSettings("$DEVICE_STATE_ROTATION_KEY_FOLDED:4") val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED) assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED) assertThat(autoRotateSetting).isNull() } @Test Loading Loading @@ -274,11 +266,66 @@ class DeviceStateAutoRotateSettingManagerImplTest { assertThat(settableDeviceStates) .containsExactly( SettableDeviceState(DEVICE_STATE_ROTATION_KEY_UNFOLDED, isSettable = true), SettableDeviceState(DEVICE_STATE_ROTATION_KEY_FOLDED, isSettable = true), SettableDeviceState(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED, isSettable = false), SettableDeviceState(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY, isSettable = false), SettableDeviceState(DEVICE_STATE_UNFOLDED, isSettable = true), SettableDeviceState(DEVICE_STATE_FOLDED, isSettable = true), SettableDeviceState(DEVICE_STATE_HALF_FOLDED, isSettable = false), SettableDeviceState(DEVICE_STATE_REAR_DISPLAY, isSettable = false), ) } @Test fun getRotationLockSettingMap_multipleSettings_returnsCorrectMap() { persistSettings( "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED:" + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED" ) val expectedPairs = mapOf( DEVICE_STATE_ROTATION_KEY_UNFOLDED to DEVICE_STATE_ROTATION_LOCK_LOCKED, DEVICE_STATE_ROTATION_KEY_FOLDED to DEVICE_STATE_ROTATION_LOCK_UNLOCKED ) val deviceStateAutoRotateSetting = settingManager.getRotationLockSetting() assertThat(deviceStateAutoRotateSetting).isNotNull() // Check if all expected pairs are present expectedPairs.forEach { (key, value) -> assertThat(deviceStateAutoRotateSetting?.indexOfKey(key)).isGreaterThan(-1) assertThat(value).isEqualTo(deviceStateAutoRotateSetting?.get(key)) } // Check if no unexpected pairs are present assertThat(expectedPairs.size).isEqualTo(deviceStateAutoRotateSetting?.size()) } @Test fun getRotationLockSettingMap_invalidFormat_returnsNull() { persistSettings( "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED" + ":invalid_format" ) val deviceStateAutoRotateSetting = settingManager.getRotationLockSetting() assertThat(deviceStateAutoRotateSetting).isNull() } @Test fun getDefaultRotationLockSetting_returnsDefaultsFromConfig() { val expectedPairs = mapOf( DEVICE_STATE_ROTATION_KEY_HALF_FOLDED to DEVICE_STATE_ROTATION_LOCK_IGNORED, DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY to DEVICE_STATE_ROTATION_LOCK_IGNORED, DEVICE_STATE_ROTATION_KEY_UNFOLDED to DEVICE_STATE_ROTATION_LOCK_LOCKED, DEVICE_STATE_ROTATION_KEY_FOLDED to DEVICE_STATE_ROTATION_LOCK_LOCKED ) val defaultDeviceStateAutoRotateSetting = settingManager.getDefaultRotationLockSetting() // Check if all expected pairs are present expectedPairs.forEach { (key, value) -> assertThat(defaultDeviceStateAutoRotateSetting.indexOfKey(key)).isGreaterThan(-1) assertThat(value).isEqualTo(defaultDeviceStateAutoRotateSetting.get(key)) } // Check if no unexpected pairs are present assertThat(expectedPairs.size).isEqualTo(defaultDeviceStateAutoRotateSetting.size()) } private fun persistSettings(devicePosture: Int, autoRotateSetting: Int) { Loading @@ -291,6 +338,28 @@ class DeviceStateAutoRotateSettingManagerImplTest { ) } private fun setUpMockPostureHelper() { whenever(mockPosturesHelper.deviceStateToPosture(eq(DEVICE_STATE_UNFOLDED))) .thenReturn(DEVICE_STATE_ROTATION_KEY_UNFOLDED) whenever(mockPosturesHelper.deviceStateToPosture(eq(DEVICE_STATE_FOLDED))) .thenReturn(DEVICE_STATE_ROTATION_KEY_FOLDED) whenever(mockPosturesHelper.deviceStateToPosture(eq(DEVICE_STATE_HALF_FOLDED))) .thenReturn(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED) whenever(mockPosturesHelper.deviceStateToPosture(eq(DEVICE_STATE_INVALID))) .thenReturn(DEVICE_STATE_ROTATION_LOCK_IGNORED) whenever(mockPosturesHelper.deviceStateToPosture(eq(DEVICE_STATE_REAR_DISPLAY))) .thenReturn(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY) whenever(mockPosturesHelper.postureToDeviceState(eq(DEVICE_STATE_ROTATION_KEY_UNFOLDED))) .thenReturn(DEVICE_STATE_UNFOLDED) whenever(mockPosturesHelper.postureToDeviceState(eq(DEVICE_STATE_ROTATION_KEY_FOLDED))) .thenReturn(DEVICE_STATE_FOLDED) whenever(mockPosturesHelper.postureToDeviceState(eq(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED))) .thenReturn(DEVICE_STATE_HALF_FOLDED) whenever(mockPosturesHelper.postureToDeviceState(eq(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY))) .thenReturn(DEVICE_STATE_REAR_DISPLAY) } private companion object { const val DEVICE_STATE_FOLDED = 0 const val DEVICE_STATE_HALF_FOLDED = 1 Loading Loading
packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt +46 −7 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settingslib.devicestate import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK import android.util.Dumpable import android.util.SparseIntArray /** * Interface for managing [DEVICE_STATE_ROTATION_LOCK] setting. Loading @@ -42,21 +43,59 @@ interface DeviceStateAutoRotateSettingManager : Dumpable { fun unregisterListener(settingListener: DeviceStateAutoRotateSettingListener) /** * Write [deviceState]'s setting value as [autoRotate], for [DEVICE_STATE_ROTATION_LOCK] setting. * Write [deviceState]'s setting value as [autoRotate], for [DEVICE_STATE_ROTATION_LOCK] * setting. */ fun updateSetting(deviceState: Int, autoRotate: Boolean) /** Get [DEVICE_STATE_ROTATION_LOCK] setting value for [deviceState]. */ fun getRotationLockSetting(deviceState: Int): Int /** * Get [DEVICE_STATE_ROTATION_LOCK] setting value for [deviceState]. Returns null if string * value of [DEVICE_STATE_ROTATION_LOCK] is corrupted. * * If the value is null, system_server will shortly reset the value of * [DEVICE_STATE_ROTATION_LOCK]. Clients can either subscribe to setting changes or query this * API again after a brief delay. */ fun getRotationLockSetting(deviceState: Int): Int? /** * Get [DEVICE_STATE_ROTATION_LOCK] setting value in form of integer to integer map. Returns * null if string value of [DEVICE_STATE_ROTATION_LOCK] is corrupted. * * If the value is null, system_server will shortly reset the value of * [DEVICE_STATE_ROTATION_LOCK]. Clients can either subscribe to setting changes or query this * API again after a brief delay. */ fun getRotationLockSetting(): SparseIntArray? /** Returns true if auto-rotate setting is OFF for [deviceState]. */ fun isRotationLocked(deviceState: Int): Boolean /** * Returns true if auto-rotate setting is OFF for [deviceState]. Returns null if string value * of [DEVICE_STATE_ROTATION_LOCK] is corrupted. * * If the value is null, system_server will shortly reset the value of * [DEVICE_STATE_ROTATION_LOCK]. Clients can either subscribe to setting changes or query this * API again after a brief delay. */ fun isRotationLocked(deviceState: Int): Boolean? /** Returns true if the auto-rotate setting value for all device states is OFF. */ fun isRotationLockedForAllStates(): Boolean /** * Returns true if the auto-rotate setting value for all device states is OFF. Returns null if * string value of [DEVICE_STATE_ROTATION_LOCK] is corrupted. * * If the value is null, system_server will shortly reset the value of * [DEVICE_STATE_ROTATION_LOCK]. Clients can either subscribe to setting changes or query this * API again after a brief delay. */ fun isRotationLockedForAllStates(): Boolean? /** Returns a list of device states and their respective auto rotate setting availability. */ fun getSettableDeviceStates(): List<SettableDeviceState> /** * Returns default value of [DEVICE_STATE_ROTATION_LOCK] setting from config, in form of integer * to integer map. */ fun getDefaultRotationLockSetting(): SparseIntArray } /** Represents a device state and whether it has an auto-rotation setting. */ Loading
packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt +81 −42 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import android.content.Context import android.database.ContentObserver import android.os.Handler import android.os.UserHandle import android.provider.Settings import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED Loading Loading @@ -48,6 +49,7 @@ class DeviceStateAutoRotateSettingManagerImpl( private val settingListeners: MutableList<DeviceStateAutoRotateSettingListener> = mutableListOf() private val fallbackPostureMap = SparseIntArray() private val defaultDeviceStateAutoRotateSetting = SparseIntArray() private val settableDeviceState: MutableList<SettableDeviceState> = mutableListOf() private val autoRotateSettingValue: String Loading Loading @@ -76,28 +78,47 @@ class DeviceStateAutoRotateSettingManagerImpl( } } override fun getRotationLockSetting(deviceState: Int): Int { @Settings.Secure.DeviceStateRotationLockSetting override fun getRotationLockSetting(deviceState: Int): Int? { val devicePosture = posturesHelper.deviceStateToPosture(deviceState) val serializedSetting = autoRotateSettingValue val autoRotateSetting = extractSettingForDevicePosture(devicePosture, serializedSetting) val deviceStateAutoRotateSetting = getRotationLockSetting() val autoRotateSettingValue = extractSettingForDevicePosture(devicePosture, deviceStateAutoRotateSetting) // If the setting is ignored for this posture, check the fallback posture. if (autoRotateSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { if (autoRotateSettingValue == DEVICE_STATE_ROTATION_LOCK_IGNORED) { val fallbackPosture = fallbackPostureMap.get(devicePosture, DEVICE_STATE_ROTATION_LOCK_IGNORED) return extractSettingForDevicePosture(fallbackPosture, serializedSetting) return extractSettingForDevicePosture(fallbackPosture, deviceStateAutoRotateSetting) } return autoRotateSettingValue } return autoRotateSetting override fun getRotationLockSetting(): SparseIntArray? { val serializedSetting = autoRotateSettingValue if (serializedSetting.isEmpty()) return null return try { serializedSetting .split(SEPARATOR_REGEX) .hasEvenSize() .chunked(2) .map(::parsePostureSettingPair) .toSparseIntArray() } catch (e: Exception) { Log.w( TAG, "Invalid format in serializedSetting=$serializedSetting: ${e.message}" ) return null } } override fun isRotationLocked(deviceState: Int) = getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED getRotationLockSetting(deviceState)?.let { it == DEVICE_STATE_ROTATION_LOCK_LOCKED } override fun isRotationLockedForAllStates(): Boolean = convertSerializedSettingToMap(autoRotateSettingValue).all { (_, value) -> value == DEVICE_STATE_ROTATION_LOCK_LOCKED } override fun isRotationLockedForAllStates(): Boolean? = getRotationLockSetting()?.allSettingValuesLocked() override fun getSettableDeviceStates(): List<SettableDeviceState> = settableDeviceState Loading @@ -115,39 +136,40 @@ class DeviceStateAutoRotateSettingManagerImpl( indentingWriter.decreaseIndent() } override fun getDefaultRotationLockSetting() = defaultDeviceStateAutoRotateSetting.clone() private fun notifyListeners() = settingListeners.forEach { listener -> listener.onSettingsChanged() } /** * Loads the [R.array.config_perDeviceStateRotationLockDefaults] array and populates the * [fallbackPostureMap], [settableDeviceState], and [defaultDeviceStateAutoRotateSetting] * fields. */ private fun loadAutoRotateDeviceStates(context: Context) { val perDeviceStateAutoRotateDefaults = context.resources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults) for (entry in perDeviceStateAutoRotateDefaults) { entry.parsePostureEntry()?.let { (posture, autoRotate, fallbackPosture) -> if (autoRotate == DEVICE_STATE_ROTATION_LOCK_IGNORED && fallbackPosture != null) { fallbackPostureMap.put(posture, fallbackPosture) posturesHelper.postureToDeviceState(posture).also { if (it == null) { Log.wtf(TAG, "No matching device state for posture: $posture") return@also } settableDeviceState.add( SettableDeviceState(posture, autoRotate != DEVICE_STATE_ROTATION_LOCK_IGNORED) SettableDeviceState( it, autoRotate != DEVICE_STATE_ROTATION_LOCK_IGNORED ) ) } if (autoRotate == DEVICE_STATE_ROTATION_LOCK_IGNORED && fallbackPosture != null) { fallbackPostureMap.put(posture, fallbackPosture) } else if (autoRotate == DEVICE_STATE_ROTATION_LOCK_IGNORED) { Log.w(TAG, "Auto rotate setting is IGNORED, but no fallback-posture defined") } defaultDeviceStateAutoRotateSetting.put(posture, autoRotate) } private fun convertSerializedSettingToMap(serializedSetting: String): Map<Int, Int> { if (serializedSetting.isEmpty()) return emptyMap() return try { serializedSetting .split(SEPARATOR_REGEX) .hasEvenSize() .chunked(2) .mapNotNull(::parsePostureSettingPair) .toMap() } catch (e: Exception) { Log.w( TAG, "Invalid format in serializedSetting=$serializedSetting: ${e.message}" ) return emptyMap() } } Loading @@ -158,30 +180,30 @@ class DeviceStateAutoRotateSettingManagerImpl( return this } private fun parsePostureSettingPair(settingPair: List<String>): Pair<Int, Int>? { private fun parsePostureSettingPair(settingPair: List<String>): Pair<Int, Int> { return settingPair.let { (keyStr, valueStr) -> val key = keyStr.toIntOrNull() val value = valueStr.toIntOrNull() if (key != null && value != null && value in 0..2) { key to value } else { Log.w(TAG, "Invalid key or value in pair: $keyStr, $valueStr") null // Invalid pair, skip it throw IllegalStateException("Invalid key or value in pair: $keyStr, $valueStr") } } } private fun extractSettingForDevicePosture( devicePosture: Int, serializedSetting: String ): Int = convertSerializedSettingToMap(serializedSetting)[devicePosture] ?: DEVICE_STATE_ROTATION_LOCK_IGNORED deviceStateAutoRotateSetting: SparseIntArray? ): Int? = deviceStateAutoRotateSetting?.let { it[devicePosture] ?: DEVICE_STATE_ROTATION_LOCK_IGNORED } private fun String.parsePostureEntry(): Triple<Int, Int, Int?>? { val values = split(SEPARATOR_REGEX) if (values.size !in 2..3) { // It should contain 2 or 3 values. Log.w(TAG, "Invalid number of values in entry: '$this'") Log.wtf(TAG, "Invalid number of values in entry: '$this'") return null } return try { Loading @@ -190,13 +212,30 @@ class DeviceStateAutoRotateSettingManagerImpl( val fallbackPosture = if (values.size == 3) values[2].toIntOrNull() else null Triple(posture, rotationLockSetting, fallbackPosture) } catch (e: NumberFormatException) { Log.w(TAG, "Invalid number format in '$this': ${e.message}") Log.wtf(TAG, "Invalid number format in '$this': ${e.message}") null } } private fun List<Pair<Int, Int>>.toSparseIntArray(): SparseIntArray { val sparseArray = SparseIntArray() forEach { (key, value) -> sparseArray.put(key, value) } return sparseArray } private fun SparseIntArray.allSettingValuesLocked(): Boolean { for (i in 0 until size()) { if (valueAt(i) != DEVICE_STATE_ROTATION_LOCK_LOCKED) { return false } } return true } companion object { private const val TAG = "DeviceStateAutoRotate" private const val TAG = "DSAutoRotateMngr" private const val SEPARATOR_REGEX = ":" } }
packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java +16 −5 Original line number Diff line number Diff line Loading @@ -42,8 +42,10 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** Loading @@ -56,8 +58,6 @@ public final class DeviceStateRotationLockSettingsManager implements private static final String TAG = "DSRotLockSettingsMngr"; private static final String SEPARATOR_REGEX = ":"; private static DeviceStateRotationLockSettingsManager sSingleton; private final Handler mMainHandler = new Handler(Looper.getMainLooper()); private final Set<DeviceStateAutoRotateSettingListener> mListeners = new HashSet<>(); private final SecureSettings mSecureSettings; Loading Loading @@ -140,6 +140,11 @@ public final class DeviceStateRotationLockSettingsManager implements persistSettings(); } @Override public SparseIntArray getRotationLockSetting() { return mPostureRotationLockSettings.clone(); } /** * Returns the {@link Settings.Secure.DeviceStateRotationLockSetting} for the given device * state. Loading @@ -152,7 +157,7 @@ public final class DeviceStateRotationLockSettingsManager implements */ @Settings.Secure.DeviceStateRotationLockSetting @Override public int getRotationLockSetting(int deviceState) { public Integer getRotationLockSetting(int deviceState) { int devicePosture = mPosturesHelper.deviceStateToPosture(deviceState); int rotationLockSetting = mPostureRotationLockSettings.get( devicePosture, /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED); Loading @@ -176,7 +181,7 @@ public final class DeviceStateRotationLockSettingsManager implements /** Returns true if the rotation is locked for the current device state */ @Override public boolean isRotationLocked(int deviceState) { public Boolean isRotationLocked(int deviceState) { return getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED; } Loading @@ -185,7 +190,7 @@ public final class DeviceStateRotationLockSettingsManager implements * DEVICE_STATE_ROTATION_LOCK_UNLOCKED}. */ @Override public boolean isRotationLockedForAllStates() { public Boolean isRotationLockedForAllStates() { for (int i = 0; i < mPostureRotationLockSettings.size(); i++) { if (mPostureRotationLockSettings.valueAt(i) == DEVICE_STATE_ROTATION_LOCK_UNLOCKED) { Loading @@ -203,6 +208,12 @@ public final class DeviceStateRotationLockSettingsManager implements return new ArrayList<>(mSettableDeviceStates); } @NonNull @Override public SparseIntArray getDefaultRotationLockSetting() { return mPostureDefaultRotationLockSettings.clone(); } private void initializeInMemoryMap() { String serializedSetting = getPersistedSettingValue(); if (TextUtils.isEmpty(serializedSetting)) { Loading
packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt +89 −20 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.never Loading Loading @@ -99,16 +100,7 @@ class DeviceStateAutoRotateSettingManagerImplTest { } whenever(mockContext.getSystemService(DeviceStateManager::class.java)) .thenReturn(mockDeviceStateManager) whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_UNFOLDED)) .thenReturn(DEVICE_STATE_ROTATION_KEY_UNFOLDED) whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_FOLDED)) .thenReturn(DEVICE_STATE_ROTATION_KEY_FOLDED) whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_HALF_FOLDED)) .thenReturn(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED) whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_INVALID)) .thenReturn(DEVICE_STATE_ROTATION_LOCK_IGNORED) whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_REAR_DISPLAY)) .thenReturn(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY) setUpMockPostureHelper() settingManager = DeviceStateAutoRotateSettingManagerImpl( Loading Loading @@ -173,10 +165,10 @@ class DeviceStateAutoRotateSettingManagerImplTest { } @Test fun getAutoRotateSetting_forInvalidPostureWithNoFallback_returnsIgnored() { fun getAutoRotateSetting_forInvalidPostureWithNoFallback_returnsNull() { val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_INVALID) assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED) assertThat(autoRotateSetting).isNull() } @Test Loading @@ -192,21 +184,21 @@ class DeviceStateAutoRotateSettingManagerImplTest { } @Test fun getAutoRotateSetting_invalidFormat_returnsIgnored() { fun getAutoRotateSetting_invalidFormat_returnsNull() { persistSettings("invalid_format") val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED) assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED) assertThat(autoRotateSetting).isNull() } @Test fun getAutoRotateSetting_invalidNumberFormat_returnsIgnored() { fun getAutoRotateSetting_invalidNumberFormat_returnsNull() { persistSettings("$DEVICE_STATE_ROTATION_KEY_FOLDED:4") val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED) assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED) assertThat(autoRotateSetting).isNull() } @Test Loading Loading @@ -274,11 +266,66 @@ class DeviceStateAutoRotateSettingManagerImplTest { assertThat(settableDeviceStates) .containsExactly( SettableDeviceState(DEVICE_STATE_ROTATION_KEY_UNFOLDED, isSettable = true), SettableDeviceState(DEVICE_STATE_ROTATION_KEY_FOLDED, isSettable = true), SettableDeviceState(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED, isSettable = false), SettableDeviceState(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY, isSettable = false), SettableDeviceState(DEVICE_STATE_UNFOLDED, isSettable = true), SettableDeviceState(DEVICE_STATE_FOLDED, isSettable = true), SettableDeviceState(DEVICE_STATE_HALF_FOLDED, isSettable = false), SettableDeviceState(DEVICE_STATE_REAR_DISPLAY, isSettable = false), ) } @Test fun getRotationLockSettingMap_multipleSettings_returnsCorrectMap() { persistSettings( "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED:" + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED" ) val expectedPairs = mapOf( DEVICE_STATE_ROTATION_KEY_UNFOLDED to DEVICE_STATE_ROTATION_LOCK_LOCKED, DEVICE_STATE_ROTATION_KEY_FOLDED to DEVICE_STATE_ROTATION_LOCK_UNLOCKED ) val deviceStateAutoRotateSetting = settingManager.getRotationLockSetting() assertThat(deviceStateAutoRotateSetting).isNotNull() // Check if all expected pairs are present expectedPairs.forEach { (key, value) -> assertThat(deviceStateAutoRotateSetting?.indexOfKey(key)).isGreaterThan(-1) assertThat(value).isEqualTo(deviceStateAutoRotateSetting?.get(key)) } // Check if no unexpected pairs are present assertThat(expectedPairs.size).isEqualTo(deviceStateAutoRotateSetting?.size()) } @Test fun getRotationLockSettingMap_invalidFormat_returnsNull() { persistSettings( "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED" + ":invalid_format" ) val deviceStateAutoRotateSetting = settingManager.getRotationLockSetting() assertThat(deviceStateAutoRotateSetting).isNull() } @Test fun getDefaultRotationLockSetting_returnsDefaultsFromConfig() { val expectedPairs = mapOf( DEVICE_STATE_ROTATION_KEY_HALF_FOLDED to DEVICE_STATE_ROTATION_LOCK_IGNORED, DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY to DEVICE_STATE_ROTATION_LOCK_IGNORED, DEVICE_STATE_ROTATION_KEY_UNFOLDED to DEVICE_STATE_ROTATION_LOCK_LOCKED, DEVICE_STATE_ROTATION_KEY_FOLDED to DEVICE_STATE_ROTATION_LOCK_LOCKED ) val defaultDeviceStateAutoRotateSetting = settingManager.getDefaultRotationLockSetting() // Check if all expected pairs are present expectedPairs.forEach { (key, value) -> assertThat(defaultDeviceStateAutoRotateSetting.indexOfKey(key)).isGreaterThan(-1) assertThat(value).isEqualTo(defaultDeviceStateAutoRotateSetting.get(key)) } // Check if no unexpected pairs are present assertThat(expectedPairs.size).isEqualTo(defaultDeviceStateAutoRotateSetting.size()) } private fun persistSettings(devicePosture: Int, autoRotateSetting: Int) { Loading @@ -291,6 +338,28 @@ class DeviceStateAutoRotateSettingManagerImplTest { ) } private fun setUpMockPostureHelper() { whenever(mockPosturesHelper.deviceStateToPosture(eq(DEVICE_STATE_UNFOLDED))) .thenReturn(DEVICE_STATE_ROTATION_KEY_UNFOLDED) whenever(mockPosturesHelper.deviceStateToPosture(eq(DEVICE_STATE_FOLDED))) .thenReturn(DEVICE_STATE_ROTATION_KEY_FOLDED) whenever(mockPosturesHelper.deviceStateToPosture(eq(DEVICE_STATE_HALF_FOLDED))) .thenReturn(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED) whenever(mockPosturesHelper.deviceStateToPosture(eq(DEVICE_STATE_INVALID))) .thenReturn(DEVICE_STATE_ROTATION_LOCK_IGNORED) whenever(mockPosturesHelper.deviceStateToPosture(eq(DEVICE_STATE_REAR_DISPLAY))) .thenReturn(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY) whenever(mockPosturesHelper.postureToDeviceState(eq(DEVICE_STATE_ROTATION_KEY_UNFOLDED))) .thenReturn(DEVICE_STATE_UNFOLDED) whenever(mockPosturesHelper.postureToDeviceState(eq(DEVICE_STATE_ROTATION_KEY_FOLDED))) .thenReturn(DEVICE_STATE_FOLDED) whenever(mockPosturesHelper.postureToDeviceState(eq(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED))) .thenReturn(DEVICE_STATE_HALF_FOLDED) whenever(mockPosturesHelper.postureToDeviceState(eq(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY))) .thenReturn(DEVICE_STATE_REAR_DISPLAY) } private companion object { const val DEVICE_STATE_FOLDED = 0 const val DEVICE_STATE_HALF_FOLDED = 1 Loading