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.GridType.Companion.GRID_TYPE_ANY 24 import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY 25 import com.android.launcher3.InvariantDeviceProfile.NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY 26 import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY 27 import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY 28 import com.android.launcher3.dagger.ApplicationContext 29 import com.android.launcher3.dagger.LauncherAppComponent 30 import com.android.launcher3.dagger.LauncherAppSingleton 31 import com.android.launcher3.model.DeviceGridState 32 import com.android.launcher3.pm.InstallSessionHelper 33 import com.android.launcher3.provider.RestoreDbTask 34 import com.android.launcher3.provider.RestoreDbTask.FIRST_LOAD_AFTER_RESTORE_KEY 35 import com.android.launcher3.settings.SettingsActivity 36 import com.android.launcher3.states.RotationHelper 37 import com.android.launcher3.util.DaggerSingletonObject 38 import com.android.launcher3.util.DisplayController 39 import java.util.concurrent.ConcurrentHashMap 40 import javax.inject.Inject 41 42 /** 43 * Manages Launcher [SharedPreferences] through [Item] instances. 44 * 45 * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods. 46 */ 47 @LauncherAppSingleton 48 open class LauncherPrefs 49 @Inject 50 constructor(@ApplicationContext private val encryptedContext: Context) { 51 52 private val deviceProtectedSharedPrefs: SharedPreferences by lazy { 53 encryptedContext 54 .createDeviceProtectedStorageContext() 55 .getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE) 56 } 57 58 open protected fun getSharedPrefs(item: Item): SharedPreferences = 59 item.run { 60 if (encryptionType == EncryptionType.DEVICE_PROTECTED) deviceProtectedSharedPrefs 61 else encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE) 62 } 63 64 /** Returns the value with type [T] for [item]. */ 65 fun <T> get(item: ContextualItem<T>): T = 66 getInner(item, item.defaultValueFromContext(encryptedContext)) 67 68 /** Returns the value with type [T] for [item]. */ 69 fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue) 70 71 /** 72 * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the 73 * default value type, and will throw an error if the type of the item provided is not a 74 * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`. 75 */ 76 @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST") 77 private fun <T> getInner(item: Item, default: T): T { 78 val sp = getSharedPrefs(item) 79 return when { 80 item.type == String::class.java -> sp.getString(item.sharedPrefKey, default as? String) 81 item.type == Boolean::class.java || item.type == java.lang.Boolean::class.java -> 82 sp.getBoolean(item.sharedPrefKey, default as Boolean) 83 item.type == Int::class.java || item.type == java.lang.Integer::class.java -> 84 sp.getInt(item.sharedPrefKey, default as Int) 85 item.type == Float::class.java || item.type == java.lang.Float::class.java -> 86 sp.getFloat(item.sharedPrefKey, default as Float) 87 item.type == Long::class.java || item.type == java.lang.Long::class.java -> 88 sp.getLong(item.sharedPrefKey, default as Long) 89 Set::class.java.isAssignableFrom(item.type) -> 90 sp.getStringSet(item.sharedPrefKey, default as? Set<String>) 91 else -> 92 throw IllegalArgumentException( 93 "item type: ${item.type}" + " is not compatible with sharedPref methods" 94 ) 95 } 96 as T 97 } 98 99 /** 100 * Stores each of the values provided in `SharedPreferences` according to the configuration 101 * contained within the associated items provided. Internally, it uses apply, so the caller 102 * cannot assume that the values that have been put are immediately available for use. 103 * 104 * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from 105 * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the 106 * provided item configurations. 107 */ 108 fun put(vararg itemsToValues: Pair<Item, Any>): Unit = 109 prepareToPutValues(itemsToValues).forEach { it.apply() } 110 111 /** See referenced `put` method above. */ 112 fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value)) 113 114 /** 115 * Synchronously stores all the values provided according to their associated Item 116 * configuration. 117 */ 118 fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit = 119 prepareToPutValues(itemsToValues).forEach { it.commit() } 120 121 /** 122 * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If 123 * the item is boot aware, this method updates both the boot aware and the encrypted files. This 124 * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs 125 * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which 126 * already points to encrypted storage. 127 * 128 * Returns a list of editors with all transactions added so that the caller can determine to use 129 * .apply() or .commit() 130 */ 131 private fun prepareToPutValues( 132 updates: Array<out Pair<Item, Any>> 133 ): List<SharedPreferences.Editor> { 134 val updatesPerPrefFile = updates.groupBy { getSharedPrefs(it.first) }.toMap() 135 136 return updatesPerPrefFile.map { (sharedPref, itemList) -> 137 sharedPref.edit().apply { itemList.forEach { (item, value) -> putValue(item, value) } } 138 } 139 } 140 141 /** 142 * Handles adding values to `SharedPreferences` regardless of type. This method is especially 143 * helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple 144 * types of Item values. 145 */ 146 @Suppress("UNCHECKED_CAST") 147 internal fun SharedPreferences.Editor.putValue( 148 item: Item, 149 value: Any?, 150 ): SharedPreferences.Editor = 151 when { 152 item.type == String::class.java -> putString(item.sharedPrefKey, value as? String) 153 item.type == Boolean::class.java || item.type == java.lang.Boolean::class.java -> 154 putBoolean(item.sharedPrefKey, value as Boolean) 155 item.type == Int::class.java || item.type == java.lang.Integer::class.java -> 156 putInt(item.sharedPrefKey, value as Int) 157 item.type == Float::class.java || item.type == java.lang.Float::class.java -> 158 putFloat(item.sharedPrefKey, value as Float) 159 item.type == Long::class.java || item.type == java.lang.Long::class.java -> 160 putLong(item.sharedPrefKey, value as Long) 161 Set::class.java.isAssignableFrom(item.type) -> 162 putStringSet(item.sharedPrefKey, value as? Set<String>) 163 else -> 164 throw IllegalArgumentException( 165 "item type: ${item.type} is not compatible with sharedPref methods" 166 ) 167 } 168 169 /** 170 * After calling this method, the listener will be notified of any future updates to the 171 * `SharedPreferences` files associated with the provided list of items. The listener will need 172 * to filter update notifications so they don't activate for non-relevant updates. 173 */ 174 fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) { 175 items 176 .map { getSharedPrefs(it) } 177 .distinct() 178 .forEach { it.registerOnSharedPreferenceChangeListener(listener) } 179 } 180 181 /** 182 * Stops the listener from getting notified of any more updates to any of the 183 * `SharedPreferences` files associated with any of the provided list of [Item]. 184 */ 185 fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) { 186 // If a listener is not registered to a SharedPreference, unregistering it does nothing 187 items 188 .map { getSharedPrefs(it) } 189 .distinct() 190 .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) } 191 } 192 193 /** 194 * Checks if all the provided [Item] have values stored in their corresponding 195 * `SharedPreferences` files. 196 */ 197 fun has(vararg items: Item): Boolean { 198 items 199 .groupBy { getSharedPrefs(it) } 200 .forEach { (prefs, itemsSublist) -> 201 if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false 202 } 203 return true 204 } 205 206 /** 207 * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. 208 */ 209 fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() } 210 211 /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */ 212 fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() } 213 214 /** 215 * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the 216 * item is boot aware, this method removes the data from both the boot aware and encrypted 217 * files. 218 * 219 * @return a list of editors with all transactions added so that the caller can determine to use 220 * .apply() or .commit() 221 */ 222 private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> { 223 val itemsPerFile = items.groupBy { getSharedPrefs(it) }.toMap() 224 225 return itemsPerFile.map { (prefs, items) -> 226 prefs.edit().also { editor -> 227 items.forEach { item -> editor.remove(item.sharedPrefKey) } 228 } 229 } 230 } 231 232 companion object { 233 @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs" 234 235 @JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getLauncherPrefs) 236 237 @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context) 238 239 const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY" 240 const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY" 241 const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY" 242 243 @JvmField 244 val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false) 245 @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "") 246 @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0) 247 @JvmField 248 val WORKSPACE_SIZE = 249 backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED) 250 @JvmField 251 val HOTSEAT_COUNT = 252 backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED) 253 @JvmField 254 val TASKBAR_PINNING = 255 backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED) 256 @JvmField 257 val TASKBAR_PINNING_IN_DESKTOP_MODE = 258 backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED) 259 260 @JvmField 261 val DEVICE_TYPE = 262 backedUpItem( 263 DeviceGridState.KEY_DEVICE_TYPE, 264 InvariantDeviceProfile.TYPE_PHONE, 265 EncryptionType.ENCRYPTED, 266 ) 267 @JvmField 268 val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED) 269 @JvmField 270 val GRID_TYPE = 271 backedUpItem(DeviceGridState.KEY_GRID_TYPE, GRID_TYPE_ANY, EncryptionType.ENCRYPTED) 272 @JvmField 273 val SHOULD_SHOW_SMARTSPACE = 274 backedUpItem( 275 SHOULD_SHOW_SMARTSPACE_KEY, 276 WIDGET_ON_FIRST_SCREEN, 277 EncryptionType.DEVICE_PROTECTED, 278 ) 279 @JvmField 280 val RESTORE_DEVICE = 281 backedUpItem( 282 RestoreDbTask.RESTORED_DEVICE_TYPE, 283 InvariantDeviceProfile.TYPE_PHONE, 284 EncryptionType.ENCRYPTED, 285 ) 286 @JvmField 287 val NO_DB_FILES_RESTORED = 288 nonRestorableItem("no_db_files_restored", false, EncryptionType.DEVICE_PROTECTED) 289 @JvmField 290 val IS_FIRST_LOAD_AFTER_RESTORE = 291 nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED) 292 @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "") 293 @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "") 294 295 @JvmField 296 val GRID_NAME = 297 ConstantItem( 298 GRID_NAME_PREFS_KEY, 299 isBackedUp = true, 300 defaultValue = null, 301 encryptionType = EncryptionType.ENCRYPTED, 302 type = String::class.java, 303 ) 304 @JvmField 305 val ALLOW_ROTATION = 306 backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) { 307 RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info) 308 } 309 310 @JvmField 311 val FIXED_LANDSCAPE_MODE = backedUpItem(SettingsActivity.FIXED_LANDSCAPE_MODE, false) 312 313 @JvmField 314 val NON_FIXED_LANDSCAPE_GRID_NAME = 315 ConstantItem( 316 NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY, 317 isBackedUp = true, 318 defaultValue = null, 319 encryptionType = EncryptionType.ENCRYPTED, 320 type = String::class.java, 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 428 /** 429 * LauncherPrefs which delegates all lookup to [prefs] but uses the real prefs for initial values 430 */ 431 class ProxyPrefs(context: Context, private val prefs: SharedPreferences) : LauncherPrefs(context) { 432 433 private val copiedPrefs = ConcurrentHashMap<SharedPreferences, Boolean>() 434 getSharedPrefsnull435 override fun getSharedPrefs(item: Item): SharedPreferences { 436 val originalPrefs = super.getSharedPrefs(item) 437 // Copy all existing values, when the pref is accessed for the first time 438 copiedPrefs.computeIfAbsent(originalPrefs) { op -> 439 val editor = prefs.edit() 440 op.all.forEach { (key, value) -> 441 if (value != null) { 442 editor.putValue(backedUpItem(key, value), value) 443 } 444 } 445 editor.commit() 446 } 447 return prefs 448 } 449 } 450