diff options
author | 2025-03-07 07:35:08 -0800 | |
---|---|---|
committer | 2025-03-07 07:35:08 -0800 | |
commit | 4edcbc29adb4f7a8018bc61f691703041c31ba73 (patch) | |
tree | f29ce20f0f3c6a9bfdff88c9aa85e50d5de24c92 | |
parent | 8220a96d49b3e9baf4404f8e5041e1109a71d2a2 (diff) |
Create new device state auto rotate setting manager
As part of auto-rotate setting refactor, create a new manager to be used between sysui and settings.
This manager will replace the current DeviceStateRotationLockManager.
Next CL: Integrate this manager to be used when auto-rotate refactor flag is ON.
For more info:go/auto-rotate-refactor
Bug: 394303723
Flag: com.android.window.flags.enable_device_state_auto_rotate_setting_refactor
Test: atest DeviceStateAutoRotateSettingManagerImplTest
Change-Id: I5cd81df5ea602adc3942d869a1294e5ba232d770
8 files changed, 608 insertions, 64 deletions
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt new file mode 100644 index 000000000000..fdde3d3f5669 --- /dev/null +++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt @@ -0,0 +1,69 @@ +/* + * 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 com.android.settingslib.devicestate + +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK + +/** + * Interface for managing [DEVICE_STATE_ROTATION_LOCK] setting. + * + * It provides methods to register/unregister listeners for setting changes, update the setting for + * specific device states, retrieve the setting value, and check if rotation is locked for specific + * or all device states. + */ +interface DeviceStateAutoRotateSettingManager { + // TODO: b/397928958 - Rename all terms from rotationLock to autoRotate in all apis. + + /** Listener for changes in device-state based auto rotate setting. */ + interface DeviceStateAutoRotateSettingListener { + /** Called whenever the setting has changed. */ + fun onSettingsChanged() + } + + /** Register listener for changes to [DEVICE_STATE_ROTATION_LOCK] setting. */ + fun registerListener(settingListener: DeviceStateAutoRotateSettingListener) + + /** Unregister listener for changes to [DEVICE_STATE_ROTATION_LOCK] setting. */ + fun unregisterListener(settingListener: DeviceStateAutoRotateSettingListener) + + /** + * 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 + + /** Returns true if auto-rotate setting is OFF for [deviceState]. */ + fun isRotationLocked(deviceState: Int): Boolean + + /** Returns true if the auto-rotate setting value for all device states is OFF. */ + fun isRotationLockedForAllStates(): Boolean + + /** Returns a list of device states and their respective auto rotate setting availability. */ + fun getSettableDeviceStates(): List<SettableDeviceState> +} + +/** Represents a device state and whether it has an auto-rotation setting. */ +data class SettableDeviceState( + /** Returns the device state associated with this object. */ + val deviceState: Int, + /** Returns whether there is an auto-rotation setting for this device state. */ + val isSettable: Boolean +) + + diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt new file mode 100644 index 000000000000..0b6c6e238956 --- /dev/null +++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt @@ -0,0 +1,191 @@ +/* + * 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 com.android.settingslib.devicestate + +import android.content.Context +import android.database.ContentObserver +import android.os.Handler +import android.os.UserHandle +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 +import android.util.Log +import android.util.SparseIntArray +import com.android.internal.R +import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener +import com.android.window.flags.Flags +import java.util.concurrent.Executor + +/** + * Implementation of [DeviceStateAutoRotateSettingManager]. This implementation is a part of + * refactoring, it should be used when [Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR] + * is enabled. + */ +class DeviceStateAutoRotateSettingManagerImpl( + context: Context, + backgroundExecutor: Executor, + private val secureSettings: SecureSettings, + private val mainHandler: Handler, + private val posturesHelper: PosturesHelper, +) : DeviceStateAutoRotateSettingManager { + // TODO: b/397928958 rename the fields and apis from rotationLock to autoRotate. + + private val settingListeners: MutableList<DeviceStateAutoRotateSettingListener> = + mutableListOf() + private val fallbackPostureMap = SparseIntArray() + private val settableDeviceState: MutableList<SettableDeviceState> = mutableListOf() + + private val autoRotateSettingValue: String + get() = secureSettings.getStringForUser(DEVICE_STATE_ROTATION_LOCK, UserHandle.USER_CURRENT) + + init { + loadAutoRotateDeviceStates(context) + val contentObserver = + object : ContentObserver(mainHandler) { + override fun onChange(selfChange: Boolean) = notifyListeners() + } + backgroundExecutor.execute { + secureSettings.registerContentObserver( + DEVICE_STATE_ROTATION_LOCK, false, contentObserver, UserHandle.USER_CURRENT + ) + } + } + + override fun registerListener(settingListener: DeviceStateAutoRotateSettingListener) { + settingListeners.add(settingListener) + } + + override fun unregisterListener(settingListener: DeviceStateAutoRotateSettingListener) { + if (!settingListeners.remove(settingListener)) { + Log.w(TAG, "Attempting to unregister a listener hadn't been registered") + } + } + + override fun getRotationLockSetting(deviceState: Int): Int { + val devicePosture = posturesHelper.deviceStateToPosture(deviceState) + val serializedSetting = autoRotateSettingValue + val autoRotateSetting = extractSettingForDevicePosture(devicePosture, serializedSetting) + + // If the setting is ignored for this posture, check the fallback posture. + if (autoRotateSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { + val fallbackPosture = + fallbackPostureMap.get(devicePosture, DEVICE_STATE_ROTATION_LOCK_IGNORED) + return extractSettingForDevicePosture(fallbackPosture, serializedSetting) + } + + return autoRotateSetting + } + + override fun isRotationLocked(deviceState: Int) = + getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED + + override fun isRotationLockedForAllStates(): Boolean = + convertSerializedSettingToMap(autoRotateSettingValue).all { (_, value) -> + value == DEVICE_STATE_ROTATION_LOCK_LOCKED + } + + override fun getSettableDeviceStates(): List<SettableDeviceState> = settableDeviceState + + override fun updateSetting(deviceState: Int, autoRotate: Boolean) { + // TODO: b/350946537 - Create IPC to update the setting, and call it here. + throw UnsupportedOperationException("API updateSetting is not implemented yet") + } + + private fun notifyListeners() = + settingListeners.forEach { listener -> listener.onSettingsChanged() } + + 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) + } + settableDeviceState.add( + SettableDeviceState(posture, autoRotate != DEVICE_STATE_ROTATION_LOCK_IGNORED) + ) + } + } + } + + 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() + } + } + + private fun List<String>.hasEvenSize(): List<String> { + if (this.size % 2 != 0) { + throw IllegalStateException("Odd number of elements in the list") + } + return this + } + + 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 + } + } + } + + private fun extractSettingForDevicePosture( + devicePosture: Int, + serializedSetting: String + ): Int = + convertSerializedSettingToMap(serializedSetting)[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'") + return null + } + return try { + val posture = values[0].toInt() + val rotationLockSetting = values[1].toInt() + 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}") + null + } + } + + companion object { + private const val TAG = "DeviceStateAutoRotate" + private const val SEPARATOR_REGEX = ":" + } +} diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingUtils.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingUtils.kt new file mode 100644 index 000000000000..4d1d29242832 --- /dev/null +++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingUtils.kt @@ -0,0 +1,31 @@ +/* + * 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. + */ +@file:JvmName("DeviceStateAutoRotateSettingUtils") + +package com.android.settingslib.devicestate + +import android.content.Context +import com.android.internal.R + +/** Returns true if device-state based rotation lock settings are enabled. */ +object DeviceStateAutoRotateSettingUtils { + @JvmStatic + fun isDeviceStateRotationLockEnabled(context: Context) = + context.resources + .getStringArray(R.array.config_perDeviceStateRotationLockDefaults) + .isNotEmpty() +} + diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java index 635f6905e4f0..deeba574f2ad 100644 --- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java +++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java @@ -20,6 +20,8 @@ import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORE import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED; import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED; +import static com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener; + import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; @@ -43,7 +45,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.Set; /** @@ -58,7 +59,7 @@ public final class DeviceStateRotationLockSettingsManager { private static DeviceStateRotationLockSettingsManager sSingleton; private final Handler mMainHandler = new Handler(Looper.getMainLooper()); - private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>(); + private final Set<DeviceStateAutoRotateSettingListener> mListeners = new HashSet<>(); private final SecureSettings mSecureSettings; private final PosturesHelper mPosturesHelper; private String[] mPostureRotationLockDefaults; @@ -127,20 +128,20 @@ public final class DeviceStateRotationLockSettingsManager { } /** - * Registers a {@link DeviceStateRotationLockSettingsListener} to be notified when the settings + * Registers a {@link DeviceStateAutoRotateSettingListener} to be notified when the settings * change. Can be called multiple times with different listeners. */ - public void registerListener(DeviceStateRotationLockSettingsListener runnable) { + public void registerListener(DeviceStateAutoRotateSettingListener runnable) { mListeners.add(runnable); } /** - * Unregisters a {@link DeviceStateRotationLockSettingsListener}. No-op if the given instance + * Unregisters a {@link DeviceStateAutoRotateSettingListener}. No-op if the given instance * was never registered. */ public void unregisterListener( - DeviceStateRotationLockSettingsListener deviceStateRotationLockSettingsListener) { - if (!mListeners.remove(deviceStateRotationLockSettingsListener)) { + DeviceStateAutoRotateSettingListener deviceStateAutoRotateSettingListener) { + if (!mListeners.remove(deviceStateAutoRotateSettingListener)) { Log.w(TAG, "Attempting to unregister a listener hadn't been registered"); } } @@ -379,56 +380,8 @@ public final class DeviceStateRotationLockSettingsManager { } private void notifyListeners() { - for (DeviceStateRotationLockSettingsListener r : mListeners) { + for (DeviceStateAutoRotateSettingListener r : mListeners) { r.onSettingsChanged(); } } - - /** Listener for changes in device-state based rotation lock settings */ - public interface DeviceStateRotationLockSettingsListener { - /** Called whenever the settings have changed. */ - void onSettingsChanged(); - } - - /** Represents a device state and whether it has an auto-rotation setting. */ - public static class SettableDeviceState { - private final int mDeviceState; - private final boolean mIsSettable; - - SettableDeviceState(int deviceState, boolean isSettable) { - mDeviceState = deviceState; - mIsSettable = isSettable; - } - - /** Returns the device state associated with this object. */ - public int getDeviceState() { - return mDeviceState; - } - - /** Returns whether there is an auto-rotation setting for this device state. */ - public boolean isSettable() { - return mIsSettable; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SettableDeviceState)) return false; - SettableDeviceState that = (SettableDeviceState) o; - return mDeviceState == that.mDeviceState && mIsSettable == that.mIsSettable; - } - - @Override - public int hashCode() { - return Objects.hash(mDeviceState, mIsSettable); - } - - @Override - public String toString() { - return "SettableDeviceState{" - + "mDeviceState=" + mDeviceState - + ", mIsSettable=" + mIsSettable - + '}'; - } - } } diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java index 10528739b2b0..ea40e148aed6 100644 --- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java +++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java @@ -19,7 +19,7 @@ package com.android.settingslib.devicestate; import android.database.ContentObserver; /** Minimal wrapper interface around {@link android.provider.Settings.Secure} for easier testing. */ -interface SecureSettings { +public interface SecureSettings { void putStringForUser(String name, String value, int userHandle); diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt new file mode 100644 index 000000000000..78dba57028ba --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt @@ -0,0 +1,300 @@ +/* + * 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 com.android.settingslib.devicestate + +import android.content.ContentResolver +import android.content.Context +import android.content.res.Resources +import android.hardware.devicestate.DeviceStateManager +import android.os.Handler +import android.os.UserHandle +import android.provider.Settings +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_FOLDED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_HALF_FOLDED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_UNFOLDED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import java.util.concurrent.Executor +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceStateAutoRotateSettingManagerImplTest { + @get:Rule + val rule = MockitoJUnit.rule() + + private val fakeSecureSettings = FakeSecureSettings() + private val executor: Executor = Executor { it.run() } + private val configPerDeviceStateRotationLockDefaults = arrayOf( + "$DEVICE_STATE_ROTATION_KEY_HALF_FOLDED:" + + "$DEVICE_STATE_ROTATION_LOCK_IGNORED:" + + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED", + "$DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY:" + + "$DEVICE_STATE_ROTATION_LOCK_IGNORED:" + + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED", + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED", + "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED", + ) + + @Mock + private lateinit var mockContext: Context + + @Mock + private lateinit var mockContentResolver: ContentResolver + + @Mock + private lateinit var mockPosturesHelper: PosturesHelper + + @Mock + private lateinit var mockHandler: Handler + + @Mock + private lateinit var mockDeviceStateManager: DeviceStateManager + + @Mock + private lateinit var mockResources: Resources + private lateinit var settingManager: DeviceStateAutoRotateSettingManagerImpl + + @Before + fun setUp() { + whenever(mockContext.contentResolver).thenReturn(mockContentResolver) + whenever(mockContext.resources).thenReturn(mockResources) + whenever(mockResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults)) + .thenReturn(configPerDeviceStateRotationLockDefaults) + whenever(mockHandler.post(any(Runnable::class.java))).thenAnswer { invocation -> + val runnable = invocation.arguments[0] as Runnable + runnable.run() + null + } + 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) + + settingManager = + DeviceStateAutoRotateSettingManagerImpl( + mockContext, + executor, + fakeSecureSettings, + mockHandler, + mockPosturesHelper, + ) + } + + @Test + fun registerListener_onSettingsChanged_listenerNotified() { + val listener = mock(DeviceStateAutoRotateSettingListener::class.java) + settingManager.registerListener(listener) + + persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED) + + verify(listener).onSettingsChanged() + } + + @Test + fun registerMultipleListeners_onSettingsChanged_allListenersNotified() { + val listener1 = mock(DeviceStateAutoRotateSettingListener::class.java) + val listener2 = mock(DeviceStateAutoRotateSettingListener::class.java) + settingManager.registerListener(listener1) + settingManager.registerListener(listener2) + + persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED) + + verify(listener1).onSettingsChanged() + verify(listener2).onSettingsChanged() + } + + @Test + fun unregisterListener_onSettingsChanged_listenerNotNotified() { + val listener = mock(DeviceStateAutoRotateSettingListener::class.java) + settingManager.registerListener(listener) + settingManager.unregisterListener(listener) + + persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED) + + verify(listener, never()).onSettingsChanged() + } + + @Test + fun getAutoRotateSetting_offForUnfolded_returnsOff() { + persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED) + + val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_UNFOLDED) + + assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_LOCKED) + } + + @Test + fun getAutoRotateSetting_onForFolded_returnsOn() { + persistSettings(DEVICE_STATE_ROTATION_KEY_FOLDED, DEVICE_STATE_ROTATION_LOCK_UNLOCKED) + + val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED) + + assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_UNLOCKED) + } + + @Test + fun getAutoRotateSetting_forInvalidPostureWithNoFallback_returnsIgnored() { + val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_INVALID) + + assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED) + } + + @Test + fun getAutoRotateSetting_forInvalidPosture_returnsSettingForFallbackPosture() { + persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_UNLOCKED) + persistSettings(DEVICE_STATE_ROTATION_KEY_FOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED) + + val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_HALF_FOLDED) + + assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_UNLOCKED) + } + + @Test + fun getAutoRotateSetting_invalidFormat_returnsIgnored() { + persistSettings("invalid_format") + + val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED) + + assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED) + } + + @Test + fun getAutoRotateSetting_invalidNumberFormat_returnsIgnored() { + persistSettings("$DEVICE_STATE_ROTATION_KEY_FOLDED:4") + + val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED) + + assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED) + } + + @Test + fun getAutoRotateSetting_multipleSettings_returnsCorrectSetting() { + persistSettings( + "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED:" + + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED" + ) + + val foldedSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED) + val unfoldedSetting = settingManager.getRotationLockSetting(DEVICE_STATE_UNFOLDED) + + assertThat(foldedSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_LOCKED) + assertThat(unfoldedSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_UNLOCKED) + } + + @Test + fun isAutoRotateOff_offForUnfolded_returnsTrue() { + persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED) + + val isAutoRotateOff = settingManager.isRotationLocked(DEVICE_STATE_UNFOLDED) + + assertThat(isAutoRotateOff).isTrue() + } + + @Test + fun isRotationLockedForAllStates_allStatesLocked_returnsTrue() { + persistSettings( + "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED:" + + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED" + ) + + val isRotationLockedForAllStates = settingManager.isRotationLockedForAllStates() + + assertThat(isRotationLockedForAllStates).isTrue() + } + + @Test + fun isRotationLockedForAllStates_someStatesLocked_returnsFalse() { + persistSettings( + "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED:" + + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED" + ) + + val isRotationLockedForAllStates = settingManager.isRotationLockedForAllStates() + + assertThat(isRotationLockedForAllStates).isFalse() + } + + @Test + fun isRotationLockedForAllStates_noStatesLocked_returnsFalse() { + persistSettings( + "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED:" + + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED" + ) + + val isRotationLockedForAllStates = settingManager.isRotationLockedForAllStates() + + assertThat(isRotationLockedForAllStates).isFalse() + } + + @Test + fun getSettableDeviceStates_returnsExpectedValuesInOriginalOrder() { + val settableDeviceStates = settingManager.getSettableDeviceStates() + + 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_ROTATION_LOCK_IGNORED, isSettable = false), + ) + } + + private fun persistSettings(devicePosture: Int, autoRotateSetting: Int) { + persistSettings("$devicePosture:$autoRotateSetting") + } + + private fun persistSettings(value: String) { + fakeSecureSettings.putStringForUser( + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, value, UserHandle.USER_CURRENT + ) + } + + private companion object { + const val DEVICE_STATE_FOLDED = 0 + const val DEVICE_STATE_HALF_FOLDED = 1 + const val DEVICE_STATE_UNFOLDED = 2 + const val DEVICE_STATE_REAR_DISPLAY = 3 + const val DEVICE_STATE_INVALID = 4 + } +} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java index 9f9aaf5ff83a..baebaf7dfef0 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java @@ -40,7 +40,6 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; -import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager.SettableDeviceState; import com.google.common.truth.Expect; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java index b13e01be40f7..fa022b4768fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java @@ -27,6 +27,7 @@ import android.util.IndentingPrintWriter; import androidx.annotation.NonNull; +import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager; import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; import com.android.systemui.Dumpable; import com.android.systemui.dagger.qualifiers.Main; @@ -56,8 +57,8 @@ public final class DeviceStateRotationLockSettingController private int mDeviceState = -1; @Nullable private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; - private DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener - mDeviceStateRotationLockSettingsListener; + private DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener + mDeviceStateAutoRotateSettingListener; @Inject public DeviceStateRotationLockSettingController( @@ -83,17 +84,17 @@ public final class DeviceStateRotationLockSettingController // is no user action. mDeviceStateCallback = this::updateDeviceState; mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback); - mDeviceStateRotationLockSettingsListener = () -> + mDeviceStateAutoRotateSettingListener = () -> readPersistedSetting("deviceStateRotationLockChange", mDeviceState); mDeviceStateRotationLockSettingsManager.registerListener( - mDeviceStateRotationLockSettingsListener); + mDeviceStateAutoRotateSettingListener); } else { if (mDeviceStateCallback != null) { mDeviceStateManager.unregisterCallback(mDeviceStateCallback); } - if (mDeviceStateRotationLockSettingsListener != null) { + if (mDeviceStateAutoRotateSettingListener != null) { mDeviceStateRotationLockSettingsManager.unregisterListener( - mDeviceStateRotationLockSettingsListener); + mDeviceStateAutoRotateSettingListener); } } } |