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 android.content.SharedPreferences.OnSharedPreferenceChangeListener
22 import androidx.annotation.VisibleForTesting
23 import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
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.states.RotationHelper
31 import com.android.launcher3.util.DisplayController
32 import com.android.launcher3.util.MainThreadInitializedObject
33 import com.android.launcher3.util.SafeCloseable
34 import com.android.launcher3.util.Themes
35 
36 /**
37  * Use same context for shared preferences, so that we use a single cached instance
38  *
39  * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
40  */
41 class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
42     private val deviceProtectedStorageContext =
43         encryptedContext.createDeviceProtectedStorageContext()
44 
45     private val bootAwarePrefs
46         get() =
47             deviceProtectedStorageContext.getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
48 
49     private val Item.encryptedPrefs
50         get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
51 
52     private fun chooseSharedPreferences(item: Item): SharedPreferences =
53         if (item.encryptionType == EncryptionType.DEVICE_PROTECTED) bootAwarePrefs
54         else item.encryptedPrefs
55 
56     /** Wrapper around `getInner` for a `ContextualItem` */
57     fun <T> get(item: ContextualItem<T>): T =
58         getInner(item, item.defaultValueFromContext(encryptedContext))
59 
60     /** Wrapper around `getInner` for an `Item` */
61     fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
62 
63     /**
64      * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
65      * default value type, and will throw an error if the type of the item provided is not a
66      * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
67      */
68     @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
69     private fun <T> getInner(item: Item, default: T): T {
70         val sp = chooseSharedPreferences(item)
71 
72         return when (item.type) {
73             String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
74             Boolean::class.java,
75             java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
76             Int::class.java,
77             java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int)
78             Float::class.java,
79             java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
80             Long::class.java,
81             java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long)
82             Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
83             else ->
84                 throw IllegalArgumentException(
85                     "item type: ${item.type}" + " is not compatible with sharedPref methods"
86                 )
87         }
88             as T
89     }
90 
91     /**
92      * Stores each of the values provided in `SharedPreferences` according to the configuration
93      * contained within the associated items provided. Internally, it uses apply, so the caller
94      * cannot assume that the values that have been put are immediately available for use.
95      *
96      * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from
97      * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
98      * provided item configurations.
99      */
100     fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
101         prepareToPutValues(itemsToValues).forEach { it.apply() }
102 
103     /** See referenced `put` method above. */
104     fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
105 
106     /**
107      * Synchronously stores all the values provided according to their associated Item
108      * configuration.
109      */
110     fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
111         prepareToPutValues(itemsToValues).forEach { it.commit() }
112 
113     /**
114      * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If
115      * the item is boot aware, this method updates both the boot aware and the encrypted files. This
116      * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs
117      * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which
118      * already points to encrypted storage.
119      *
120      * Returns a list of editors with all transactions added so that the caller can determine to use
121      * .apply() or .commit()
122      */
123     private fun prepareToPutValues(
124         updates: Array<out Pair<Item, Any>>
125     ): List<SharedPreferences.Editor> {
126         val updatesPerPrefFile =
127             updates
128                 .filter { it.first.encryptionType != EncryptionType.DEVICE_PROTECTED }
129                 .groupBy { it.first.encryptedPrefs }
130                 .toMutableMap()
131 
132         val bootAwareUpdates =
133             updates.filter { it.first.encryptionType == EncryptionType.DEVICE_PROTECTED }
134         if (bootAwareUpdates.isNotEmpty()) {
135             updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
136         }
137 
138         return updatesPerPrefFile.map { prefToItemValueList ->
139             prefToItemValueList.key.edit().apply {
140                 prefToItemValueList.value.forEach { itemToValue: Pair<Item, Any> ->
141                     putValue(itemToValue.first, itemToValue.second)
142                 }
143             }
144         }
145     }
146 
147     /**
148      * Handles adding values to `SharedPreferences` regardless of type. This method is especially
149      * helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple
150      * types of Item values.
151      */
152     @Suppress("UNCHECKED_CAST")
153     private fun SharedPreferences.Editor.putValue(
154         item: Item,
155         value: Any?
156     ): SharedPreferences.Editor =
157         when (item.type) {
158             String::class.java -> putString(item.sharedPrefKey, value as? String)
159             Boolean::class.java,
160             java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
161             Int::class.java,
162             java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int)
163             Float::class.java,
164             java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
165             Long::class.java,
166             java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
167             Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>)
168             else ->
169                 throw IllegalArgumentException(
170                     "item type: ${item.type} is not compatible with sharedPref methods"
171                 )
172         }
173 
174     /**
175      * After calling this method, the listener will be notified of any future updates to the
176      * `SharedPreferences` files associated with the provided list of items. The listener will need
177      * to filter update notifications so they don't activate for non-relevant updates.
178      */
179     fun addListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
180         items
181             .map { chooseSharedPreferences(it) }
182             .distinct()
183             .forEach { it.registerOnSharedPreferenceChangeListener(listener) }
184     }
185 
186     /**
187      * Stops the listener from getting notified of any more updates to any of the
188      * `SharedPreferences` files associated with any of the provided list of [Item].
189      */
190     fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
191         // If a listener is not registered to a SharedPreference, unregistering it does nothing
192         items
193             .map { chooseSharedPreferences(it) }
194             .distinct()
195             .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
196     }
197 
198     /**
199      * Checks if all the provided [Item] have values stored in their corresponding
200      * `SharedPreferences` files.
201      */
202     fun has(vararg items: Item): Boolean {
203         items
204             .groupBy { chooseSharedPreferences(it) }
205             .forEach { (prefs, itemsSublist) ->
206                 if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
207             }
208         return true
209     }
210 
211     /**
212      * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
213      */
214     fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
215 
216     /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
217     fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
218 
219     /**
220      * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
221      * item is boot aware, this method removes the data from both the boot aware and encrypted
222      * files.
223      *
224      * @return a list of editors with all transactions added so that the caller can determine to use
225      *   .apply() or .commit()
226      */
227     private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
228         val itemsPerFile =
229             items
230                 .filter { it.encryptionType != EncryptionType.DEVICE_PROTECTED }
231                 .groupBy { it.encryptedPrefs }
232                 .toMutableMap()
233 
234         val bootAwareUpdates = items.filter { it.encryptionType == EncryptionType.DEVICE_PROTECTED }
235         if (bootAwareUpdates.isNotEmpty()) {
236             itemsPerFile[bootAwarePrefs] = bootAwareUpdates
237         }
238 
239         return itemsPerFile.map { (prefs, items) ->
240             prefs.edit().also { editor ->
241                 items.forEach { item -> editor.remove(item.sharedPrefKey) }
242             }
243         }
244     }
245 
246     override fun close() {}
247 
248     companion object {
249         @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
250 
251         @JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) }
252 
253         @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
254 
255         const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
256         const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY"
257         const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
258         @JvmField
259         val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED)
260 
261         @JvmField
262         val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
263         @JvmField
264         val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED)
265         @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
266         @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
267         @JvmField
268         val WORKSPACE_SIZE =
269             backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED)
270         @JvmField
271         val HOTSEAT_COUNT =
272             backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED)
273         @JvmField
274         val TASKBAR_PINNING =
275             backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
276         @JvmField
277         val TASKBAR_PINNING_IN_DESKTOP_MODE =
278             backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED)
279 
280         @JvmField
281         val DEVICE_TYPE =
282             backedUpItem(
283                 DeviceGridState.KEY_DEVICE_TYPE,
284                 InvariantDeviceProfile.TYPE_PHONE,
285                 EncryptionType.ENCRYPTED
286             )
287         @JvmField
288         val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED)
289         @JvmField
290         val SHOULD_SHOW_SMARTSPACE =
291             backedUpItem(
292                 SHOULD_SHOW_SMARTSPACE_KEY,
293                 WIDGET_ON_FIRST_SCREEN,
294                 EncryptionType.DEVICE_PROTECTED
295             )
296         @JvmField
297         val RESTORE_DEVICE =
298             backedUpItem(
299                 RestoreDbTask.RESTORED_DEVICE_TYPE,
300                 InvariantDeviceProfile.TYPE_PHONE,
301                 EncryptionType.ENCRYPTED
302             )
303         @JvmField
304         val IS_FIRST_LOAD_AFTER_RESTORE =
305             nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED)
306         @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
307         @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
308         @JvmField
309         val GRID_NAME =
310             ConstantItem(
311                 "idp_grid_name",
312                 isBackedUp = true,
313                 defaultValue = null,
314                 encryptionType = EncryptionType.ENCRYPTED,
315                 type = String::class.java
316             )
317         @JvmField
318         val ALLOW_ROTATION =
319             backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
320                 RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
321             }
322 
323         // Preferences for widget configurations
324         @JvmField
325         val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
326             backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false)
327 
328         @JvmStatic
329         fun <T> backedUpItem(
330             sharedPrefKey: String,
331             defaultValue: T,
332             encryptionType: EncryptionType = EncryptionType.ENCRYPTED
333         ): ConstantItem<T> =
334             ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType)
335 
336         @JvmStatic
337         fun <T> backedUpItem(
338             sharedPrefKey: String,
339             type: Class<out T>,
340             encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
341             defaultValueFromContext: (c: Context) -> T
342         ): ContextualItem<T> =
343             ContextualItem(
344                 sharedPrefKey,
345                 isBackedUp = true,
346                 defaultValueFromContext,
347                 encryptionType,
348                 type
349             )
350 
351         @JvmStatic
352         fun <T> nonRestorableItem(
353             sharedPrefKey: String,
354             defaultValue: T,
355             encryptionType: EncryptionType = EncryptionType.ENCRYPTED
356         ): ConstantItem<T> =
357             ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType)
358 
359         @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
360         @JvmStatic
361         fun getPrefs(context: Context): SharedPreferences {
362             // Use application context for shared preferences, so we use single cached instance
363             return context.applicationContext.getSharedPreferences(
364                 SHARED_PREFERENCES_KEY,
365                 MODE_PRIVATE
366             )
367         }
368 
369         @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
370         @JvmStatic
371         fun getDevicePrefs(context: Context): SharedPreferences {
372             // Use application context for shared preferences, so we use a single cached instance
373             return context.applicationContext.getSharedPreferences(
374                 DEVICE_PREFERENCES_KEY,
375                 MODE_PRIVATE
376             )
377         }
378     }
379 }
380 
381 abstract class Item {
382     abstract val sharedPrefKey: String
383     abstract val isBackedUp: Boolean
384     abstract val type: Class<*>
385     abstract val encryptionType: EncryptionType
386     val sharedPrefFile: String
387         get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY
388 
tonull389     fun <T> to(value: T): Pair<Item, T> = Pair(this, value)
390 }
391 
392 data class ConstantItem<T>(
393     override val sharedPrefKey: String,
394     override val isBackedUp: Boolean,
395     val defaultValue: T,
396     override val encryptionType: EncryptionType,
397     // The default value can be null. If so, the type needs to be explicitly stated, or else NPE
398     override val type: Class<out T> = defaultValue!!::class.java
399 ) : Item() {
400 
401     fun get(c: Context): T = LauncherPrefs.get(c).get(this)
402 }
403 
404 data class ContextualItem<T>(
405     override val sharedPrefKey: String,
406     override val isBackedUp: Boolean,
407     private val defaultSupplier: (c: Context) -> T,
408     override val encryptionType: EncryptionType,
409     override val type: Class<out T>
410 ) : Item() {
411     private var default: T? = null
412 
defaultValueFromContextnull413     fun defaultValueFromContext(context: Context): T {
414         if (default == null) {
415             default = defaultSupplier(context)
416         }
417         return default!!
418     }
419 
getnull420     fun get(c: Context): T = LauncherPrefs.get(c).get(this)
421 }
422 
423 enum class EncryptionType {
424     ENCRYPTED,
425     DEVICE_PROTECTED,
426 }
427