/* * 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 = mutableListOf() private val fallbackPostureMap = SparseIntArray() private val settableDeviceState: MutableList = 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 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?) { 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 { 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.hasEvenSize(): List { if (this.size % 2 != 0) { throw IllegalStateException("Odd number of elements in the list") } return this } private fun parsePostureSettingPair(settingPair: List): Pair? { 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? { 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 = ":" } }