1d7f703514
This semi-complicated case requires use of a context in order to get the default value. That is why it was migrated by itself. Bug: 261635315 Test: Manually tested all the base functionality of the launcher. Change-Id: I40d8ccd414402b55921a0a224264414d3245871b
303 lines
13 KiB
Kotlin
303 lines
13 KiB
Kotlin
package com.android.launcher3
|
|
|
|
import android.content.Context
|
|
import android.content.SharedPreferences
|
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
|
import androidx.annotation.VisibleForTesting
|
|
import com.android.launcher3.allapps.WorkProfileManager
|
|
import com.android.launcher3.model.DeviceGridState
|
|
import com.android.launcher3.pm.InstallSessionHelper
|
|
import com.android.launcher3.provider.RestoreDbTask
|
|
import com.android.launcher3.states.RotationHelper
|
|
import com.android.launcher3.util.DisplayController
|
|
import com.android.launcher3.util.MainThreadInitializedObject
|
|
import com.android.launcher3.util.Themes
|
|
|
|
/**
|
|
* Use same context for shared preferences, so that we use a single cached instance
|
|
* TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
|
|
*/
|
|
class LauncherPrefs(private val context: Context) {
|
|
|
|
/** Wrapper around `getInner` for a `ContextualItem` */
|
|
fun <T : Any> get(item: ContextualItem<T>): T =
|
|
getInner(item, item.defaultValueFromContext(context))
|
|
|
|
/** Wrapper around `getInner` for an `Item` */
|
|
fun <T : Any> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
|
|
|
|
/**
|
|
* Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
|
|
* default value type, and will throw an error if the type of the item provided is not a
|
|
* `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
|
|
*/
|
|
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
|
|
private fun <T : Any> getInner(item: Item, default: T): T {
|
|
val sp = context.getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE)
|
|
|
|
return when (default::class.java) {
|
|
String::class.java -> sp.getString(item.sharedPrefKey, default as String)
|
|
Boolean::class.java,
|
|
java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
|
|
Int::class.java,
|
|
java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int)
|
|
Float::class.java,
|
|
java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
|
|
Long::class.java,
|
|
java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long)
|
|
Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as Set<String>)
|
|
else ->
|
|
throw IllegalArgumentException(
|
|
"item type: ${default::class.java}" +
|
|
" is not compatible with sharedPref methods"
|
|
)
|
|
}
|
|
as T
|
|
}
|
|
|
|
/**
|
|
* Stores each of the values provided in `SharedPreferences` according to the configuration
|
|
* contained within the associated items provided. Internally, it uses apply, so the caller
|
|
* cannot assume that the values that have been put are immediately available for use.
|
|
*
|
|
* The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from
|
|
* prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
|
|
* provided item configurations.
|
|
*/
|
|
fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
|
|
prepareToPutValues(itemsToValues).forEach { it.apply() }
|
|
|
|
/**
|
|
* Stores the value provided in `SharedPreferences` according to the item configuration provided
|
|
* It is asynchronous, so the caller can't assume that the value put is immediately available.
|
|
*/
|
|
fun <T : Any> put(item: Item, value: T): Unit =
|
|
context
|
|
.getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE)
|
|
.edit()
|
|
.putValue(item, value)
|
|
.apply()
|
|
|
|
/**
|
|
* Synchronously stores all the values provided according to their associated Item
|
|
* configuration.
|
|
*/
|
|
fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
|
|
prepareToPutValues(itemsToValues).forEach { it.commit() }
|
|
|
|
/**
|
|
* Update each shared preference file with the item - value pairs provided. This method is
|
|
* optimized to avoid retrieving the same shared preference file multiple times.
|
|
*
|
|
* @return `List<SharedPreferences.Editor>` 1 for each distinct shared preference file among the
|
|
* items given as part of the itemsToValues parameter
|
|
*/
|
|
private fun prepareToPutValues(
|
|
itemsToValues: Array<out Pair<Item, Any>>
|
|
): List<SharedPreferences.Editor> =
|
|
itemsToValues
|
|
.groupBy { it.first.sharedPrefFile }
|
|
.map { fileToItemValueList ->
|
|
context
|
|
.getSharedPreferences(fileToItemValueList.key, Context.MODE_PRIVATE)
|
|
.edit()
|
|
.apply {
|
|
fileToItemValueList.value.forEach { itemToValue ->
|
|
putValue(itemToValue.first, itemToValue.second)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles adding values to `SharedPreferences` regardless of type. This method is especially
|
|
* helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple
|
|
* types of Item values.
|
|
*/
|
|
@Suppress("UNCHECKED_CAST")
|
|
private fun SharedPreferences.Editor.putValue(
|
|
item: Item,
|
|
value: Any
|
|
): SharedPreferences.Editor =
|
|
when (value::class.java) {
|
|
String::class.java -> putString(item.sharedPrefKey, value as String)
|
|
Boolean::class.java,
|
|
java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
|
|
Int::class.java,
|
|
java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int)
|
|
Float::class.java,
|
|
java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
|
|
Long::class.java,
|
|
java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
|
|
Set::class.java -> putStringSet(item.sharedPrefKey, value as Set<String>)
|
|
else ->
|
|
throw IllegalArgumentException(
|
|
"item type: ${value::class} is not compatible with sharedPref methods"
|
|
)
|
|
}
|
|
|
|
/**
|
|
* After calling this method, the listener will be notified of any future updates to the
|
|
* `SharedPreferences` files associated with the provided list of items. The listener will need
|
|
* to filter update notifications so they don't activate for non-relevant updates.
|
|
*/
|
|
fun addListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
|
|
items
|
|
.map { it.sharedPrefFile }
|
|
.distinct()
|
|
.forEach {
|
|
context
|
|
.getSharedPreferences(it, Context.MODE_PRIVATE)
|
|
.registerOnSharedPreferenceChangeListener(listener)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops the listener from getting notified of any more updates to any of the
|
|
* `SharedPreferences` files associated with any of the provided list of [Item].
|
|
*/
|
|
fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
|
|
// If a listener is not registered to a SharedPreference, unregistering it does nothing
|
|
items
|
|
.map { it.sharedPrefFile }
|
|
.distinct()
|
|
.forEach {
|
|
context
|
|
.getSharedPreferences(it, Context.MODE_PRIVATE)
|
|
.unregisterOnSharedPreferenceChangeListener(listener)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if all the provided [Item] have values stored in their corresponding
|
|
* `SharedPreferences` files.
|
|
*/
|
|
fun has(vararg items: Item): Boolean {
|
|
items
|
|
.groupBy { it.sharedPrefFile }
|
|
.forEach { (file, itemsSublist) ->
|
|
val prefs: SharedPreferences =
|
|
context.getSharedPreferences(file, Context.MODE_PRIVATE)
|
|
if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
|
|
*/
|
|
fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
|
|
|
|
/** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
|
|
fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
|
|
|
|
/**
|
|
* Creates `SharedPreferences.Editor` transactions for removing all the provided [Item] values
|
|
* from their respective `SharedPreferences` files. These returned `Editors` can then be
|
|
* committed or applied for synchronous or async behavior.
|
|
*/
|
|
private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> =
|
|
items
|
|
.groupBy { it.sharedPrefFile }
|
|
.map { (file, items) ->
|
|
context.getSharedPreferences(file, Context.MODE_PRIVATE).edit().also { editor ->
|
|
items.forEach { item -> editor.remove(item.sharedPrefKey) }
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
@JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) }
|
|
|
|
@JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
|
|
|
|
@JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "")
|
|
@JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false)
|
|
@JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
|
|
@JvmField val WORK_EDU_STEP = backedUpItem(WorkProfileManager.KEY_WORK_EDU_STEP, 0)
|
|
@JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "")
|
|
@JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1)
|
|
@JvmField
|
|
val DEVICE_TYPE =
|
|
backedUpItem(DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE)
|
|
@JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "")
|
|
@JvmField
|
|
val RESTORE_DEVICE =
|
|
backedUpItem(RestoreDbTask.RESTORED_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE)
|
|
@JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
|
|
@JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
|
|
@JvmField
|
|
val ALLOW_ROTATION =
|
|
backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY) {
|
|
RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
|
|
}
|
|
|
|
@VisibleForTesting
|
|
@JvmStatic
|
|
fun <T> backedUpItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
|
|
ConstantItem(sharedPrefKey, LauncherFiles.SHARED_PREFERENCES_KEY, defaultValue)
|
|
|
|
@JvmStatic
|
|
fun <T> backedUpItem(
|
|
sharedPrefKey: String,
|
|
defaultValueFromContext: (c: Context) -> T
|
|
): ContextualItem<T> =
|
|
ContextualItem(
|
|
sharedPrefKey,
|
|
LauncherFiles.SHARED_PREFERENCES_KEY,
|
|
defaultValueFromContext
|
|
)
|
|
|
|
@VisibleForTesting
|
|
@JvmStatic
|
|
fun <T> nonRestorableItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
|
|
ConstantItem(sharedPrefKey, LauncherFiles.DEVICE_PREFERENCES_KEY, defaultValue)
|
|
|
|
@Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
|
|
@JvmStatic
|
|
fun getPrefs(context: Context): SharedPreferences {
|
|
// Use application context for shared preferences, so we use single cached instance
|
|
return context.applicationContext.getSharedPreferences(
|
|
LauncherFiles.SHARED_PREFERENCES_KEY,
|
|
Context.MODE_PRIVATE
|
|
)
|
|
}
|
|
|
|
@Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
|
|
@JvmStatic
|
|
fun getDevicePrefs(context: Context): SharedPreferences {
|
|
// Use application context for shared preferences, so we use a single cached instance
|
|
return context.applicationContext.getSharedPreferences(
|
|
LauncherFiles.DEVICE_PREFERENCES_KEY,
|
|
Context.MODE_PRIVATE
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
abstract class Item {
|
|
abstract val sharedPrefKey: String
|
|
abstract val sharedPrefFile: String
|
|
|
|
fun <T> to(value: T): Pair<Item, T> = Pair(this, value)
|
|
}
|
|
|
|
data class ConstantItem<T>(
|
|
override val sharedPrefKey: String,
|
|
override val sharedPrefFile: String,
|
|
val defaultValue: T
|
|
) : Item()
|
|
|
|
data class ContextualItem<T>(
|
|
override val sharedPrefKey: String,
|
|
override val sharedPrefFile: String,
|
|
private val defaultSupplier: (c: Context) -> T
|
|
) : Item() {
|
|
private var default: T? = null
|
|
|
|
fun defaultValueFromContext(context: Context): T {
|
|
if (default == null) {
|
|
default = defaultSupplier(context)
|
|
}
|
|
return default!!
|
|
}
|
|
}
|