1 /*
<lambda>null2  * 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  */
16 package com.android.launcher3
17 
18 import android.content.Context
19 import android.content.Context.MODE_PRIVATE
20 import android.content.SharedPreferences
21 import androidx.annotation.VisibleForTesting
22 import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
23 import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY
24 import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
25 import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
26 import com.android.launcher3.model.DeviceGridState
27 import com.android.launcher3.pm.InstallSessionHelper
28 import com.android.launcher3.provider.RestoreDbTask
29 import com.android.launcher3.provider.RestoreDbTask.FIRST_LOAD_AFTER_RESTORE_KEY
30 import com.android.launcher3.settings.SettingsActivity
31 import com.android.launcher3.states.RotationHelper
32 import com.android.launcher3.util.DisplayController
33 import com.android.launcher3.util.MainThreadInitializedObject
34 import com.android.launcher3.util.SafeCloseable
35 import com.android.launcher3.util.Themes
36 
37 /**
38  * Manages Launcher [SharedPreferences] through [Item] instances.
39  *
40  * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
41  */
42 abstract 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
131         val NO_DB_FILES_RESTORED =
132             nonRestorableItem("no_db_files_restored", false, EncryptionType.DEVICE_PROTECTED)
133         @JvmField
134         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, "")
138 
139         @JvmField
140         val GRID_NAME =
141             ConstantItem(
142                 GRID_NAME_PREFS_KEY,
143                 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 
154         @JvmField
155         val FIXED_LANDSCAPE_MODE = backedUpItem(SettingsActivity.FIXED_LANDSCAPE_MODE, false)
156 
157         // 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 
215 private class LauncherPrefsImpl(private val encryptedContext: Context) : LauncherPrefs() {
216     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 
chooseSharedPreferencesnull226     private fun chooseSharedPreferences(item: Item): SharedPreferences =
227         if (item.encryptionType == EncryptionType.DEVICE_PROTECTED) bootAwarePrefs
228         else item.encryptedPrefs
229 
230     /** Wrapper around `getInner` for a `ContextualItem` */
231     override fun <T> get(item: ContextualItem<T>): T =
232         getInner(item, item.defaultValueFromContext(encryptedContext))
233 
234     /** Wrapper around `getInner` for an `Item` */
235     override fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
236 
237     /**
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")
243     private fun <T> getInner(item: Item, default: T): T {
244         val sp = chooseSharedPreferences(item)
245 
246         return when (item.type) {
247             String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
248             Boolean::class.java,
249             java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
250             Int::class.java,
251             java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int)
252             Float::class.java,
253             java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
254             Long::class.java,
255             java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long)
256             Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
257             else ->
258                 throw IllegalArgumentException(
259                     "item type: ${item.type}" + " is not compatible with sharedPref methods"
260                 )
261         }
262             as T
263     }
264 
265     /**
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      */
putnull274     override fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
275         prepareToPutValues(itemsToValues).forEach { it.apply() }
276 
277     /** See referenced `put` method above. */
putnull278     override fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
279 
280     /**
281      * Synchronously stores all the values provided according to their associated Item
282      * configuration.
283      */
284     override fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
285         prepareToPutValues(itemsToValues).forEach { it.commit() }
286 
287     /**
288      * 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.
293      *
294      * Returns a list of editors with all transactions added so that the caller can determine to use
295      * .apply() or .commit()
296      */
prepareToPutValuesnull297     private fun prepareToPutValues(
298         updates: Array<out Pair<Item, Any>>
299     ): List<SharedPreferences.Editor> {
300         val updatesPerPrefFile =
301             updates
302                 .filter { it.first.encryptionType != EncryptionType.DEVICE_PROTECTED }
303                 .groupBy { it.first.encryptedPrefs }
304                 .toMutableMap()
305 
306         val bootAwareUpdates =
307             updates.filter { it.first.encryptionType == EncryptionType.DEVICE_PROTECTED }
308         if (bootAwareUpdates.isNotEmpty()) {
309             updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
310         }
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     }
320 
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")
SharedPreferencesnull327     private fun SharedPreferences.Editor.putValue(
328         item: Item,
329         value: Any?,
330     ): SharedPreferences.Editor =
331         when (item.type) {
332             String::class.java -> putString(item.sharedPrefKey, value as? String)
333             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)
341             Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>)
342             else ->
343                 throw IllegalArgumentException(
344                     "item type: ${item.type} is not compatible with sharedPref methods"
345                 )
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      */
addListenernull353     override fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
354         items
355             .map { chooseSharedPreferences(it) }
356             .distinct()
357             .forEach { it.registerOnSharedPreferenceChangeListener(listener) }
358     }
359 
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      */
removeListenernull364     override fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
365         // If a listener is not registered to a SharedPreference, unregistering it does nothing
366         items
367             .map { chooseSharedPreferences(it) }
368             .distinct()
369             .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
370     }
371 
372     /**
373      * Checks if all the provided [Item] have values stored in their corresponding
374      * `SharedPreferences` files.
375      */
hasnull376     override fun has(vararg items: Item): Boolean {
377         items
378             .groupBy { chooseSharedPreferences(it) }
379             .forEach { (prefs, itemsSublist) ->
380                 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      */
<lambda>null388     override fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
389 
390     /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
<lambda>null391     override fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
392 
393     /**
394      * 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
399      *   .apply() or .commit()
400      */
prepareToRemovenull401     private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
402         val itemsPerFile =
403             items
404                 .filter { it.encryptionType != EncryptionType.DEVICE_PROTECTED }
405                 .groupBy { it.encryptedPrefs }
406                 .toMutableMap()
407 
408         val bootAwareUpdates = items.filter { it.encryptionType == EncryptionType.DEVICE_PROTECTED }
409         if (bootAwareUpdates.isNotEmpty()) {
410             itemsPerFile[bootAwarePrefs] = bootAwareUpdates
411         }
412 
413         return itemsPerFile.map { (prefs, items) ->
414             prefs.edit().also { editor ->
415                 items.forEach { item -> editor.remove(item.sharedPrefKey) }
416             }
417         }
418     }
419 
closenull420     override fun close() {}
421 }
422 
423 abstract class Item {
424     abstract val sharedPrefKey: String
425     abstract val isBackedUp: Boolean
426     abstract val type: Class<*>
427     abstract val encryptionType: EncryptionType
428     val sharedPrefFile: String
429         get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY
430 
tonull431     fun <T> to(value: T): Pair<Item, T> = Pair(this, value)
432 }
433 
434 data class ConstantItem<T>(
435     override val sharedPrefKey: String,
436     override val isBackedUp: Boolean,
437     val defaultValue: T,
438     override val encryptionType: EncryptionType,
439     // The default value can be null. If so, the type needs to be explicitly stated, or else NPE
440     override val type: Class<out T> = defaultValue!!::class.java,
441 ) : Item() {
442 
443     fun get(c: Context): T = LauncherPrefs.get(c).get(this)
444 }
445 
446 data class ContextualItem<T>(
447     override val sharedPrefKey: String,
448     override val isBackedUp: Boolean,
449     private val defaultSupplier: (c: Context) -> T,
450     override val encryptionType: EncryptionType,
451     override val type: Class<out T>,
452 ) : Item() {
453     private var default: T? = null
454 
defaultValueFromContextnull455     fun defaultValueFromContext(context: Context): T {
456         if (default == null) {
457             default = defaultSupplier(context)
458         }
459         return default!!
460     }
461 
getnull462     fun get(c: Context): T = LauncherPrefs.get(c).get(this)
463 }
464 
465 enum class EncryptionType {
466     ENCRYPTED,
467     DEVICE_PROTECTED,
468 }
469