1 /* <lambda>null2 * Copyright (C) 2025 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.settingslib.devicestate 17 18 import android.content.Context 19 import android.database.ContentObserver 20 import android.os.Handler 21 import android.os.UserHandle 22 import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK 23 import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED 24 import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED 25 import android.util.IndentingPrintWriter 26 import android.util.Log 27 import android.util.SparseIntArray 28 import com.android.internal.R 29 import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener 30 import com.android.window.flags.Flags 31 import java.io.PrintWriter 32 import java.util.concurrent.Executor 33 34 /** 35 * Implementation of [DeviceStateAutoRotateSettingManager]. This implementation is a part of 36 * refactoring, it should be used when [Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR] 37 * is enabled. 38 */ 39 class DeviceStateAutoRotateSettingManagerImpl( 40 context: Context, 41 backgroundExecutor: Executor, 42 private val secureSettings: SecureSettings, 43 private val mainHandler: Handler, 44 private val posturesHelper: PosturesHelper, 45 ) : DeviceStateAutoRotateSettingManager { 46 // TODO: b/397928958 rename the fields and apis from rotationLock to autoRotate. 47 48 private val settingListeners: MutableList<DeviceStateAutoRotateSettingListener> = 49 mutableListOf() 50 private val fallbackPostureMap = SparseIntArray() 51 private val settableDeviceState: MutableList<SettableDeviceState> = mutableListOf() 52 53 private val autoRotateSettingValue: String 54 get() = secureSettings.getStringForUser(DEVICE_STATE_ROTATION_LOCK, UserHandle.USER_CURRENT) 55 56 init { 57 loadAutoRotateDeviceStates(context) 58 val contentObserver = 59 object : ContentObserver(mainHandler) { 60 override fun onChange(selfChange: Boolean) = notifyListeners() 61 } 62 backgroundExecutor.execute { 63 secureSettings.registerContentObserver( 64 DEVICE_STATE_ROTATION_LOCK, false, contentObserver, UserHandle.USER_CURRENT 65 ) 66 } 67 } 68 69 override fun registerListener(settingListener: DeviceStateAutoRotateSettingListener) { 70 settingListeners.add(settingListener) 71 } 72 73 override fun unregisterListener(settingListener: DeviceStateAutoRotateSettingListener) { 74 if (!settingListeners.remove(settingListener)) { 75 Log.w(TAG, "Attempting to unregister a listener hadn't been registered") 76 } 77 } 78 79 override fun getRotationLockSetting(deviceState: Int): Int { 80 val devicePosture = posturesHelper.deviceStateToPosture(deviceState) 81 val serializedSetting = autoRotateSettingValue 82 val autoRotateSetting = extractSettingForDevicePosture(devicePosture, serializedSetting) 83 84 // If the setting is ignored for this posture, check the fallback posture. 85 if (autoRotateSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { 86 val fallbackPosture = 87 fallbackPostureMap.get(devicePosture, DEVICE_STATE_ROTATION_LOCK_IGNORED) 88 return extractSettingForDevicePosture(fallbackPosture, serializedSetting) 89 } 90 91 return autoRotateSetting 92 } 93 94 override fun isRotationLocked(deviceState: Int) = 95 getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED 96 97 override fun isRotationLockedForAllStates(): Boolean = 98 convertSerializedSettingToMap(autoRotateSettingValue).all { (_, value) -> 99 value == DEVICE_STATE_ROTATION_LOCK_LOCKED 100 } 101 102 override fun getSettableDeviceStates(): List<SettableDeviceState> = settableDeviceState 103 104 override fun updateSetting(deviceState: Int, autoRotate: Boolean) { 105 // TODO: b/350946537 - Create IPC to update the setting, and call it here. 106 throw UnsupportedOperationException("API updateSetting is not implemented yet") 107 } 108 109 override fun dump(writer: PrintWriter, args: Array<out String>?) { 110 val indentingWriter = IndentingPrintWriter(writer) 111 indentingWriter.println("DeviceStateAutoRotateSettingManagerImpl") 112 indentingWriter.increaseIndent() 113 indentingWriter.println("fallbackPostureMap: $fallbackPostureMap") 114 indentingWriter.println("settableDeviceState: $settableDeviceState") 115 indentingWriter.decreaseIndent() 116 } 117 118 private fun notifyListeners() = 119 settingListeners.forEach { listener -> listener.onSettingsChanged() } 120 121 private fun loadAutoRotateDeviceStates(context: Context) { 122 val perDeviceStateAutoRotateDefaults = 123 context.resources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults) 124 for (entry in perDeviceStateAutoRotateDefaults) { 125 entry.parsePostureEntry()?.let { (posture, autoRotate, fallbackPosture) -> 126 if (autoRotate == DEVICE_STATE_ROTATION_LOCK_IGNORED && fallbackPosture != null) { 127 fallbackPostureMap.put(posture, fallbackPosture) 128 } 129 settableDeviceState.add( 130 SettableDeviceState(posture, autoRotate != DEVICE_STATE_ROTATION_LOCK_IGNORED) 131 ) 132 } 133 } 134 } 135 136 private fun convertSerializedSettingToMap(serializedSetting: String): Map<Int, Int> { 137 if (serializedSetting.isEmpty()) return emptyMap() 138 return try { 139 serializedSetting 140 .split(SEPARATOR_REGEX) 141 .hasEvenSize() 142 .chunked(2) 143 .mapNotNull(::parsePostureSettingPair) 144 .toMap() 145 } catch (e: Exception) { 146 Log.w( 147 TAG, 148 "Invalid format in serializedSetting=$serializedSetting: ${e.message}" 149 ) 150 return emptyMap() 151 } 152 } 153 154 private fun List<String>.hasEvenSize(): List<String> { 155 if (this.size % 2 != 0) { 156 throw IllegalStateException("Odd number of elements in the list") 157 } 158 return this 159 } 160 161 private fun parsePostureSettingPair(settingPair: List<String>): Pair<Int, Int>? { 162 return settingPair.let { (keyStr, valueStr) -> 163 val key = keyStr.toIntOrNull() 164 val value = valueStr.toIntOrNull() 165 if (key != null && value != null && value in 0..2) { 166 key to value 167 } else { 168 Log.w(TAG, "Invalid key or value in pair: $keyStr, $valueStr") 169 null // Invalid pair, skip it 170 } 171 } 172 } 173 174 private fun extractSettingForDevicePosture( 175 devicePosture: Int, 176 serializedSetting: String 177 ): Int = 178 convertSerializedSettingToMap(serializedSetting)[devicePosture] 179 ?: DEVICE_STATE_ROTATION_LOCK_IGNORED 180 181 private fun String.parsePostureEntry(): Triple<Int, Int, Int?>? { 182 val values = split(SEPARATOR_REGEX) 183 if (values.size !in 2..3) { // It should contain 2 or 3 values. 184 Log.w(TAG, "Invalid number of values in entry: '$this'") 185 return null 186 } 187 return try { 188 val posture = values[0].toInt() 189 val rotationLockSetting = values[1].toInt() 190 val fallbackPosture = if (values.size == 3) values[2].toIntOrNull() else null 191 Triple(posture, rotationLockSetting, fallbackPosture) 192 } catch (e: NumberFormatException) { 193 Log.w(TAG, "Invalid number format in '$this': ${e.message}") 194 null 195 } 196 } 197 198 companion object { 199 private const val TAG = "DeviceStateAutoRotate" 200 private const val SEPARATOR_REGEX = ":" 201 } 202 } 203