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