blob: 1120ec8b8d72734801808a40b61aa7451cc90208 [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 androidx.annotation.VisibleForTesting
fbarone58aaf12023-09-25 11:34:56 -070022import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
Charlie Andersonbad2be42024-12-06 16:32:03 -050023import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY
Stefan Andonian4c9612b2023-02-22 00:00:03 +000024import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
25import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
Sunny Goyale79d4532024-12-31 00:10:20 -080026import com.android.launcher3.dagger.ApplicationContext
27import com.android.launcher3.dagger.LauncherAppComponent
28import com.android.launcher3.dagger.LauncherAppSingleton
Stefan Andoniand1b33b32022-12-16 21:22:27 +000029import com.android.launcher3.model.DeviceGridState
30import com.android.launcher3.pm.InstallSessionHelper
31import com.android.launcher3.provider.RestoreDbTask
Charlie Anderson511421c2023-10-26 11:22:26 -040032import com.android.launcher3.provider.RestoreDbTask.FIRST_LOAD_AFTER_RESTORE_KEY
Sebastian Franco9e4c99b2024-10-02 15:16:37 -070033import com.android.launcher3.settings.SettingsActivity
Stefan Andonian1d7f7032023-01-23 21:55:04 +000034import com.android.launcher3.states.RotationHelper
Sunny Goyale79d4532024-12-31 00:10:20 -080035import com.android.launcher3.util.DaggerSingletonObject
Stefan Andonian1d7f7032023-01-23 21:55:04 +000036import com.android.launcher3.util.DisplayController
Sunny Goyalc9d9e332025-02-05 13:49:04 -080037import java.util.concurrent.ConcurrentHashMap
Sunny Goyale79d4532024-12-31 00:10:20 -080038import javax.inject.Inject
Stefan Andonian146701c2022-11-10 23:07:40 +000039
Stefan Andoniand1b33b32022-12-16 21:22:27 +000040/**
Brian Isganitis9cbc4782024-10-08 14:47:17 -040041 * Manages Launcher [SharedPreferences] through [Item] instances.
Jordan Demeulenaerebe82bc62023-03-01 09:11:48 +000042 *
Stefan Andoniand1b33b32022-12-16 21:22:27 +000043 * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
44 */
Sunny Goyale79d4532024-12-31 00:10:20 -080045@LauncherAppSingleton
46open class LauncherPrefs
47@Inject
48constructor(@ApplicationContext private val encryptedContext: Context) {
49
50 private val deviceProtectedSharedPrefs: SharedPreferences by lazy {
51 encryptedContext
52 .createDeviceProtectedStorageContext()
53 .getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
54 }
55
Sunny Goyalc9d9e332025-02-05 13:49:04 -080056 open protected fun getSharedPrefs(item: Item): SharedPreferences =
57 item.run {
Sunny Goyale79d4532024-12-31 00:10:20 -080058 if (encryptionType == EncryptionType.DEVICE_PROTECTED) deviceProtectedSharedPrefs
59 else encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
Sunny Goyalc9d9e332025-02-05 13:49:04 -080060 }
Brian Isganitis9cbc4782024-10-08 14:47:17 -040061
62 /** Returns the value with type [T] for [item]. */
Sunny Goyalc9d9e332025-02-05 13:49:04 -080063 fun <T> get(item: ContextualItem<T>): T =
Sunny Goyale79d4532024-12-31 00:10:20 -080064 getInner(item, item.defaultValueFromContext(encryptedContext))
Brian Isganitis9cbc4782024-10-08 14:47:17 -040065
66 /** Returns the value with type [T] for [item]. */
Sunny Goyalc9d9e332025-02-05 13:49:04 -080067 fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
Brian Isganitis9cbc4782024-10-08 14:47:17 -040068
Sunny Goyale79d4532024-12-31 00:10:20 -080069 /**
70 * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
71 * default value type, and will throw an error if the type of the item provided is not a
72 * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
73 */
74 @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
75 private fun <T> getInner(item: Item, default: T): T {
Sunny Goyalc9d9e332025-02-05 13:49:04 -080076 val sp = getSharedPrefs(item)
Brian Isganitis9cbc4782024-10-08 14:47:17 -040077
Sunny Goyale79d4532024-12-31 00:10:20 -080078 return when (item.type) {
79 String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
80 Boolean::class.java,
81 java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
82 Int::class.java,
83 java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int)
84 Float::class.java,
85 java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
86 Long::class.java,
87 java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long)
88 Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
89 else ->
90 throw IllegalArgumentException(
91 "item type: ${item.type}" + " is not compatible with sharedPref methods"
92 )
93 }
94 as T
95 }
Brian Isganitis9cbc4782024-10-08 14:47:17 -040096
Sunny Goyale79d4532024-12-31 00:10:20 -080097 /**
98 * Stores each of the values provided in `SharedPreferences` according to the configuration
99 * contained within the associated items provided. Internally, it uses apply, so the caller
100 * cannot assume that the values that have been put are immediately available for use.
101 *
102 * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from
103 * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
104 * provided item configurations.
105 */
106 fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
107 prepareToPutValues(itemsToValues).forEach { it.apply() }
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400108
Sunny Goyale79d4532024-12-31 00:10:20 -0800109 /** See referenced `put` method above. */
110 fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400111
Sunny Goyale79d4532024-12-31 00:10:20 -0800112 /**
113 * Synchronously stores all the values provided according to their associated Item
114 * configuration.
115 */
116 fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
117 prepareToPutValues(itemsToValues).forEach { it.commit() }
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400118
Sunny Goyale79d4532024-12-31 00:10:20 -0800119 /**
120 * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If
121 * the item is boot aware, this method updates both the boot aware and the encrypted files. This
122 * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs
123 * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which
124 * already points to encrypted storage.
125 *
126 * Returns a list of editors with all transactions added so that the caller can determine to use
127 * .apply() or .commit()
128 */
129 private fun prepareToPutValues(
130 updates: Array<out Pair<Item, Any>>
131 ): List<SharedPreferences.Editor> {
Sunny Goyalc9d9e332025-02-05 13:49:04 -0800132 val updatesPerPrefFile = updates.groupBy { getSharedPrefs(it.first) }.toMap()
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400133
Sunny Goyale79d4532024-12-31 00:10:20 -0800134 return updatesPerPrefFile.map { (sharedPref, itemList) ->
135 sharedPref.edit().apply { itemList.forEach { (item, value) -> putValue(item, value) } }
136 }
137 }
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400138
Sunny Goyale79d4532024-12-31 00:10:20 -0800139 /**
140 * Handles adding values to `SharedPreferences` regardless of type. This method is especially
141 * helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple
142 * types of Item values.
143 */
144 @Suppress("UNCHECKED_CAST")
Sunny Goyalc9d9e332025-02-05 13:49:04 -0800145 internal fun SharedPreferences.Editor.putValue(
Sunny Goyale79d4532024-12-31 00:10:20 -0800146 item: Item,
147 value: Any?,
148 ): SharedPreferences.Editor =
149 when (item.type) {
150 String::class.java -> putString(item.sharedPrefKey, value as? String)
151 Boolean::class.java,
152 java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
153 Int::class.java,
154 java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int)
155 Float::class.java,
156 java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
157 Long::class.java,
158 java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
159 Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>)
160 else ->
161 throw IllegalArgumentException(
162 "item type: ${item.type} is not compatible with sharedPref methods"
163 )
164 }
165
166 /**
167 * After calling this method, the listener will be notified of any future updates to the
168 * `SharedPreferences` files associated with the provided list of items. The listener will need
169 * to filter update notifications so they don't activate for non-relevant updates.
170 */
171 fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
172 items
Sunny Goyalc9d9e332025-02-05 13:49:04 -0800173 .map { getSharedPrefs(it) }
Sunny Goyale79d4532024-12-31 00:10:20 -0800174 .distinct()
175 .forEach { it.registerOnSharedPreferenceChangeListener(listener) }
176 }
177
178 /**
179 * Stops the listener from getting notified of any more updates to any of the
180 * `SharedPreferences` files associated with any of the provided list of [Item].
181 */
182 fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
183 // If a listener is not registered to a SharedPreference, unregistering it does nothing
184 items
Sunny Goyalc9d9e332025-02-05 13:49:04 -0800185 .map { getSharedPrefs(it) }
Sunny Goyale79d4532024-12-31 00:10:20 -0800186 .distinct()
187 .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
188 }
189
190 /**
191 * Checks if all the provided [Item] have values stored in their corresponding
192 * `SharedPreferences` files.
193 */
194 fun has(vararg items: Item): Boolean {
195 items
Sunny Goyalc9d9e332025-02-05 13:49:04 -0800196 .groupBy { getSharedPrefs(it) }
Sunny Goyale79d4532024-12-31 00:10:20 -0800197 .forEach { (prefs, itemsSublist) ->
198 if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
199 }
200 return true
201 }
202
203 /**
204 * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
205 */
206 fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
207
208 /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
209 fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
210
211 /**
212 * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
213 * item is boot aware, this method removes the data from both the boot aware and encrypted
214 * files.
215 *
216 * @return a list of editors with all transactions added so that the caller can determine to use
217 * .apply() or .commit()
218 */
219 private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
Sunny Goyalc9d9e332025-02-05 13:49:04 -0800220 val itemsPerFile = items.groupBy { getSharedPrefs(it) }.toMap()
Sunny Goyale79d4532024-12-31 00:10:20 -0800221
222 return itemsPerFile.map { (prefs, items) ->
223 prefs.edit().also { editor ->
224 items.forEach { item -> editor.remove(item.sharedPrefKey) }
225 }
226 }
227 }
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400228
229 companion object {
230 @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
231
Sunny Goyale79d4532024-12-31 00:10:20 -0800232 @JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getLauncherPrefs)
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400233
234 @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
235
236 const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
237 const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY"
238 const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400239
240 @JvmField
241 val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400242 @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
243 @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
244 @JvmField
245 val WORKSPACE_SIZE =
246 backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED)
247 @JvmField
248 val HOTSEAT_COUNT =
249 backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED)
250 @JvmField
251 val TASKBAR_PINNING =
252 backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
253 @JvmField
254 val TASKBAR_PINNING_IN_DESKTOP_MODE =
255 backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED)
256
257 @JvmField
258 val DEVICE_TYPE =
259 backedUpItem(
260 DeviceGridState.KEY_DEVICE_TYPE,
261 InvariantDeviceProfile.TYPE_PHONE,
262 EncryptionType.ENCRYPTED,
263 )
264 @JvmField
265 val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED)
266 @JvmField
267 val SHOULD_SHOW_SMARTSPACE =
268 backedUpItem(
269 SHOULD_SHOW_SMARTSPACE_KEY,
270 WIDGET_ON_FIRST_SCREEN,
271 EncryptionType.DEVICE_PROTECTED,
272 )
273 @JvmField
274 val RESTORE_DEVICE =
275 backedUpItem(
276 RestoreDbTask.RESTORED_DEVICE_TYPE,
277 InvariantDeviceProfile.TYPE_PHONE,
278 EncryptionType.ENCRYPTED,
279 )
280 @JvmField
Stefan Andonian8c0a7872024-10-21 14:26:27 -0700281 val NO_DB_FILES_RESTORED =
282 nonRestorableItem("no_db_files_restored", false, EncryptionType.DEVICE_PROTECTED)
283 @JvmField
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400284 val IS_FIRST_LOAD_AFTER_RESTORE =
285 nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED)
286 @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
287 @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
Sebastian Franco9e4c99b2024-10-02 15:16:37 -0700288
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400289 @JvmField
290 val GRID_NAME =
291 ConstantItem(
Charlie Andersonbad2be42024-12-06 16:32:03 -0500292 GRID_NAME_PREFS_KEY,
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400293 isBackedUp = true,
294 defaultValue = null,
295 encryptionType = EncryptionType.ENCRYPTED,
296 type = String::class.java,
297 )
298 @JvmField
299 val ALLOW_ROTATION =
300 backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
301 RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
302 }
303
Sebastian Franco9e4c99b2024-10-02 15:16:37 -0700304 @JvmField
305 val FIXED_LANDSCAPE_MODE = backedUpItem(SettingsActivity.FIXED_LANDSCAPE_MODE, false)
306
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400307 // Preferences for widget configurations
308 @JvmField
309 val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
310 backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false)
311
312 @JvmStatic
313 fun <T> backedUpItem(
314 sharedPrefKey: String,
315 defaultValue: T,
316 encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
317 ): ConstantItem<T> =
318 ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType)
319
320 @JvmStatic
321 fun <T> backedUpItem(
322 sharedPrefKey: String,
323 type: Class<out T>,
324 encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
325 defaultValueFromContext: (c: Context) -> T,
326 ): ContextualItem<T> =
327 ContextualItem(
328 sharedPrefKey,
329 isBackedUp = true,
330 defaultValueFromContext,
331 encryptionType,
332 type,
333 )
334
335 @JvmStatic
336 fun <T> nonRestorableItem(
337 sharedPrefKey: String,
338 defaultValue: T,
339 encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
340 ): ConstantItem<T> =
341 ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType)
342
343 @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
344 @JvmStatic
345 fun getPrefs(context: Context): SharedPreferences {
346 // Use application context for shared preferences, so we use single cached instance
347 return context.applicationContext.getSharedPreferences(
348 SHARED_PREFERENCES_KEY,
349 MODE_PRIVATE,
350 )
351 }
352
353 @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
354 @JvmStatic
355 fun getDevicePrefs(context: Context): SharedPreferences {
356 // Use application context for shared preferences, so we use a single cached instance
357 return context.applicationContext.getSharedPreferences(
358 DEVICE_PREFERENCES_KEY,
359 MODE_PRIVATE,
360 )
361 }
362 }
363}
364
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000365abstract class Item {
366 abstract val sharedPrefKey: String
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000367 abstract val isBackedUp: Boolean
368 abstract val type: Class<*>
Stefan Andonian54495f32023-09-29 23:41:26 +0000369 abstract val encryptionType: EncryptionType
Stefan Andonian246eeaa2023-02-21 22:14:47 +0000370 val sharedPrefFile: String
371 get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000372
373 fun <T> to(value: T): Pair<Item, T> = Pair(this, value)
374}
375
376data class ConstantItem<T>(
377 override val sharedPrefKey: String,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000378 override val isBackedUp: Boolean,
379 val defaultValue: T,
Stefan Andonian54495f32023-09-29 23:41:26 +0000380 override val encryptionType: EncryptionType,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000381 // The default value can be null. If so, the type needs to be explicitly stated, or else NPE
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400382 override val type: Class<out T> = defaultValue!!::class.java,
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000383) : Item() {
Sunny Goyalcd447402023-10-06 13:47:50 -0700384
385 fun get(c: Context): T = LauncherPrefs.get(c).get(this)
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000386}
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000387
388data class ContextualItem<T>(
389 override val sharedPrefKey: String,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000390 override val isBackedUp: Boolean,
391 private val defaultSupplier: (c: Context) -> T,
Stefan Andonian54495f32023-09-29 23:41:26 +0000392 override val encryptionType: EncryptionType,
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400393 override val type: Class<out T>,
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000394) : Item() {
395 private var default: T? = null
396
397 fun defaultValueFromContext(context: Context): T {
398 if (default == null) {
399 default = defaultSupplier(context)
400 }
401 return default!!
402 }
Sunny Goyalcd447402023-10-06 13:47:50 -0700403
404 fun get(c: Context): T = LauncherPrefs.get(c).get(this)
Thales Lima03ac3772023-01-06 15:16:41 +0000405}
Stefan Andonian54495f32023-09-29 23:41:26 +0000406
407enum class EncryptionType {
408 ENCRYPTED,
409 DEVICE_PROTECTED,
Stefan Andonian54495f32023-09-29 23:41:26 +0000410}
Sunny Goyal4c261f72025-01-27 23:30:06 -0800411
412/**
413 * LauncherPrefs which delegates all lookup to [prefs] but uses the real prefs for initial values
414 */
415class ProxyPrefs(context: Context, private val prefs: SharedPreferences) : LauncherPrefs(context) {
416
Sunny Goyalc9d9e332025-02-05 13:49:04 -0800417 private val copiedPrefs = ConcurrentHashMap<SharedPreferences, Boolean>()
Sunny Goyal4c261f72025-01-27 23:30:06 -0800418
Sunny Goyalc9d9e332025-02-05 13:49:04 -0800419 override fun getSharedPrefs(item: Item): SharedPreferences {
420 val originalPrefs = super.getSharedPrefs(item)
421 // Copy all existing values, when the pref is accessed for the first time
422 copiedPrefs.computeIfAbsent(originalPrefs) { op ->
423 val editor = prefs.edit()
424 op.all.forEach { (key, value) ->
425 if (value != null) {
426 editor.putValue(backedUpItem(key, value), value)
427 }
428 }
429 editor.commit()
430 }
431 return prefs
432 }
Sunny Goyal4c261f72025-01-27 23:30:06 -0800433}