| /* | |
| * 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.IndentingPrintWriter | |
| 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.io.PrintWriter | |
| 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") | |
| } | |
| override fun dump(writer: PrintWriter, args: Array<out String>?) { | |
| val indentingWriter = IndentingPrintWriter(writer) | |
| indentingWriter.println("DeviceStateAutoRotateSettingManagerImpl") | |
| indentingWriter.increaseIndent() | |
| indentingWriter.println("fallbackPostureMap: $fallbackPostureMap") | |
| indentingWriter.println("settableDeviceState: $settableDeviceState") | |
| indentingWriter.decreaseIndent() | |
| } | |
| 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 = ":" | |
| } | |
| } |