blob: c98df1b09940d446ee6a218ffec507b437d7df30 [file] [log] [blame]
Stefan Andonian5bd9a222023-02-23 00:58:33 +00001/*
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 */
Stefan Andonian146701c2022-11-10 23:07:40 +000016package com.android.launcher3
17
18import android.content.Context
Stefan Andonian5bd9a222023-02-23 00:58:33 +000019import android.content.Context.MODE_PRIVATE
Stefan Andonian146701c2022-11-10 23:07:40 +000020import android.content.SharedPreferences
Stefan Andoniand1b33b32022-12-16 21:22:27 +000021import android.content.SharedPreferences.OnSharedPreferenceChangeListener
Stefan Andonian5bd9a222023-02-23 00:58:33 +000022import android.util.Log
Stefan Andoniand1b33b32022-12-16 21:22:27 +000023import androidx.annotation.VisibleForTesting
Stefan Andonian4c9612b2023-02-22 00:00:03 +000024import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
25import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
Stefan Andoniand1b33b32022-12-16 21:22:27 +000026import com.android.launcher3.allapps.WorkProfileManager
27import com.android.launcher3.model.DeviceGridState
28import com.android.launcher3.pm.InstallSessionHelper
29import com.android.launcher3.provider.RestoreDbTask
Stefan Andonian1d7f7032023-01-23 21:55:04 +000030import com.android.launcher3.states.RotationHelper
31import com.android.launcher3.util.DisplayController
Stefan Andoniand1b33b32022-12-16 21:22:27 +000032import com.android.launcher3.util.MainThreadInitializedObject
33import com.android.launcher3.util.Themes
Stefan Andonian146701c2022-11-10 23:07:40 +000034
Stefan Andoniand1b33b32022-12-16 21:22:27 +000035/**
36 * Use same context for shared preferences, so that we use a single cached instance
Jordan Demeulenaerebe82bc62023-03-01 09:11:48 +000037 *
Stefan Andoniand1b33b32022-12-16 21:22:27 +000038 * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
Stefan Andonian956b88e2023-03-20 20:38:46 +000039 * TODO(b/274501660): Fix ReorderWidgets#simpleReorder test before enabling
40 * isBootAwareStartupDataEnabled
Stefan Andoniand1b33b32022-12-16 21:22:27 +000041 */
Stefan Andonian5bd9a222023-02-23 00:58:33 +000042class LauncherPrefs(private val encryptedContext: Context) {
43 private val deviceProtectedStorageContext =
44 encryptedContext.createDeviceProtectedStorageContext()
45
46 private val bootAwarePrefs
47 get() =
48 deviceProtectedStorageContext.getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
49
50 private val Item.encryptedPrefs
51 get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
52
53 // This call to `SharedPreferences` needs to be explicit rather than using `get` since doing so
54 // would result in a circular dependency between `isStartupDataMigrated` and `choosePreferences`
55 val isStartupDataMigrated: Boolean
56 get() =
57 bootAwarePrefs.getBoolean(
58 IS_STARTUP_DATA_MIGRATED.sharedPrefKey,
59 IS_STARTUP_DATA_MIGRATED.defaultValue
60 )
61
62 private fun chooseSharedPreferences(item: Item): SharedPreferences =
63 if (isBootAwareStartupDataEnabled && item.isBootAware && isStartupDataMigrated)
64 bootAwarePrefs
65 else item.encryptedPrefs
Stefan Andonian146701c2022-11-10 23:07:40 +000066
Stefan Andonian1d7f7032023-01-23 21:55:04 +000067 /** Wrapper around `getInner` for a `ContextualItem` */
Stefan Andonian5bd9a222023-02-23 00:58:33 +000068 fun <T> get(item: ContextualItem<T>): T =
69 getInner(item, item.defaultValueFromContext(encryptedContext))
Stefan Andonian1d7f7032023-01-23 21:55:04 +000070
71 /** Wrapper around `getInner` for an `Item` */
Stefan Andonian4c9612b2023-02-22 00:00:03 +000072 fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
Stefan Andonian1d7f7032023-01-23 21:55:04 +000073
Stefan Andoniand1b33b32022-12-16 21:22:27 +000074 /**
75 * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
76 * default value type, and will throw an error if the type of the item provided is not a
77 * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
78 */
79 @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
Stefan Andonian4c9612b2023-02-22 00:00:03 +000080 private fun <T> getInner(item: Item, default: T): T {
Stefan Andonian5bd9a222023-02-23 00:58:33 +000081 val sp = chooseSharedPreferences(item)
Stefan Andoniand1b33b32022-12-16 21:22:27 +000082
Stefan Andonian4c9612b2023-02-22 00:00:03 +000083 return when (item.type) {
84 String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
Stefan Andoniand1b33b32022-12-16 21:22:27 +000085 Boolean::class.java,
Stefan Andonian1d7f7032023-01-23 21:55:04 +000086 java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
Stefan Andoniand1b33b32022-12-16 21:22:27 +000087 Int::class.java,
Stefan Andonian1d7f7032023-01-23 21:55:04 +000088 java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int)
Stefan Andoniand1b33b32022-12-16 21:22:27 +000089 Float::class.java,
Stefan Andonian1d7f7032023-01-23 21:55:04 +000090 java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
Stefan Andoniand1b33b32022-12-16 21:22:27 +000091 Long::class.java,
Stefan Andonian1d7f7032023-01-23 21:55:04 +000092 java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long)
Stefan Andonian4c9612b2023-02-22 00:00:03 +000093 Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
Stefan Andoniand1b33b32022-12-16 21:22:27 +000094 else ->
95 throw IllegalArgumentException(
Stefan Andonian4c9612b2023-02-22 00:00:03 +000096 "item type: ${item.type}" + " is not compatible with sharedPref methods"
Stefan Andoniand1b33b32022-12-16 21:22:27 +000097 )
98 }
99 as T
Stefan Andonian146701c2022-11-10 23:07:40 +0000100 }
101
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000102 /**
103 * Stores each of the values provided in `SharedPreferences` according to the configuration
104 * contained within the associated items provided. Internally, it uses apply, so the caller
105 * cannot assume that the values that have been put are immediately available for use.
106 *
107 * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from
108 * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
109 * provided item configurations.
110 */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000111 fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000112 prepareToPutValues(itemsToValues).forEach { it.apply() }
113
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000114 /** See referenced `put` method above. */
115 fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000116
117 /**
118 * Synchronously stores all the values provided according to their associated Item
119 * configuration.
120 */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000121 fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000122 prepareToPutValues(itemsToValues).forEach { it.commit() }
123
124 /**
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000125 * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If
126 * the item is boot aware, this method updates both the boot aware and the encrypted files. This
127 * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs
128 * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which
129 * already points to encrypted storage.
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000130 *
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000131 * Returns a list of editors with all transactions added so that the caller can determine to use
132 * .apply() or .commit()
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000133 */
134 private fun prepareToPutValues(
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000135 updates: Array<out Pair<Item, Any>>
136 ): List<SharedPreferences.Editor> {
137 val updatesPerPrefFile = updates.groupBy { it.first.encryptedPrefs }.toMutableMap()
138
139 if (isBootAwareStartupDataEnabled) {
140 val bootAwareUpdates = updates.filter { it.first.isBootAware }
141 if (bootAwareUpdates.isNotEmpty()) {
142 updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000143 }
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000144 }
145
146 return updatesPerPrefFile.map { prefToItemValueList ->
147 prefToItemValueList.key.edit().apply {
148 prefToItemValueList.value.forEach { itemToValue: Pair<Item, Any> ->
149 putValue(itemToValue.first, itemToValue.second)
150 }
151 }
152 }
153 }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000154
155 /**
156 * Handles adding values to `SharedPreferences` regardless of type. This method is especially
157 * helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple
158 * types of Item values.
159 */
160 @Suppress("UNCHECKED_CAST")
161 private fun SharedPreferences.Editor.putValue(
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000162 item: Item,
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000163 value: Any?
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000164 ): SharedPreferences.Editor =
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000165 when (item.type) {
166 String::class.java -> putString(item.sharedPrefKey, value as? String)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000167 Boolean::class.java,
168 java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
169 Int::class.java,
170 java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int)
171 Float::class.java,
172 java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
173 Long::class.java,
174 java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000175 Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000176 else ->
177 throw IllegalArgumentException(
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000178 "item type: ${item.type} is not compatible with sharedPref methods"
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000179 )
180 }
181
182 /**
183 * After calling this method, the listener will be notified of any future updates to the
184 * `SharedPreferences` files associated with the provided list of items. The listener will need
185 * to filter update notifications so they don't activate for non-relevant updates.
186 */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000187 fun addListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000188 items
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000189 .map { chooseSharedPreferences(it) }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000190 .distinct()
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000191 .forEach { it.registerOnSharedPreferenceChangeListener(listener) }
Thales Lima03ac3772023-01-06 15:16:41 +0000192 }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000193
194 /**
195 * Stops the listener from getting notified of any more updates to any of the
196 * `SharedPreferences` files associated with any of the provided list of [Item].
197 */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000198 fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000199 // If a listener is not registered to a SharedPreference, unregistering it does nothing
200 items
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000201 .map { chooseSharedPreferences(it) }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000202 .distinct()
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000203 .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000204 }
205
206 /**
207 * Checks if all the provided [Item] have values stored in their corresponding
208 * `SharedPreferences` files.
209 */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000210 fun has(vararg items: Item): Boolean {
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000211 items
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000212 .groupBy { chooseSharedPreferences(it) }
213 .forEach { (prefs, itemsSublist) ->
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000214 if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
215 }
216 return true
217 }
218
219 /**
220 * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
221 */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000222 fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000223
224 /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000225 fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000226
227 /**
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000228 * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
229 * item is boot aware, this method removes the data from both the boot aware and encrypted
230 * files.
231 *
232 * @return a list of editors with all transactions added so that the caller can determine to use
Stefan Andonian956b88e2023-03-20 20:38:46 +0000233 * .apply() or .commit()
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000234 */
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000235 private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
236 val itemsPerFile = items.groupBy { it.encryptedPrefs }.toMutableMap()
237
238 if (isBootAwareStartupDataEnabled) {
239 val bootAwareUpdates = items.filter { it.isBootAware }
240 if (bootAwareUpdates.isNotEmpty()) {
241 itemsPerFile[bootAwarePrefs] = bootAwareUpdates
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000242 }
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000243 }
244
245 return itemsPerFile.map { (prefs, items) ->
246 prefs.edit().also { editor ->
247 items.forEach { item -> editor.remove(item.sharedPrefKey) }
248 }
249 }
250 }
251
252 fun migrateStartupDataToDeviceProtectedStorage() {
253 if (!isBootAwareStartupDataEnabled) return
254
255 Log.d(
256 TAG,
257 "Migrating data to unencrypted shared preferences to enable preloading " +
258 "while the user is locked the next time the device reboots."
259 )
260
261 with(bootAwarePrefs.edit()) {
262 BOOT_AWARE_ITEMS.forEach { putValue(it, get(it)) }
263 putBoolean(IS_STARTUP_DATA_MIGRATED.sharedPrefKey, true)
264 apply()
265 }
266 }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000267
268 companion object {
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000269 private const val TAG = "LauncherPrefs"
270 @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
271
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000272 @JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) }
273
274 @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
275
Jagrut Desaic6d625b2023-04-03 16:57:32 -0700276 const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000277 @JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "", true)
278 @JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, true)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000279 @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
280 @JvmField val WORK_EDU_STEP = backedUpItem(WorkProfileManager.KEY_WORK_EDU_STEP, 0)
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000281 @JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", true)
282 @JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, true)
Jagrut Desaic6d625b2023-04-03 16:57:32 -0700283 @JvmField val TASKBAR_PINNING = backedUpItem(TASKBAR_PINNING_KEY, false)
284
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000285 @JvmField
286 val DEVICE_TYPE =
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000287 backedUpItem(DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, true)
288 @JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", true)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000289 @JvmField
290 val RESTORE_DEVICE =
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000291 backedUpItem(
292 RestoreDbTask.RESTORED_DEVICE_TYPE,
293 InvariantDeviceProfile.TYPE_PHONE,
294 true
295 )
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000296 @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
297 @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000298 @JvmField
299 val GRID_NAME =
300 ConstantItem(
301 "idp_grid_name",
302 isBackedUp = true,
303 defaultValue = null,
304 isBootAware = true,
305 type = String::class.java
306 )
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000307 @JvmField
308 val ALLOW_ROTATION =
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000309 backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000310 RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
311 }
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000312 @JvmField
313 val IS_STARTUP_DATA_MIGRATED =
314 ConstantItem(
315 "is_startup_data_boot_aware",
316 isBackedUp = false,
317 defaultValue = false,
318 isBootAware = true
319 )
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000320
321 @VisibleForTesting
322 @JvmStatic
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000323 fun <T> backedUpItem(
324 sharedPrefKey: String,
325 defaultValue: T,
326 isBootAware: Boolean = false
327 ): ConstantItem<T> =
328 ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, isBootAware)
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000329
330 @JvmStatic
331 fun <T> backedUpItem(
332 sharedPrefKey: String,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000333 type: Class<out T>,
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000334 isBootAware: Boolean = false,
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000335 defaultValueFromContext: (c: Context) -> T
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000336 ): ContextualItem<T> =
337 ContextualItem(
338 sharedPrefKey,
339 isBackedUp = true,
340 defaultValueFromContext,
341 isBootAware,
342 type
343 )
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000344
345 @VisibleForTesting
346 @JvmStatic
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000347 fun <T> nonRestorableItem(
348 sharedPrefKey: String,
349 defaultValue: T,
350 isBootAware: Boolean = false
351 ): ConstantItem<T> =
352 ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, isBootAware)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000353
354 @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
355 @JvmStatic
356 fun getPrefs(context: Context): SharedPreferences {
357 // Use application context for shared preferences, so we use single cached instance
358 return context.applicationContext.getSharedPreferences(
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000359 SHARED_PREFERENCES_KEY,
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000360 MODE_PRIVATE
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000361 )
362 }
363
364 @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
365 @JvmStatic
366 fun getDevicePrefs(context: Context): SharedPreferences {
367 // Use application context for shared preferences, so we use a single cached instance
368 return context.applicationContext.getSharedPreferences(
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000369 DEVICE_PREFERENCES_KEY,
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000370 MODE_PRIVATE
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000371 )
372 }
373 }
374}
375
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000376// This is hard-coded to false for now until it is time to release this optimization. It is only
377// a var because the unit tests are setting this to true so they can run.
378@VisibleForTesting var isBootAwareStartupDataEnabled: Boolean = false
379
380private val BOOT_AWARE_ITEMS: MutableSet<ConstantItem<*>> = mutableSetOf()
381
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000382abstract class Item {
383 abstract val sharedPrefKey: String
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000384 abstract val isBackedUp: Boolean
385 abstract val type: Class<*>
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000386 abstract val isBootAware: Boolean
Stefan Andonian246eeaa2023-02-21 22:14:47 +0000387 val sharedPrefFile: String
388 get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000389
390 fun <T> to(value: T): Pair<Item, T> = Pair(this, value)
391}
392
393data class ConstantItem<T>(
394 override val sharedPrefKey: String,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000395 override val isBackedUp: Boolean,
396 val defaultValue: T,
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000397 override val isBootAware: Boolean,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000398 // The default value can be null. If so, the type needs to be explicitly stated, or else NPE
399 override val type: Class<out T> = defaultValue!!::class.java
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000400) : Item() {
401 init {
402 if (isBootAware && isBootAwareStartupDataEnabled) {
403 BOOT_AWARE_ITEMS.add(this)
404 }
405 }
406}
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000407
408data class ContextualItem<T>(
409 override val sharedPrefKey: String,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000410 override val isBackedUp: Boolean,
411 private val defaultSupplier: (c: Context) -> T,
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000412 override val isBootAware: Boolean,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000413 override val type: Class<out T>
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000414) : Item() {
415 private var default: T? = null
416
417 fun defaultValueFromContext(context: Context): T {
418 if (default == null) {
419 default = defaultSupplier(context)
420 }
421 return default!!
422 }
Thales Lima03ac3772023-01-06 15:16:41 +0000423}