blob: 5b9c2fa3a41860c39441ea1fa8e0c326add4612f [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
Stefan Andoniand1b33b32022-12-16 21:22:27 +000026import com.android.launcher3.model.DeviceGridState
27import com.android.launcher3.pm.InstallSessionHelper
28import com.android.launcher3.provider.RestoreDbTask
Charlie Anderson511421c2023-10-26 11:22:26 -040029import com.android.launcher3.provider.RestoreDbTask.FIRST_LOAD_AFTER_RESTORE_KEY
Sebastian Franco9e4c99b2024-10-02 15:16:37 -070030import com.android.launcher3.settings.SettingsActivity
Stefan Andonian1d7f7032023-01-23 21:55:04 +000031import com.android.launcher3.states.RotationHelper
32import com.android.launcher3.util.DisplayController
Stefan Andoniand1b33b32022-12-16 21:22:27 +000033import com.android.launcher3.util.MainThreadInitializedObject
Sunny Goyal10fa0162024-04-21 00:13:35 -070034import com.android.launcher3.util.SafeCloseable
Stefan Andoniand1b33b32022-12-16 21:22:27 +000035import com.android.launcher3.util.Themes
Stefan Andonian146701c2022-11-10 23:07:40 +000036
Stefan Andoniand1b33b32022-12-16 21:22:27 +000037/**
Brian Isganitis9cbc4782024-10-08 14:47:17 -040038 * Manages Launcher [SharedPreferences] through [Item] instances.
Jordan Demeulenaerebe82bc62023-03-01 09:11:48 +000039 *
Stefan Andoniand1b33b32022-12-16 21:22:27 +000040 * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
41 */
Brian Isganitis9cbc4782024-10-08 14:47:17 -040042abstract class LauncherPrefs : SafeCloseable {
43
44 /** Returns the value with type [T] for [item]. */
45 abstract fun <T> get(item: ContextualItem<T>): T
46
47 /** Returns the value with type [T] for [item]. */
48 abstract fun <T> get(item: ConstantItem<T>): T
49
50 /** Stores the values for each item in preferences. */
51 abstract fun put(vararg itemsToValues: Pair<Item, Any>)
52
53 /** Stores the [value] with type [T] for [item] in preferences. */
54 abstract fun <T : Any> put(item: Item, value: T)
55
56 /** Synchronous version of [put]. */
57 abstract fun putSync(vararg itemsToValues: Pair<Item, Any>)
58
59 /** Registers [listener] for [items]. */
60 abstract fun addListener(listener: LauncherPrefChangeListener, vararg items: Item)
61
62 /** Unregisters [listener] for [items]. */
63 abstract fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item)
64
65 /** Returns `true` iff all [items] have a value. */
66 abstract fun has(vararg items: Item): Boolean
67
68 /** Removes the value for each item in [items]. */
69 abstract fun remove(vararg items: Item)
70
71 /** Synchronous version of [remove]. */
72 abstract fun removeSync(vararg items: Item)
73
74 companion object {
75 @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
76
77 @JvmField
78 var INSTANCE = MainThreadInitializedObject<LauncherPrefs> { LauncherPrefsImpl(it) }
79
80 @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
81
82 const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
83 const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY"
84 const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
85 @JvmField
86 val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED)
87
88 @JvmField
89 val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
90 @JvmField
91 val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED)
92 @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
93 @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
94 @JvmField
95 val WORKSPACE_SIZE =
96 backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED)
97 @JvmField
98 val HOTSEAT_COUNT =
99 backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED)
100 @JvmField
101 val TASKBAR_PINNING =
102 backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
103 @JvmField
104 val TASKBAR_PINNING_IN_DESKTOP_MODE =
105 backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED)
106
107 @JvmField
108 val DEVICE_TYPE =
109 backedUpItem(
110 DeviceGridState.KEY_DEVICE_TYPE,
111 InvariantDeviceProfile.TYPE_PHONE,
112 EncryptionType.ENCRYPTED,
113 )
114 @JvmField
115 val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED)
116 @JvmField
117 val SHOULD_SHOW_SMARTSPACE =
118 backedUpItem(
119 SHOULD_SHOW_SMARTSPACE_KEY,
120 WIDGET_ON_FIRST_SCREEN,
121 EncryptionType.DEVICE_PROTECTED,
122 )
123 @JvmField
124 val RESTORE_DEVICE =
125 backedUpItem(
126 RestoreDbTask.RESTORED_DEVICE_TYPE,
127 InvariantDeviceProfile.TYPE_PHONE,
128 EncryptionType.ENCRYPTED,
129 )
130 @JvmField
Stefan Andonian8c0a7872024-10-21 14:26:27 -0700131 val NO_DB_FILES_RESTORED =
132 nonRestorableItem("no_db_files_restored", false, EncryptionType.DEVICE_PROTECTED)
133 @JvmField
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400134 val IS_FIRST_LOAD_AFTER_RESTORE =
135 nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED)
136 @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
137 @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
Sebastian Franco9e4c99b2024-10-02 15:16:37 -0700138
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400139 @JvmField
140 val GRID_NAME =
141 ConstantItem(
Charlie Andersonbad2be42024-12-06 16:32:03 -0500142 GRID_NAME_PREFS_KEY,
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400143 isBackedUp = true,
144 defaultValue = null,
145 encryptionType = EncryptionType.ENCRYPTED,
146 type = String::class.java,
147 )
148 @JvmField
149 val ALLOW_ROTATION =
150 backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
151 RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
152 }
153
Sebastian Franco9e4c99b2024-10-02 15:16:37 -0700154 @JvmField
155 val FIXED_LANDSCAPE_MODE = backedUpItem(SettingsActivity.FIXED_LANDSCAPE_MODE, false)
156
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400157 // Preferences for widget configurations
158 @JvmField
159 val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
160 backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false)
161
162 @JvmStatic
163 fun <T> backedUpItem(
164 sharedPrefKey: String,
165 defaultValue: T,
166 encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
167 ): ConstantItem<T> =
168 ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType)
169
170 @JvmStatic
171 fun <T> backedUpItem(
172 sharedPrefKey: String,
173 type: Class<out T>,
174 encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
175 defaultValueFromContext: (c: Context) -> T,
176 ): ContextualItem<T> =
177 ContextualItem(
178 sharedPrefKey,
179 isBackedUp = true,
180 defaultValueFromContext,
181 encryptionType,
182 type,
183 )
184
185 @JvmStatic
186 fun <T> nonRestorableItem(
187 sharedPrefKey: String,
188 defaultValue: T,
189 encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
190 ): ConstantItem<T> =
191 ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType)
192
193 @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
194 @JvmStatic
195 fun getPrefs(context: Context): SharedPreferences {
196 // Use application context for shared preferences, so we use single cached instance
197 return context.applicationContext.getSharedPreferences(
198 SHARED_PREFERENCES_KEY,
199 MODE_PRIVATE,
200 )
201 }
202
203 @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
204 @JvmStatic
205 fun getDevicePrefs(context: Context): SharedPreferences {
206 // Use application context for shared preferences, so we use a single cached instance
207 return context.applicationContext.getSharedPreferences(
208 DEVICE_PREFERENCES_KEY,
209 MODE_PRIVATE,
210 )
211 }
212 }
213}
214
215private class LauncherPrefsImpl(private val encryptedContext: Context) : LauncherPrefs() {
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000216 private val deviceProtectedStorageContext =
217 encryptedContext.createDeviceProtectedStorageContext()
218
219 private val bootAwarePrefs
220 get() =
221 deviceProtectedStorageContext.getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
222
223 private val Item.encryptedPrefs
224 get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
225
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000226 private fun chooseSharedPreferences(item: Item): SharedPreferences =
Stefan Andonian1a626d22024-04-03 18:36:16 +0000227 if (item.encryptionType == EncryptionType.DEVICE_PROTECTED) bootAwarePrefs
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000228 else item.encryptedPrefs
Stefan Andonian146701c2022-11-10 23:07:40 +0000229
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000230 /** Wrapper around `getInner` for a `ContextualItem` */
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400231 override fun <T> get(item: ContextualItem<T>): T =
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000232 getInner(item, item.defaultValueFromContext(encryptedContext))
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000233
234 /** Wrapper around `getInner` for an `Item` */
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400235 override fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000236
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000237 /**
238 * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
239 * default value type, and will throw an error if the type of the item provided is not a
240 * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
241 */
242 @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000243 private fun <T> getInner(item: Item, default: T): T {
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000244 val sp = chooseSharedPreferences(item)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000245
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000246 return when (item.type) {
247 String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000248 Boolean::class.java,
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000249 java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000250 Int::class.java,
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000251 java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000252 Float::class.java,
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000253 java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000254 Long::class.java,
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000255 java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long)
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000256 Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000257 else ->
258 throw IllegalArgumentException(
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000259 "item type: ${item.type}" + " is not compatible with sharedPref methods"
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000260 )
261 }
262 as T
Stefan Andonian146701c2022-11-10 23:07:40 +0000263 }
264
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000265 /**
266 * Stores each of the values provided in `SharedPreferences` according to the configuration
267 * contained within the associated items provided. Internally, it uses apply, so the caller
268 * cannot assume that the values that have been put are immediately available for use.
269 *
270 * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from
271 * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
272 * provided item configurations.
273 */
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400274 override fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000275 prepareToPutValues(itemsToValues).forEach { it.apply() }
276
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000277 /** See referenced `put` method above. */
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400278 override fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000279
280 /**
281 * Synchronously stores all the values provided according to their associated Item
282 * configuration.
283 */
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400284 override fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000285 prepareToPutValues(itemsToValues).forEach { it.commit() }
286
287 /**
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000288 * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If
289 * the item is boot aware, this method updates both the boot aware and the encrypted files. This
290 * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs
291 * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which
292 * already points to encrypted storage.
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000293 *
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000294 * Returns a list of editors with all transactions added so that the caller can determine to use
295 * .apply() or .commit()
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000296 */
297 private fun prepareToPutValues(
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000298 updates: Array<out Pair<Item, Any>>
299 ): List<SharedPreferences.Editor> {
Stefan Andonian54495f32023-09-29 23:41:26 +0000300 val updatesPerPrefFile =
301 updates
302 .filter { it.first.encryptionType != EncryptionType.DEVICE_PROTECTED }
303 .groupBy { it.first.encryptedPrefs }
304 .toMutableMap()
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000305
Stefan Andonian54495f32023-09-29 23:41:26 +0000306 val bootAwareUpdates =
Stefan Andonian1a626d22024-04-03 18:36:16 +0000307 updates.filter { it.first.encryptionType == EncryptionType.DEVICE_PROTECTED }
Stefan Andonian54495f32023-09-29 23:41:26 +0000308 if (bootAwareUpdates.isNotEmpty()) {
309 updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000310 }
311
312 return updatesPerPrefFile.map { prefToItemValueList ->
313 prefToItemValueList.key.edit().apply {
314 prefToItemValueList.value.forEach { itemToValue: Pair<Item, Any> ->
315 putValue(itemToValue.first, itemToValue.second)
316 }
317 }
318 }
319 }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000320
321 /**
322 * Handles adding values to `SharedPreferences` regardless of type. This method is especially
323 * helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple
324 * types of Item values.
325 */
326 @Suppress("UNCHECKED_CAST")
327 private fun SharedPreferences.Editor.putValue(
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000328 item: Item,
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400329 value: Any?,
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000330 ): SharedPreferences.Editor =
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000331 when (item.type) {
332 String::class.java -> putString(item.sharedPrefKey, value as? String)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000333 Boolean::class.java,
334 java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
335 Int::class.java,
336 java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int)
337 Float::class.java,
338 java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
339 Long::class.java,
340 java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000341 Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000342 else ->
343 throw IllegalArgumentException(
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000344 "item type: ${item.type} is not compatible with sharedPref methods"
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000345 )
346 }
347
348 /**
349 * After calling this method, the listener will be notified of any future updates to the
350 * `SharedPreferences` files associated with the provided list of items. The listener will need
351 * to filter update notifications so they don't activate for non-relevant updates.
352 */
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400353 override fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000354 items
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000355 .map { chooseSharedPreferences(it) }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000356 .distinct()
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000357 .forEach { it.registerOnSharedPreferenceChangeListener(listener) }
Thales Lima03ac3772023-01-06 15:16:41 +0000358 }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000359
360 /**
361 * Stops the listener from getting notified of any more updates to any of the
362 * `SharedPreferences` files associated with any of the provided list of [Item].
363 */
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400364 override fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000365 // If a listener is not registered to a SharedPreference, unregistering it does nothing
366 items
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000367 .map { chooseSharedPreferences(it) }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000368 .distinct()
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000369 .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000370 }
371
372 /**
373 * Checks if all the provided [Item] have values stored in their corresponding
374 * `SharedPreferences` files.
375 */
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400376 override fun has(vararg items: Item): Boolean {
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000377 items
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000378 .groupBy { chooseSharedPreferences(it) }
379 .forEach { (prefs, itemsSublist) ->
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000380 if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
381 }
382 return true
383 }
384
385 /**
386 * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
387 */
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400388 override fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000389
390 /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400391 override fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000392
393 /**
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000394 * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
395 * item is boot aware, this method removes the data from both the boot aware and encrypted
396 * files.
397 *
398 * @return a list of editors with all transactions added so that the caller can determine to use
Stefan Andonian956b88e2023-03-20 20:38:46 +0000399 * .apply() or .commit()
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000400 */
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000401 private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
Stefan Andonian54495f32023-09-29 23:41:26 +0000402 val itemsPerFile =
403 items
404 .filter { it.encryptionType != EncryptionType.DEVICE_PROTECTED }
405 .groupBy { it.encryptedPrefs }
406 .toMutableMap()
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000407
Stefan Andonian1a626d22024-04-03 18:36:16 +0000408 val bootAwareUpdates = items.filter { it.encryptionType == EncryptionType.DEVICE_PROTECTED }
Stefan Andonian54495f32023-09-29 23:41:26 +0000409 if (bootAwareUpdates.isNotEmpty()) {
410 itemsPerFile[bootAwarePrefs] = bootAwareUpdates
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000411 }
412
413 return itemsPerFile.map { (prefs, items) ->
414 prefs.edit().also { editor ->
415 items.forEach { item -> editor.remove(item.sharedPrefKey) }
416 }
417 }
418 }
419
Sunny Goyal10fa0162024-04-21 00:13:35 -0700420 override fun close() {}
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000421}
422
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000423abstract class Item {
424 abstract val sharedPrefKey: String
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000425 abstract val isBackedUp: Boolean
426 abstract val type: Class<*>
Stefan Andonian54495f32023-09-29 23:41:26 +0000427 abstract val encryptionType: EncryptionType
Stefan Andonian246eeaa2023-02-21 22:14:47 +0000428 val sharedPrefFile: String
429 get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000430
431 fun <T> to(value: T): Pair<Item, T> = Pair(this, value)
432}
433
434data class ConstantItem<T>(
435 override val sharedPrefKey: String,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000436 override val isBackedUp: Boolean,
437 val defaultValue: T,
Stefan Andonian54495f32023-09-29 23:41:26 +0000438 override val encryptionType: EncryptionType,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000439 // 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 -0400440 override val type: Class<out T> = defaultValue!!::class.java,
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000441) : Item() {
Sunny Goyalcd447402023-10-06 13:47:50 -0700442
443 fun get(c: Context): T = LauncherPrefs.get(c).get(this)
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000444}
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000445
446data class ContextualItem<T>(
447 override val sharedPrefKey: String,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000448 override val isBackedUp: Boolean,
449 private val defaultSupplier: (c: Context) -> T,
Stefan Andonian54495f32023-09-29 23:41:26 +0000450 override val encryptionType: EncryptionType,
Brian Isganitis9cbc4782024-10-08 14:47:17 -0400451 override val type: Class<out T>,
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000452) : Item() {
453 private var default: T? = null
454
455 fun defaultValueFromContext(context: Context): T {
456 if (default == null) {
457 default = defaultSupplier(context)
458 }
459 return default!!
460 }
Sunny Goyalcd447402023-10-06 13:47:50 -0700461
462 fun get(c: Context): T = LauncherPrefs.get(c).get(this)
Thales Lima03ac3772023-01-06 15:16:41 +0000463}
Stefan Andonian54495f32023-09-29 23:41:26 +0000464
465enum class EncryptionType {
466 ENCRYPTED,
467 DEVICE_PROTECTED,
Stefan Andonian54495f32023-09-29 23:41:26 +0000468}