1 /* <lambda>null2 * 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.launcher3 17 18 import android.content.Context 19 import android.content.Context.MODE_PRIVATE 20 import android.content.SharedPreferences 21 import androidx.annotation.VisibleForTesting 22 import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN 23 import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY 24 import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY 25 import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY 26 import com.android.launcher3.model.DeviceGridState 27 import com.android.launcher3.pm.InstallSessionHelper 28 import com.android.launcher3.provider.RestoreDbTask 29 import com.android.launcher3.provider.RestoreDbTask.FIRST_LOAD_AFTER_RESTORE_KEY 30 import com.android.launcher3.settings.SettingsActivity 31 import com.android.launcher3.states.RotationHelper 32 import com.android.launcher3.util.DisplayController 33 import com.android.launcher3.util.MainThreadInitializedObject 34 import com.android.launcher3.util.SafeCloseable 35 import com.android.launcher3.util.Themes 36 37 /** 38 * Manages Launcher [SharedPreferences] through [Item] instances. 39 * 40 * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods. 41 */ 42 abstract class LauncherPrefs : SafeCloseable { 43 44 /** Returns the value with type [T] for [item]. */ 45 abstract fun <T> get(item: ContextualItem<T>): T 46 47 /** Returns the value with type [T] for [item]. */ 48 abstract fun <T> get(item: ConstantItem<T>): T 49 50 /** Stores the values for each item in preferences. */ 51 abstract fun put(vararg itemsToValues: Pair<Item, Any>) 52 53 /** Stores the [value] with type [T] for [item] in preferences. */ 54 abstract fun <T : Any> put(item: Item, value: T) 55 56 /** Synchronous version of [put]. */ 57 abstract fun putSync(vararg itemsToValues: Pair<Item, Any>) 58 59 /** Registers [listener] for [items]. */ 60 abstract fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) 61 62 /** Unregisters [listener] for [items]. */ 63 abstract fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) 64 65 /** Returns `true` iff all [items] have a value. */ 66 abstract fun has(vararg items: Item): Boolean 67 68 /** Removes the value for each item in [items]. */ 69 abstract fun remove(vararg items: Item) 70 71 /** Synchronous version of [remove]. */ 72 abstract fun removeSync(vararg items: Item) 73 74 companion object { 75 @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs" 76 77 @JvmField 78 var INSTANCE = MainThreadInitializedObject<LauncherPrefs> { LauncherPrefsImpl(it) } 79 80 @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context) 81 82 const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY" 83 const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY" 84 const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY" 85 @JvmField 86 val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED) 87 88 @JvmField 89 val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false) 90 @JvmField 91 val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED) 92 @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "") 93 @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0) 94 @JvmField 95 val WORKSPACE_SIZE = 96 backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED) 97 @JvmField 98 val HOTSEAT_COUNT = 99 backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED) 100 @JvmField 101 val TASKBAR_PINNING = 102 backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED) 103 @JvmField 104 val TASKBAR_PINNING_IN_DESKTOP_MODE = 105 backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED) 106 107 @JvmField 108 val DEVICE_TYPE = 109 backedUpItem( 110 DeviceGridState.KEY_DEVICE_TYPE, 111 InvariantDeviceProfile.TYPE_PHONE, 112 EncryptionType.ENCRYPTED, 113 ) 114 @JvmField 115 val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED) 116 @JvmField 117 val SHOULD_SHOW_SMARTSPACE = 118 backedUpItem( 119 SHOULD_SHOW_SMARTSPACE_KEY, 120 WIDGET_ON_FIRST_SCREEN, 121 EncryptionType.DEVICE_PROTECTED, 122 ) 123 @JvmField 124 val RESTORE_DEVICE = 125 backedUpItem( 126 RestoreDbTask.RESTORED_DEVICE_TYPE, 127 InvariantDeviceProfile.TYPE_PHONE, 128 EncryptionType.ENCRYPTED, 129 ) 130 @JvmField 131 val NO_DB_FILES_RESTORED = 132 nonRestorableItem("no_db_files_restored", false, EncryptionType.DEVICE_PROTECTED) 133 @JvmField 134 val IS_FIRST_LOAD_AFTER_RESTORE = 135 nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED) 136 @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "") 137 @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "") 138 139 @JvmField 140 val GRID_NAME = 141 ConstantItem( 142 GRID_NAME_PREFS_KEY, 143 isBackedUp = true, 144 defaultValue = null, 145 encryptionType = EncryptionType.ENCRYPTED, 146 type = String::class.java, 147 ) 148 @JvmField 149 val ALLOW_ROTATION = 150 backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) { 151 RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info) 152 } 153 154 @JvmField 155 val FIXED_LANDSCAPE_MODE = backedUpItem(SettingsActivity.FIXED_LANDSCAPE_MODE, false) 156 157 // Preferences for widget configurations 158 @JvmField 159 val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN = 160 backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false) 161 162 @JvmStatic 163 fun <T> backedUpItem( 164 sharedPrefKey: String, 165 defaultValue: T, 166 encryptionType: EncryptionType = EncryptionType.ENCRYPTED, 167 ): ConstantItem<T> = 168 ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType) 169 170 @JvmStatic 171 fun <T> backedUpItem( 172 sharedPrefKey: String, 173 type: Class<out T>, 174 encryptionType: EncryptionType = EncryptionType.ENCRYPTED, 175 defaultValueFromContext: (c: Context) -> T, 176 ): ContextualItem<T> = 177 ContextualItem( 178 sharedPrefKey, 179 isBackedUp = true, 180 defaultValueFromContext, 181 encryptionType, 182 type, 183 ) 184 185 @JvmStatic 186 fun <T> nonRestorableItem( 187 sharedPrefKey: String, 188 defaultValue: T, 189 encryptionType: EncryptionType = EncryptionType.ENCRYPTED, 190 ): ConstantItem<T> = 191 ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType) 192 193 @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.") 194 @JvmStatic 195 fun getPrefs(context: Context): SharedPreferences { 196 // Use application context for shared preferences, so we use single cached instance 197 return context.applicationContext.getSharedPreferences( 198 SHARED_PREFERENCES_KEY, 199 MODE_PRIVATE, 200 ) 201 } 202 203 @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.") 204 @JvmStatic 205 fun getDevicePrefs(context: Context): SharedPreferences { 206 // Use application context for shared preferences, so we use a single cached instance 207 return context.applicationContext.getSharedPreferences( 208 DEVICE_PREFERENCES_KEY, 209 MODE_PRIVATE, 210 ) 211 } 212 } 213 } 214 215 private class LauncherPrefsImpl(private val encryptedContext: Context) : LauncherPrefs() { 216 private val deviceProtectedStorageContext = 217 encryptedContext.createDeviceProtectedStorageContext() 218 219 private val bootAwarePrefs 220 get() = 221 deviceProtectedStorageContext.getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE) 222 223 private val Item.encryptedPrefs 224 get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE) 225 chooseSharedPreferencesnull226 private fun chooseSharedPreferences(item: Item): SharedPreferences = 227 if (item.encryptionType == EncryptionType.DEVICE_PROTECTED) bootAwarePrefs 228 else item.encryptedPrefs 229 230 /** Wrapper around `getInner` for a `ContextualItem` */ 231 override fun <T> get(item: ContextualItem<T>): T = 232 getInner(item, item.defaultValueFromContext(encryptedContext)) 233 234 /** Wrapper around `getInner` for an `Item` */ 235 override fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue) 236 237 /** 238 * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the 239 * default value type, and will throw an error if the type of the item provided is not a 240 * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`. 241 */ 242 @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST") 243 private fun <T> getInner(item: Item, default: T): T { 244 val sp = chooseSharedPreferences(item) 245 246 return when (item.type) { 247 String::class.java -> sp.getString(item.sharedPrefKey, default as? String) 248 Boolean::class.java, 249 java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean) 250 Int::class.java, 251 java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int) 252 Float::class.java, 253 java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float) 254 Long::class.java, 255 java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long) 256 Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set<String>) 257 else -> 258 throw IllegalArgumentException( 259 "item type: ${item.type}" + " is not compatible with sharedPref methods" 260 ) 261 } 262 as T 263 } 264 265 /** 266 * Stores each of the values provided in `SharedPreferences` according to the configuration 267 * contained within the associated items provided. Internally, it uses apply, so the caller 268 * cannot assume that the values that have been put are immediately available for use. 269 * 270 * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from 271 * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the 272 * provided item configurations. 273 */ putnull274 override fun put(vararg itemsToValues: Pair<Item, Any>): Unit = 275 prepareToPutValues(itemsToValues).forEach { it.apply() } 276 277 /** See referenced `put` method above. */ putnull278 override fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value)) 279 280 /** 281 * Synchronously stores all the values provided according to their associated Item 282 * configuration. 283 */ 284 override fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit = 285 prepareToPutValues(itemsToValues).forEach { it.commit() } 286 287 /** 288 * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If 289 * the item is boot aware, this method updates both the boot aware and the encrypted files. This 290 * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs 291 * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which 292 * already points to encrypted storage. 293 * 294 * Returns a list of editors with all transactions added so that the caller can determine to use 295 * .apply() or .commit() 296 */ prepareToPutValuesnull297 private fun prepareToPutValues( 298 updates: Array<out Pair<Item, Any>> 299 ): List<SharedPreferences.Editor> { 300 val updatesPerPrefFile = 301 updates 302 .filter { it.first.encryptionType != EncryptionType.DEVICE_PROTECTED } 303 .groupBy { it.first.encryptedPrefs } 304 .toMutableMap() 305 306 val bootAwareUpdates = 307 updates.filter { it.first.encryptionType == EncryptionType.DEVICE_PROTECTED } 308 if (bootAwareUpdates.isNotEmpty()) { 309 updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates 310 } 311 312 return updatesPerPrefFile.map { prefToItemValueList -> 313 prefToItemValueList.key.edit().apply { 314 prefToItemValueList.value.forEach { itemToValue: Pair<Item, Any> -> 315 putValue(itemToValue.first, itemToValue.second) 316 } 317 } 318 } 319 } 320 321 /** 322 * Handles adding values to `SharedPreferences` regardless of type. This method is especially 323 * helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple 324 * types of Item values. 325 */ 326 @Suppress("UNCHECKED_CAST") SharedPreferencesnull327 private fun SharedPreferences.Editor.putValue( 328 item: Item, 329 value: Any?, 330 ): SharedPreferences.Editor = 331 when (item.type) { 332 String::class.java -> putString(item.sharedPrefKey, value as? String) 333 Boolean::class.java, 334 java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean) 335 Int::class.java, 336 java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int) 337 Float::class.java, 338 java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float) 339 Long::class.java, 340 java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long) 341 Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>) 342 else -> 343 throw IllegalArgumentException( 344 "item type: ${item.type} is not compatible with sharedPref methods" 345 ) 346 } 347 348 /** 349 * After calling this method, the listener will be notified of any future updates to the 350 * `SharedPreferences` files associated with the provided list of items. The listener will need 351 * to filter update notifications so they don't activate for non-relevant updates. 352 */ addListenernull353 override fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) { 354 items 355 .map { chooseSharedPreferences(it) } 356 .distinct() 357 .forEach { it.registerOnSharedPreferenceChangeListener(listener) } 358 } 359 360 /** 361 * Stops the listener from getting notified of any more updates to any of the 362 * `SharedPreferences` files associated with any of the provided list of [Item]. 363 */ removeListenernull364 override fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) { 365 // If a listener is not registered to a SharedPreference, unregistering it does nothing 366 items 367 .map { chooseSharedPreferences(it) } 368 .distinct() 369 .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) } 370 } 371 372 /** 373 * Checks if all the provided [Item] have values stored in their corresponding 374 * `SharedPreferences` files. 375 */ hasnull376 override fun has(vararg items: Item): Boolean { 377 items 378 .groupBy { chooseSharedPreferences(it) } 379 .forEach { (prefs, itemsSublist) -> 380 if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false 381 } 382 return true 383 } 384 385 /** 386 * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. 387 */ <lambda>null388 override fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() } 389 390 /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */ <lambda>null391 override fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() } 392 393 /** 394 * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the 395 * item is boot aware, this method removes the data from both the boot aware and encrypted 396 * files. 397 * 398 * @return a list of editors with all transactions added so that the caller can determine to use 399 * .apply() or .commit() 400 */ prepareToRemovenull401 private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> { 402 val itemsPerFile = 403 items 404 .filter { it.encryptionType != EncryptionType.DEVICE_PROTECTED } 405 .groupBy { it.encryptedPrefs } 406 .toMutableMap() 407 408 val bootAwareUpdates = items.filter { it.encryptionType == EncryptionType.DEVICE_PROTECTED } 409 if (bootAwareUpdates.isNotEmpty()) { 410 itemsPerFile[bootAwarePrefs] = bootAwareUpdates 411 } 412 413 return itemsPerFile.map { (prefs, items) -> 414 prefs.edit().also { editor -> 415 items.forEach { item -> editor.remove(item.sharedPrefKey) } 416 } 417 } 418 } 419 closenull420 override fun close() {} 421 } 422 423 abstract class Item { 424 abstract val sharedPrefKey: String 425 abstract val isBackedUp: Boolean 426 abstract val type: Class<*> 427 abstract val encryptionType: EncryptionType 428 val sharedPrefFile: String 429 get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY 430 tonull431 fun <T> to(value: T): Pair<Item, T> = Pair(this, value) 432 } 433 434 data class ConstantItem<T>( 435 override val sharedPrefKey: String, 436 override val isBackedUp: Boolean, 437 val defaultValue: T, 438 override val encryptionType: EncryptionType, 439 // The default value can be null. If so, the type needs to be explicitly stated, or else NPE 440 override val type: Class<out T> = defaultValue!!::class.java, 441 ) : Item() { 442 443 fun get(c: Context): T = LauncherPrefs.get(c).get(this) 444 } 445 446 data class ContextualItem<T>( 447 override val sharedPrefKey: String, 448 override val isBackedUp: Boolean, 449 private val defaultSupplier: (c: Context) -> T, 450 override val encryptionType: EncryptionType, 451 override val type: Class<out T>, 452 ) : Item() { 453 private var default: T? = null 454 defaultValueFromContextnull455 fun defaultValueFromContext(context: Context): T { 456 if (default == null) { 457 default = defaultSupplier(context) 458 } 459 return default!! 460 } 461 getnull462 fun get(c: Context): T = LauncherPrefs.get(c).get(this) 463 } 464 465 enum class EncryptionType { 466 ENCRYPTED, 467 DEVICE_PROTECTED, 468 } 469