1 /* 2 * Copyright (C) 2023 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.systemui.util.settings 17 18 import android.annotation.SuppressLint 19 import android.annotation.UserIdInt 20 import android.annotation.WorkerThread 21 import android.content.ContentResolver 22 import android.database.ContentObserver 23 import android.net.Uri 24 import android.os.UserHandle 25 import android.provider.Settings.SettingNotFoundException 26 import com.android.app.tracing.TraceUtils.trace 27 import com.android.app.tracing.coroutines.launchTraced as launch 28 import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat 29 import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloatOrThrow 30 import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrThrow 31 import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrUseDefault 32 import kotlinx.coroutines.Job 33 34 /** 35 * Used to interact with per-user Settings.Secure and Settings.System settings (but not 36 * Settings.Global, since those do not vary per-user) 37 * 38 * This interface can be implemented to give instance method (instead of static method) versions of 39 * Settings.Secure and Settings.System. It can be injected into class constructors and then faked or 40 * mocked as needed in tests. 41 * 42 * You can ask for [SecureSettings] or [SystemSettings] to be injected as needed. 43 * 44 * This class also provides [.registerContentObserver] methods, normally found on [ContentResolver] 45 * instances, unifying setting related actions in one place. 46 */ 47 @SuppressLint("RegisterContentObserverSyncWarning") 48 public interface UserSettingsProxy : SettingsProxy { 49 public val currentUserProvider: SettingsProxy.CurrentUserIdProvider 50 51 /** Returns the user id for the associated [ContentResolver]. */ 52 public var userId: Int 53 get() = getContentResolver().userId 54 set(_) { 55 throw UnsupportedOperationException( 56 "userId cannot be set in interface, use setter from an implementation instead." 57 ) 58 } 59 60 /** 61 * Returns the actual current user handle when querying with the current user. Otherwise, 62 * returns the passed in user id. 63 */ getRealUserHandlenull64 public fun getRealUserHandle(userHandle: Int): Int { 65 return if (userHandle != UserHandle.USER_CURRENT) { 66 userHandle 67 } else currentUserProvider.getUserId() 68 } 69 70 @WorkerThread registerContentObserverSyncnull71 override fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) { 72 registerContentObserverForUserSync(uri, settingsObserver, userId) 73 } 74 registerContentObservernull75 override suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) { 76 executeOnSettingsScopeDispatcher("registerContentObserver-A") { 77 registerContentObserverForUserSync(uri, settingsObserver, userId) 78 } 79 } 80 registerContentObserverAsyncnull81 override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver): Job = 82 settingsScope.launch("registerContentObserverAsync-A") { 83 registerContentObserverForUserSync(uri, settingsObserver, userId) 84 } 85 86 /** Convenience wrapper around [ContentResolver.registerContentObserver].' */ 87 @WorkerThread registerContentObserverSyncnull88 override fun registerContentObserverSync( 89 uri: Uri, 90 notifyForDescendants: Boolean, 91 settingsObserver: ContentObserver, 92 ) { 93 registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) 94 } 95 registerContentObservernull96 override suspend fun registerContentObserver( 97 uri: Uri, 98 notifyForDescendants: Boolean, 99 settingsObserver: ContentObserver, 100 ) { 101 executeOnSettingsScopeDispatcher("registerContentObserver-B") { 102 registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) 103 } 104 } 105 106 /** 107 * Convenience wrapper around [ContentResolver.registerContentObserver].' 108 * 109 * API corresponding to [registerContentObserverForUser] for Java usage. 110 */ registerContentObserverAsyncnull111 override fun registerContentObserverAsync( 112 uri: Uri, 113 notifyForDescendants: Boolean, 114 settingsObserver: ContentObserver, 115 ): Job = 116 settingsScope.launch("registerContentObserverAsync-B") { 117 registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) 118 } 119 120 /** 121 * Convenience wrapper around [ContentResolver.registerContentObserver] 122 * 123 * Implicitly calls [getUriFor] on the passed in name. 124 */ 125 @WorkerThread registerContentObserverForUserSyncnull126 public fun registerContentObserverForUserSync( 127 name: String, 128 settingsObserver: ContentObserver, 129 userHandle: Int, 130 ) { 131 registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle) 132 } 133 134 /** 135 * Convenience wrapper around [ContentResolver.registerContentObserver].' 136 * 137 * suspend API corresponding to [registerContentObserverForUser] to ensure that 138 * [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an 139 * async block if they wish to synchronize execution. 140 */ registerContentObserverForUsernull141 public suspend fun registerContentObserverForUser( 142 name: String, 143 settingsObserver: ContentObserver, 144 userHandle: Int, 145 ) { 146 executeOnSettingsScopeDispatcher("registerContentObserverForUser-A") { 147 registerContentObserverForUserSync(name, settingsObserver, userHandle) 148 } 149 } 150 151 /** 152 * Convenience wrapper around [ContentResolver.registerContentObserver].' 153 * 154 * API corresponding to [registerContentObserverForUser] for Java usage. 155 */ registerContentObserverForUserAsyncnull156 public fun registerContentObserverForUserAsync( 157 name: String, 158 settingsObserver: ContentObserver, 159 userHandle: Int, 160 ): Job = 161 settingsScope.launch("registerContentObserverForUserAsync-A") { 162 try { 163 registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle) 164 } catch (e: SecurityException) { 165 throw SecurityException("registerContentObserverForUserAsync-A, name: $name", e) 166 } 167 } 168 169 /** Convenience wrapper around [ContentResolver.registerContentObserver] */ 170 @WorkerThread registerContentObserverForUserSyncnull171 public fun registerContentObserverForUserSync( 172 uri: Uri, 173 settingsObserver: ContentObserver, 174 userHandle: Int, 175 ) { 176 registerContentObserverForUserSync(uri, false, settingsObserver, userHandle) 177 } 178 179 /** 180 * Convenience wrapper around [ContentResolver.registerContentObserver].' 181 * 182 * suspend API corresponding to [registerContentObserverForUser] to ensure that 183 * [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an 184 * async block if they wish to synchronize execution. 185 */ registerContentObserverForUsernull186 public suspend fun registerContentObserverForUser( 187 uri: Uri, 188 settingsObserver: ContentObserver, 189 userHandle: Int, 190 ) { 191 executeOnSettingsScopeDispatcher("registerContentObserverForUser-B") { 192 registerContentObserverForUserSync(uri, settingsObserver, userHandle) 193 } 194 } 195 196 /** 197 * Convenience wrapper around [ContentResolver.registerContentObserver].' 198 * 199 * API corresponding to [registerContentObserverForUser] for Java usage. 200 */ registerContentObserverForUserAsyncnull201 public fun registerContentObserverForUserAsync( 202 uri: Uri, 203 settingsObserver: ContentObserver, 204 userHandle: Int, 205 ): Job = 206 settingsScope.launch("registerContentObserverForUserAsync-B") { 207 try { 208 registerContentObserverForUserSync(uri, settingsObserver, userHandle) 209 } catch (e: SecurityException) { 210 throw SecurityException("registerContentObserverForUserAsync-B, uri: $uri", e) 211 } 212 } 213 214 /** 215 * Convenience wrapper around [ContentResolver.registerContentObserver].' 216 * 217 * API corresponding to [registerContentObserverForUser] for Java usage. After registration is 218 * complete, the callback block is called on the <b>background thread</b> to allow for update of 219 * value. 220 */ registerContentObserverForUserAsyncnull221 public fun registerContentObserverForUserAsync( 222 uri: Uri, 223 settingsObserver: ContentObserver, 224 userHandle: Int, 225 @WorkerThread registered: Runnable, 226 ): Job = 227 settingsScope.launch("registerContentObserverForUserAsync-C") { 228 try { 229 registerContentObserverForUserSync(uri, settingsObserver, userHandle) 230 } catch (e: SecurityException) { 231 throw SecurityException("registerContentObserverForUserAsync-C, uri: $uri", e) 232 } 233 registered.run() 234 } 235 236 /** 237 * Convenience wrapper around [ContentResolver.registerContentObserver] 238 * 239 * Implicitly calls [getUriFor] on the passed in name. 240 */ 241 @WorkerThread registerContentObserverForUserSyncnull242 public fun registerContentObserverForUserSync( 243 name: String, 244 notifyForDescendants: Boolean, 245 settingsObserver: ContentObserver, 246 userHandle: Int, 247 ) { 248 registerContentObserverForUserSync( 249 getUriFor(name), 250 notifyForDescendants, 251 settingsObserver, 252 userHandle, 253 ) 254 } 255 256 /** 257 * Convenience wrapper around [ContentResolver.registerContentObserver].' 258 * 259 * suspend API corresponding to [registerContentObserverForUser] to ensure that 260 * [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an 261 * async block if they wish to synchronize execution. 262 */ registerContentObserverForUsernull263 public suspend fun registerContentObserverForUser( 264 name: String, 265 notifyForDescendants: Boolean, 266 settingsObserver: ContentObserver, 267 userHandle: Int, 268 ) { 269 executeOnSettingsScopeDispatcher("registerContentObserverForUser-C") { 270 registerContentObserverForUserSync( 271 name, 272 notifyForDescendants, 273 settingsObserver, 274 userHandle, 275 ) 276 } 277 } 278 279 /** 280 * Convenience wrapper around [ContentResolver.registerContentObserver].' 281 * 282 * API corresponding to [registerContentObserverForUser] for Java usage. 283 */ registerContentObserverForUserAsyncnull284 public fun registerContentObserverForUserAsync( 285 name: String, 286 notifyForDescendants: Boolean, 287 settingsObserver: ContentObserver, 288 userHandle: Int, 289 ) { 290 settingsScope.launch("registerContentObserverForUserAsync-D") { 291 try { 292 registerContentObserverForUserSync( 293 getUriFor(name), 294 notifyForDescendants, 295 settingsObserver, 296 userHandle, 297 ) 298 } catch (e: SecurityException) { 299 throw SecurityException("registerContentObserverForUserAsync-D, name: $name", e) 300 } 301 } 302 } 303 304 /** Convenience wrapper around [ContentResolver.registerContentObserver] */ 305 @WorkerThread registerContentObserverForUserSyncnull306 public fun registerContentObserverForUserSync( 307 uri: Uri, 308 notifyForDescendants: Boolean, 309 settingsObserver: ContentObserver, 310 userHandle: Int, 311 ) { 312 trace({ "USP#registerObserver#[$uri]" }) { 313 getContentResolver() 314 .registerContentObserver( 315 uri, 316 notifyForDescendants, 317 settingsObserver, 318 getRealUserHandle(userHandle), 319 ) 320 } 321 } 322 323 /** 324 * Convenience wrapper around [ContentResolver.registerContentObserver].' 325 * 326 * suspend API corresponding to [registerContentObserverForUser] to ensure that 327 * [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an 328 * async block if they wish to synchronize execution. 329 */ registerContentObserverForUsernull330 public suspend fun registerContentObserverForUser( 331 uri: Uri, 332 notifyForDescendants: Boolean, 333 settingsObserver: ContentObserver, 334 userHandle: Int, 335 ) { 336 executeOnSettingsScopeDispatcher("registerContentObserverForUser-D") { 337 registerContentObserverForUserSync( 338 uri, 339 notifyForDescendants, 340 settingsObserver, 341 getRealUserHandle(userHandle), 342 ) 343 } 344 } 345 346 /** 347 * Convenience wrapper around [ContentResolver.registerContentObserver].' 348 * 349 * API corresponding to [registerContentObserverForUser] for Java usage. 350 */ registerContentObserverForUserAsyncnull351 public fun registerContentObserverForUserAsync( 352 uri: Uri, 353 notifyForDescendants: Boolean, 354 settingsObserver: ContentObserver, 355 userHandle: Int, 356 ): Job = 357 settingsScope.launch("registerContentObserverForUserAsync-E") { 358 try { 359 registerContentObserverForUserSync( 360 uri, 361 notifyForDescendants, 362 settingsObserver, 363 userHandle, 364 ) 365 } catch (e: SecurityException) { 366 throw SecurityException("registerContentObserverForUserAsync-E, uri: $uri", e) 367 } 368 } 369 370 /** 371 * Look up a name in the database. 372 * 373 * @param name to look up in the table 374 * @return the corresponding value, or null if not present 375 */ getStringnull376 override fun getString(name: String): String? { 377 return getStringForUser(name, userId) 378 } 379 380 /** See [getString]. */ getStringForUsernull381 public fun getStringForUser(name: String, userHandle: Int): String? 382 383 /** 384 * Store a name/value pair into the database. Values written by this method will be overridden 385 * if a restore happens in the future. 386 * 387 * @param name to store 388 * @param value to associate with the name 389 * @return true if the value was set, false on database errors 390 */ 391 public fun putString(name: String, value: String?, overrideableByRestore: Boolean): Boolean 392 393 override fun putString(name: String, value: String?): Boolean { 394 return putStringForUser(name, value, userId) 395 } 396 397 /** Similar implementation to [putString] for the specified [userHandle]. */ putStringForUsernull398 public fun putStringForUser(name: String, value: String?, userHandle: Int): Boolean 399 400 /** Similar implementation to [putString] for the specified [userHandle]. */ 401 public fun putStringForUser( 402 name: String, 403 value: String?, 404 tag: String?, 405 makeDefault: Boolean, 406 @UserIdInt userHandle: Int, 407 overrideableByRestore: Boolean, 408 ): Boolean 409 410 override fun getInt(name: String, default: Int): Int { 411 return getIntForUser(name, default, userId) 412 } 413 414 /** Similar implementation to [getInt] for the specified [userHandle]. */ getIntForUsernull415 public fun getIntForUser(name: String, default: Int, userHandle: Int): Int { 416 val v = getStringForUser(name, userHandle) 417 return try { 418 v?.toInt() ?: default 419 } catch (e: NumberFormatException) { 420 default 421 } 422 } 423 424 @Throws(SettingNotFoundException::class) getIntnull425 override fun getInt(name: String): Int = getIntForUser(name, userId) 426 427 /** Similar implementation to [getInt] for the specified [userHandle]. */ 428 @Throws(SettingNotFoundException::class) 429 public fun getIntForUser(name: String, userHandle: Int): Int { 430 val v = getStringForUser(name, userHandle) ?: throw SettingNotFoundException(name) 431 return try { 432 v.toInt() 433 } catch (e: NumberFormatException) { 434 throw SettingNotFoundException(name) 435 } 436 } 437 putIntnull438 override fun putInt(name: String, value: Int): Boolean = putIntForUser(name, value, userId) 439 440 /** Similar implementation to [getInt] for the specified [userHandle]. */ 441 public fun putIntForUser(name: String, value: Int, userHandle: Int): Boolean = 442 putStringForUser(name, value.toString(), userHandle) 443 444 override fun getBool(name: String, def: Boolean): Boolean = getBoolForUser(name, def, userId) 445 446 /** Similar implementation to [getBool] for the specified [userHandle]. */ 447 public fun getBoolForUser(name: String, def: Boolean, userHandle: Int): Boolean = 448 getIntForUser(name, if (def) 1 else 0, userHandle) != 0 449 450 @Throws(SettingNotFoundException::class) 451 override fun getBool(name: String): Boolean = getBoolForUser(name, userId) 452 453 /** Similar implementation to [getBool] for the specified [userHandle]. */ 454 @Throws(SettingNotFoundException::class) 455 public fun getBoolForUser(name: String, userHandle: Int): Boolean { 456 return getIntForUser(name, userHandle) != 0 457 } 458 putBoolnull459 override fun putBool(name: String, value: Boolean): Boolean { 460 return putBoolForUser(name, value, userId) 461 } 462 463 /** Similar implementation to [putBool] for the specified [userHandle]. */ putBoolForUsernull464 public fun putBoolForUser(name: String, value: Boolean, userHandle: Int): Boolean = 465 putIntForUser(name, if (value) 1 else 0, userHandle) 466 467 /** Similar implementation to [getLong] for the specified [userHandle]. */ 468 public fun getLongForUser(name: String, def: Long, userHandle: Int): Long { 469 val valString = getStringForUser(name, userHandle) 470 return parseLongOrUseDefault(valString, def) 471 } 472 473 /** Similar implementation to [getLong] for the specified [userHandle]. */ 474 @Throws(SettingNotFoundException::class) getLongForUsernull475 public fun getLongForUser(name: String, userHandle: Int): Long { 476 val valString = getStringForUser(name, userHandle) 477 return parseLongOrThrow(name, valString) 478 } 479 480 /** Similar implementation to [putLong] for the specified [userHandle]. */ putLongForUsernull481 public fun putLongForUser(name: String, value: Long, userHandle: Int): Boolean = 482 putStringForUser(name, value.toString(), userHandle) 483 484 /** Similar implementation to [getFloat] for the specified [userHandle]. */ 485 public fun getFloatForUser(name: String, def: Float, userHandle: Int): Float { 486 val v = getStringForUser(name, userHandle) 487 return parseFloat(v, def) 488 } 489 490 /** Similar implementation to [getFloat] for the specified [userHandle]. */ 491 @Throws(SettingNotFoundException::class) getFloatForUsernull492 public fun getFloatForUser(name: String, userHandle: Int): Float { 493 val v = getStringForUser(name, userHandle) 494 return parseFloatOrThrow(name, v) 495 } 496 497 /** Similar implementation to [putFloat] for the specified [userHandle]. */ putFloatForUsernull498 public fun putFloatForUser(name: String, value: Float, userHandle: Int): Boolean = 499 putStringForUser(name, value.toString(), userHandle) 500 } 501