blob: fb58cbe855ccb4dba63732b19a7b14c86635d526 [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
fbarone58aaf12023-09-25 11:34:56 -070024import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
Stefan Andonian4c9612b2023-02-22 00:00:03 +000025import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
26import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
Andreas Agvardb36bcff2023-11-10 15:16:55 +010027import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_END_SCALE_PERCENT
28import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_ITERATIONS
29import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_SCALE_EXPONENT
30import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_START_SCALE_PERCENT
Anushree Ganjamcb571da2023-10-24 14:47:09 -070031import com.android.launcher3.config.FeatureFlags.LPNH_SLOP_PERCENTAGE
32import com.android.launcher3.config.FeatureFlags.LPNH_TIMEOUT_MS
Stefan Andoniand1b33b32022-12-16 21:22:27 +000033import com.android.launcher3.model.DeviceGridState
34import com.android.launcher3.pm.InstallSessionHelper
35import com.android.launcher3.provider.RestoreDbTask
Stefan Andonian1d7f7032023-01-23 21:55:04 +000036import com.android.launcher3.states.RotationHelper
37import com.android.launcher3.util.DisplayController
Stefan Andoniand1b33b32022-12-16 21:22:27 +000038import com.android.launcher3.util.MainThreadInitializedObject
39import com.android.launcher3.util.Themes
Stefan Andonian146701c2022-11-10 23:07:40 +000040
Stefan Andoniand1b33b32022-12-16 21:22:27 +000041/**
42 * Use same context for shared preferences, so that we use a single cached instance
Jordan Demeulenaerebe82bc62023-03-01 09:11:48 +000043 *
Stefan Andoniand1b33b32022-12-16 21:22:27 +000044 * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
Stefan Andonian956b88e2023-03-20 20:38:46 +000045 * TODO(b/274501660): Fix ReorderWidgets#simpleReorder test before enabling
46 * isBootAwareStartupDataEnabled
Stefan Andoniand1b33b32022-12-16 21:22:27 +000047 */
Stefan Andonian5bd9a222023-02-23 00:58:33 +000048class LauncherPrefs(private val encryptedContext: Context) {
49 private val deviceProtectedStorageContext =
50 encryptedContext.createDeviceProtectedStorageContext()
51
52 private val bootAwarePrefs
53 get() =
54 deviceProtectedStorageContext.getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
55
56 private val Item.encryptedPrefs
57 get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
58
59 // This call to `SharedPreferences` needs to be explicit rather than using `get` since doing so
60 // would result in a circular dependency between `isStartupDataMigrated` and `choosePreferences`
61 val isStartupDataMigrated: Boolean
62 get() =
63 bootAwarePrefs.getBoolean(
64 IS_STARTUP_DATA_MIGRATED.sharedPrefKey,
65 IS_STARTUP_DATA_MIGRATED.defaultValue
66 )
67
68 private fun chooseSharedPreferences(item: Item): SharedPreferences =
Stefan Andonian939d5f82023-09-25 18:07:43 +000069 if (
Stefan Andonian54495f32023-09-29 23:41:26 +000070 (moveStartupDataToDeviceProtectedStorageIsEnabled &&
71 item.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
72 isStartupDataMigrated) || item.encryptionType == EncryptionType.DEVICE_PROTECTED
Stefan Andonian939d5f82023-09-25 18:07:43 +000073 )
Stefan Andonian5bd9a222023-02-23 00:58:33 +000074 bootAwarePrefs
75 else item.encryptedPrefs
Stefan Andonian146701c2022-11-10 23:07:40 +000076
Stefan Andonian1d7f7032023-01-23 21:55:04 +000077 /** Wrapper around `getInner` for a `ContextualItem` */
Stefan Andonian5bd9a222023-02-23 00:58:33 +000078 fun <T> get(item: ContextualItem<T>): T =
79 getInner(item, item.defaultValueFromContext(encryptedContext))
Stefan Andonian1d7f7032023-01-23 21:55:04 +000080
81 /** Wrapper around `getInner` for an `Item` */
Stefan Andonian4c9612b2023-02-22 00:00:03 +000082 fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
Stefan Andonian1d7f7032023-01-23 21:55:04 +000083
Stefan Andoniand1b33b32022-12-16 21:22:27 +000084 /**
85 * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
86 * default value type, and will throw an error if the type of the item provided is not a
87 * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
88 */
89 @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
Stefan Andonian4c9612b2023-02-22 00:00:03 +000090 private fun <T> getInner(item: Item, default: T): T {
Stefan Andonian5bd9a222023-02-23 00:58:33 +000091 val sp = chooseSharedPreferences(item)
Stefan Andoniand1b33b32022-12-16 21:22:27 +000092
Stefan Andonian4c9612b2023-02-22 00:00:03 +000093 return when (item.type) {
94 String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
Stefan Andoniand1b33b32022-12-16 21:22:27 +000095 Boolean::class.java,
Stefan Andonian1d7f7032023-01-23 21:55:04 +000096 java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
Stefan Andoniand1b33b32022-12-16 21:22:27 +000097 Int::class.java,
Stefan Andonian1d7f7032023-01-23 21:55:04 +000098 java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int)
Stefan Andoniand1b33b32022-12-16 21:22:27 +000099 Float::class.java,
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000100 java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000101 Long::class.java,
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000102 java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long)
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000103 Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000104 else ->
105 throw IllegalArgumentException(
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000106 "item type: ${item.type}" + " is not compatible with sharedPref methods"
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000107 )
108 }
109 as T
Stefan Andonian146701c2022-11-10 23:07:40 +0000110 }
111
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000112 /**
113 * Stores each of the values provided in `SharedPreferences` according to the configuration
114 * contained within the associated items provided. Internally, it uses apply, so the caller
115 * cannot assume that the values that have been put are immediately available for use.
116 *
117 * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from
118 * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
119 * provided item configurations.
120 */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000121 fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000122 prepareToPutValues(itemsToValues).forEach { it.apply() }
123
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000124 /** See referenced `put` method above. */
125 fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000126
127 /**
128 * Synchronously stores all the values provided according to their associated Item
129 * configuration.
130 */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000131 fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000132 prepareToPutValues(itemsToValues).forEach { it.commit() }
133
134 /**
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000135 * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If
136 * the item is boot aware, this method updates both the boot aware and the encrypted files. This
137 * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs
138 * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which
139 * already points to encrypted storage.
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000140 *
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000141 * Returns a list of editors with all transactions added so that the caller can determine to use
142 * .apply() or .commit()
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000143 */
144 private fun prepareToPutValues(
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000145 updates: Array<out Pair<Item, Any>>
146 ): List<SharedPreferences.Editor> {
Stefan Andonian54495f32023-09-29 23:41:26 +0000147 val updatesPerPrefFile =
148 updates
149 .filter { it.first.encryptionType != EncryptionType.DEVICE_PROTECTED }
150 .groupBy { it.first.encryptedPrefs }
151 .toMutableMap()
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000152
Stefan Andonian54495f32023-09-29 23:41:26 +0000153 val bootAwareUpdates =
154 updates.filter {
155 (it.first.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
156 moveStartupDataToDeviceProtectedStorageIsEnabled) ||
157 it.first.encryptionType == EncryptionType.DEVICE_PROTECTED
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000158 }
Stefan Andonian54495f32023-09-29 23:41:26 +0000159 if (bootAwareUpdates.isNotEmpty()) {
160 updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000161 }
162
163 return updatesPerPrefFile.map { prefToItemValueList ->
164 prefToItemValueList.key.edit().apply {
165 prefToItemValueList.value.forEach { itemToValue: Pair<Item, Any> ->
166 putValue(itemToValue.first, itemToValue.second)
167 }
168 }
169 }
170 }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000171
172 /**
173 * Handles adding values to `SharedPreferences` regardless of type. This method is especially
174 * helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple
175 * types of Item values.
176 */
177 @Suppress("UNCHECKED_CAST")
178 private fun SharedPreferences.Editor.putValue(
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000179 item: Item,
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000180 value: Any?
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000181 ): SharedPreferences.Editor =
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000182 when (item.type) {
183 String::class.java -> putString(item.sharedPrefKey, value as? String)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000184 Boolean::class.java,
185 java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
186 Int::class.java,
187 java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int)
188 Float::class.java,
189 java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
190 Long::class.java,
191 java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000192 Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000193 else ->
194 throw IllegalArgumentException(
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000195 "item type: ${item.type} is not compatible with sharedPref methods"
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000196 )
197 }
198
199 /**
200 * After calling this method, the listener will be notified of any future updates to the
201 * `SharedPreferences` files associated with the provided list of items. The listener will need
202 * to filter update notifications so they don't activate for non-relevant updates.
203 */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000204 fun addListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000205 items
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000206 .map { chooseSharedPreferences(it) }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000207 .distinct()
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000208 .forEach { it.registerOnSharedPreferenceChangeListener(listener) }
Thales Lima03ac3772023-01-06 15:16:41 +0000209 }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000210
211 /**
212 * Stops the listener from getting notified of any more updates to any of the
213 * `SharedPreferences` files associated with any of the provided list of [Item].
214 */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000215 fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000216 // If a listener is not registered to a SharedPreference, unregistering it does nothing
217 items
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000218 .map { chooseSharedPreferences(it) }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000219 .distinct()
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000220 .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000221 }
222
223 /**
224 * Checks if all the provided [Item] have values stored in their corresponding
225 * `SharedPreferences` files.
226 */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000227 fun has(vararg items: Item): Boolean {
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000228 items
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000229 .groupBy { chooseSharedPreferences(it) }
230 .forEach { (prefs, itemsSublist) ->
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000231 if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
232 }
233 return true
234 }
235
236 /**
237 * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
238 */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000239 fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000240
241 /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000242 fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000243
244 /**
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000245 * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
246 * item is boot aware, this method removes the data from both the boot aware and encrypted
247 * files.
248 *
249 * @return a list of editors with all transactions added so that the caller can determine to use
Stefan Andonian956b88e2023-03-20 20:38:46 +0000250 * .apply() or .commit()
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000251 */
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000252 private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
Stefan Andonian54495f32023-09-29 23:41:26 +0000253 val itemsPerFile =
254 items
255 .filter { it.encryptionType != EncryptionType.DEVICE_PROTECTED }
256 .groupBy { it.encryptedPrefs }
257 .toMutableMap()
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000258
Stefan Andonian54495f32023-09-29 23:41:26 +0000259 val bootAwareUpdates =
260 items.filter {
261 (it.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
262 moveStartupDataToDeviceProtectedStorageIsEnabled) ||
263 it.encryptionType == EncryptionType.DEVICE_PROTECTED
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000264 }
Stefan Andonian54495f32023-09-29 23:41:26 +0000265 if (bootAwareUpdates.isNotEmpty()) {
266 itemsPerFile[bootAwarePrefs] = bootAwareUpdates
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000267 }
268
269 return itemsPerFile.map { (prefs, items) ->
270 prefs.edit().also { editor ->
271 items.forEach { item -> editor.remove(item.sharedPrefKey) }
272 }
273 }
274 }
275
276 fun migrateStartupDataToDeviceProtectedStorage() {
Stefan Andonian54495f32023-09-29 23:41:26 +0000277 if (!moveStartupDataToDeviceProtectedStorageIsEnabled) return
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000278
279 Log.d(
280 TAG,
281 "Migrating data to unencrypted shared preferences to enable preloading " +
282 "while the user is locked the next time the device reboots."
283 )
284
285 with(bootAwarePrefs.edit()) {
Stefan Andonian54495f32023-09-29 23:41:26 +0000286 ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.forEach { putValue(it, get(it)) }
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000287 putBoolean(IS_STARTUP_DATA_MIGRATED.sharedPrefKey, true)
288 apply()
289 }
290 }
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000291
292 companion object {
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000293 private const val TAG = "LauncherPrefs"
294 @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
295
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000296 @JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) }
297
298 @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
299
Jagrut Desaic6d625b2023-04-03 16:57:32 -0700300 const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
fbarone58aaf12023-09-25 11:34:56 -0700301 const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
Stefan Andonian54495f32023-09-29 23:41:26 +0000302 @JvmField
303 val ICON_STATE =
304 nonRestorableItem(
Andreas Agvardd1674982023-10-16 15:49:56 +0200305 "pref_icon_shape_path",
Stefan Andonian54495f32023-09-29 23:41:26 +0000306 "",
307 EncryptionType.MOVE_TO_DEVICE_PROTECTED
308 )
Andy Wickhamaf5bb302023-05-17 14:13:44 -0700309 @JvmField
310 val ALL_APPS_OVERVIEW_THRESHOLD =
Stefan Andonian54495f32023-09-29 23:41:26 +0000311 nonRestorableItem(
Andreas Agvardd1674982023-10-16 15:49:56 +0200312 "pref_all_apps_overview_threshold",
Stefan Andonian54495f32023-09-29 23:41:26 +0000313 180,
314 EncryptionType.MOVE_TO_DEVICE_PROTECTED
315 )
316 @JvmField
Andy Wickham9fbc52f2023-09-22 17:44:23 -0700317 val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
Anushree Ganjamcb571da2023-10-24 14:47:09 -0700318 nonRestorableItem(
319 "pref_long_press_nav_handle_slop_percentage",
320 LPNH_SLOP_PERCENTAGE.get(),
Andreas Agvardd1674982023-10-16 15:49:56 +0200321 EncryptionType.MOVE_TO_DEVICE_PROTECTED
Anushree Ganjamcb571da2023-10-24 14:47:09 -0700322 )
Andy Wickham9fbc52f2023-09-22 17:44:23 -0700323 @JvmField
324 val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
Andreas Agvardd1674982023-10-16 15:49:56 +0200325 nonRestorableItem(
326 "pref_long_press_nav_handle_timeout_ms",
Anushree Ganjamcb571da2023-10-24 14:47:09 -0700327 LPNH_TIMEOUT_MS.get(),
Andreas Agvardd1674982023-10-16 15:49:56 +0200328 EncryptionType.MOVE_TO_DEVICE_PROTECTED
329 )
330 @JvmField
331 val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT =
332 nonRestorableItem(
333 "pref_long_press_nav_handle_haptic_hint_start_scale_percent",
Andreas Agvardb36bcff2023-11-10 15:16:55 +0100334 LPNH_HAPTIC_HINT_START_SCALE_PERCENT.get(),
Andreas Agvardd1674982023-10-16 15:49:56 +0200335 EncryptionType.MOVE_TO_DEVICE_PROTECTED
336 )
337 @JvmField
338 val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT =
339 nonRestorableItem(
340 "pref_long_press_nav_handle_haptic_hint_end_scale_percent",
Andreas Agvardb36bcff2023-11-10 15:16:55 +0100341 LPNH_HAPTIC_HINT_END_SCALE_PERCENT.get(),
Andreas Agvardd1674982023-10-16 15:49:56 +0200342 EncryptionType.MOVE_TO_DEVICE_PROTECTED
343 )
344 @JvmField
345 val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT =
346 nonRestorableItem(
347 "pref_long_press_nav_handle_haptic_hint_scale_exponent",
Andreas Agvardb36bcff2023-11-10 15:16:55 +0100348 LPNH_HAPTIC_HINT_SCALE_EXPONENT.get(),
Andreas Agvardd1674982023-10-16 15:49:56 +0200349 EncryptionType.MOVE_TO_DEVICE_PROTECTED
350 )
351 @JvmField
352 val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS =
353 nonRestorableItem(
354 "pref_long_press_nav_handle_haptic_hint_iterations",
Andreas Agvardb36bcff2023-11-10 15:16:55 +0100355 LPNH_HAPTIC_HINT_ITERATIONS.get(),
Andreas Agvardd1674982023-10-16 15:49:56 +0200356 EncryptionType.MOVE_TO_DEVICE_PROTECTED
357 )
Andy Wickham9fbc52f2023-09-22 17:44:23 -0700358 @JvmField
Stefan Andonian54495f32023-09-29 23:41:26 +0000359 val THEMED_ICONS =
360 backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000361 @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
Sunny Goyal576f7a52023-10-06 11:28:25 -0700362 @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
Stefan Andonian54495f32023-09-29 23:41:26 +0000363 @JvmField
364 val WORKSPACE_SIZE =
365 backedUpItem(
366 DeviceGridState.KEY_WORKSPACE_SIZE,
367 "",
368 EncryptionType.MOVE_TO_DEVICE_PROTECTED
369 )
370 @JvmField
371 val HOTSEAT_COUNT =
372 backedUpItem(
373 DeviceGridState.KEY_HOTSEAT_COUNT,
374 -1,
375 EncryptionType.MOVE_TO_DEVICE_PROTECTED
376 )
377 @JvmField
378 val TASKBAR_PINNING =
379 backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
Jagrut Desaic6d625b2023-04-03 16:57:32 -0700380
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000381 @JvmField
382 val DEVICE_TYPE =
Stefan Andonian54495f32023-09-29 23:41:26 +0000383 backedUpItem(
384 DeviceGridState.KEY_DEVICE_TYPE,
385 InvariantDeviceProfile.TYPE_PHONE,
386 EncryptionType.MOVE_TO_DEVICE_PROTECTED
387 )
388 @JvmField
389 val DB_FILE =
390 backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.MOVE_TO_DEVICE_PROTECTED)
fbarone58aaf12023-09-25 11:34:56 -0700391 @JvmField
392 val SHOULD_SHOW_SMARTSPACE =
Stefan Andonian54495f32023-09-29 23:41:26 +0000393 backedUpItem(
394 SHOULD_SHOW_SMARTSPACE_KEY,
395 WIDGET_ON_FIRST_SCREEN,
396 EncryptionType.DEVICE_PROTECTED
397 )
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000398 @JvmField
399 val RESTORE_DEVICE =
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000400 backedUpItem(
401 RestoreDbTask.RESTORED_DEVICE_TYPE,
402 InvariantDeviceProfile.TYPE_PHONE,
Stefan Andonian54495f32023-09-29 23:41:26 +0000403 EncryptionType.MOVE_TO_DEVICE_PROTECTED
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000404 )
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000405 @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
406 @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000407 @JvmField
408 val GRID_NAME =
409 ConstantItem(
410 "idp_grid_name",
411 isBackedUp = true,
412 defaultValue = null,
Stefan Andonian54495f32023-09-29 23:41:26 +0000413 encryptionType = EncryptionType.MOVE_TO_DEVICE_PROTECTED,
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000414 type = String::class.java
415 )
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000416 @JvmField
417 val ALLOW_ROTATION =
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000418 backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000419 RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
420 }
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000421 @JvmField
422 val IS_STARTUP_DATA_MIGRATED =
423 ConstantItem(
424 "is_startup_data_boot_aware",
425 isBackedUp = false,
426 defaultValue = false,
Stefan Andonian54495f32023-09-29 23:41:26 +0000427 encryptionType = EncryptionType.DEVICE_PROTECTED
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000428 )
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000429
Sunny Goyal576f7a52023-10-06 11:28:25 -0700430 // Preferences for widget configurations
431 @JvmField
432 val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
433 backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false)
434 @JvmField
435 val WIDGETS_EDUCATION_DIALOG_SEEN =
436 backedUpItem("launcher.widgets_education_dialog_seen", false)
437 @JvmField
438 val WIDGETS_EDUCATION_TIP_SEEN = backedUpItem("launcher.widgets_education_tip_seen", false)
439
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000440 @JvmStatic
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000441 fun <T> backedUpItem(
442 sharedPrefKey: String,
443 defaultValue: T,
Stefan Andonian54495f32023-09-29 23:41:26 +0000444 encryptionType: EncryptionType = EncryptionType.ENCRYPTED
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000445 ): ConstantItem<T> =
Stefan Andonian54495f32023-09-29 23:41:26 +0000446 ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType)
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000447
448 @JvmStatic
449 fun <T> backedUpItem(
450 sharedPrefKey: String,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000451 type: Class<out T>,
Stefan Andonian54495f32023-09-29 23:41:26 +0000452 encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000453 defaultValueFromContext: (c: Context) -> T
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000454 ): ContextualItem<T> =
455 ContextualItem(
456 sharedPrefKey,
457 isBackedUp = true,
458 defaultValueFromContext,
Stefan Andonian54495f32023-09-29 23:41:26 +0000459 encryptionType,
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000460 type
461 )
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000462
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000463 @JvmStatic
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000464 fun <T> nonRestorableItem(
465 sharedPrefKey: String,
466 defaultValue: T,
Stefan Andonian54495f32023-09-29 23:41:26 +0000467 encryptionType: EncryptionType = EncryptionType.ENCRYPTED
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000468 ): ConstantItem<T> =
Stefan Andonian54495f32023-09-29 23:41:26 +0000469 ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType)
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000470
471 @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
472 @JvmStatic
473 fun getPrefs(context: Context): SharedPreferences {
474 // Use application context for shared preferences, so we use single cached instance
475 return context.applicationContext.getSharedPreferences(
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000476 SHARED_PREFERENCES_KEY,
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000477 MODE_PRIVATE
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000478 )
479 }
480
481 @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
482 @JvmStatic
483 fun getDevicePrefs(context: Context): SharedPreferences {
484 // Use application context for shared preferences, so we use a single cached instance
485 return context.applicationContext.getSharedPreferences(
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000486 DEVICE_PREFERENCES_KEY,
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000487 MODE_PRIVATE
Stefan Andoniand1b33b32022-12-16 21:22:27 +0000488 )
489 }
490 }
491}
492
Stefan Andoniandbc8ec52023-09-21 19:05:07 +0000493// It is a var because the unit tests are setting this to true so they can run.
Stefan Andonian54495f32023-09-29 23:41:26 +0000494var moveStartupDataToDeviceProtectedStorageIsEnabled: Boolean =
495 com.android.launcher3.config.FeatureFlags.MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE.get()
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000496
Stefan Andonian54495f32023-09-29 23:41:26 +0000497private val ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE: MutableSet<ConstantItem<*>> = mutableSetOf()
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000498
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000499abstract class Item {
500 abstract val sharedPrefKey: String
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000501 abstract val isBackedUp: Boolean
502 abstract val type: Class<*>
Stefan Andonian54495f32023-09-29 23:41:26 +0000503 abstract val encryptionType: EncryptionType
Stefan Andonian246eeaa2023-02-21 22:14:47 +0000504 val sharedPrefFile: String
505 get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000506
507 fun <T> to(value: T): Pair<Item, T> = Pair(this, value)
508}
509
510data class ConstantItem<T>(
511 override val sharedPrefKey: String,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000512 override val isBackedUp: Boolean,
513 val defaultValue: T,
Stefan Andonian54495f32023-09-29 23:41:26 +0000514 override val encryptionType: EncryptionType,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000515 // The default value can be null. If so, the type needs to be explicitly stated, or else NPE
516 override val type: Class<out T> = defaultValue!!::class.java
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000517) : Item() {
518 init {
Stefan Andonian54495f32023-09-29 23:41:26 +0000519 if (
520 encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
521 moveStartupDataToDeviceProtectedStorageIsEnabled
522 ) {
523 ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.add(this)
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000524 }
525 }
Sunny Goyalcd447402023-10-06 13:47:50 -0700526
527 fun get(c: Context): T = LauncherPrefs.get(c).get(this)
Stefan Andonian5bd9a222023-02-23 00:58:33 +0000528}
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000529
530data class ContextualItem<T>(
531 override val sharedPrefKey: String,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000532 override val isBackedUp: Boolean,
533 private val defaultSupplier: (c: Context) -> T,
Stefan Andonian54495f32023-09-29 23:41:26 +0000534 override val encryptionType: EncryptionType,
Stefan Andonian4c9612b2023-02-22 00:00:03 +0000535 override val type: Class<out T>
Stefan Andonian1d7f7032023-01-23 21:55:04 +0000536) : Item() {
537 private var default: T? = null
538
539 fun defaultValueFromContext(context: Context): T {
540 if (default == null) {
541 default = defaultSupplier(context)
542 }
543 return default!!
544 }
Sunny Goyalcd447402023-10-06 13:47:50 -0700545
546 fun get(c: Context): T = LauncherPrefs.get(c).get(this)
Thales Lima03ac3772023-01-06 15:16:41 +0000547}
Stefan Andonian54495f32023-09-29 23:41:26 +0000548
549enum class EncryptionType {
550 ENCRYPTED,
551 DEVICE_PROTECTED,
552 MOVE_TO_DEVICE_PROTECTED
553}