• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.GridType.Companion.GRID_TYPE_ANY
24 import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY
25 import com.android.launcher3.InvariantDeviceProfile.NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY
26 import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
27 import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
28 import com.android.launcher3.dagger.ApplicationContext
29 import com.android.launcher3.dagger.LauncherAppComponent
30 import com.android.launcher3.dagger.LauncherAppSingleton
31 import com.android.launcher3.model.DeviceGridState
32 import com.android.launcher3.pm.InstallSessionHelper
33 import com.android.launcher3.provider.RestoreDbTask
34 import com.android.launcher3.provider.RestoreDbTask.FIRST_LOAD_AFTER_RESTORE_KEY
35 import com.android.launcher3.settings.SettingsActivity
36 import com.android.launcher3.states.RotationHelper
37 import com.android.launcher3.util.DaggerSingletonObject
38 import com.android.launcher3.util.DisplayController
39 import java.util.concurrent.ConcurrentHashMap
40 import javax.inject.Inject
41 
42 /**
43  * Manages Launcher [SharedPreferences] through [Item] instances.
44  *
45  * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
46  */
47 @LauncherAppSingleton
48 open class LauncherPrefs
49 @Inject
50 constructor(@ApplicationContext private val encryptedContext: Context) {
51 
52     private val deviceProtectedSharedPrefs: SharedPreferences by lazy {
53         encryptedContext
54             .createDeviceProtectedStorageContext()
55             .getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
56     }
57 
58     open protected fun getSharedPrefs(item: Item): SharedPreferences =
59         item.run {
60             if (encryptionType == EncryptionType.DEVICE_PROTECTED) deviceProtectedSharedPrefs
61             else encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
62         }
63 
64     /** Returns the value with type [T] for [item]. */
65     fun <T> get(item: ContextualItem<T>): T =
66         getInner(item, item.defaultValueFromContext(encryptedContext))
67 
68     /** Returns the value with type [T] for [item]. */
69     fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
70 
71     /**
72      * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
73      * default value type, and will throw an error if the type of the item provided is not a
74      * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
75      */
76     @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
77     private fun <T> getInner(item: Item, default: T): T {
78         val sp = getSharedPrefs(item)
79         return when {
80             item.type == String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
81             item.type == Boolean::class.java || item.type == java.lang.Boolean::class.java ->
82                 sp.getBoolean(item.sharedPrefKey, default as Boolean)
83             item.type == Int::class.java || item.type == java.lang.Integer::class.java ->
84                 sp.getInt(item.sharedPrefKey, default as Int)
85             item.type == Float::class.java || item.type == java.lang.Float::class.java ->
86                 sp.getFloat(item.sharedPrefKey, default as Float)
87             item.type == Long::class.java || item.type == java.lang.Long::class.java ->
88                 sp.getLong(item.sharedPrefKey, default as Long)
89             Set::class.java.isAssignableFrom(item.type) ->
90                 sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
91             else ->
92                 throw IllegalArgumentException(
93                     "item type: ${item.type}" + " is not compatible with sharedPref methods"
94                 )
95         }
96             as T
97     }
98 
99     /**
100      * Stores each of the values provided in `SharedPreferences` according to the configuration
101      * contained within the associated items provided. Internally, it uses apply, so the caller
102      * cannot assume that the values that have been put are immediately available for use.
103      *
104      * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from
105      * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
106      * provided item configurations.
107      */
108     fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
109         prepareToPutValues(itemsToValues).forEach { it.apply() }
110 
111     /** See referenced `put` method above. */
112     fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
113 
114     /**
115      * Synchronously stores all the values provided according to their associated Item
116      * configuration.
117      */
118     fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
119         prepareToPutValues(itemsToValues).forEach { it.commit() }
120 
121     /**
122      * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If
123      * the item is boot aware, this method updates both the boot aware and the encrypted files. This
124      * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs
125      * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which
126      * already points to encrypted storage.
127      *
128      * Returns a list of editors with all transactions added so that the caller can determine to use
129      * .apply() or .commit()
130      */
131     private fun prepareToPutValues(
132         updates: Array<out Pair<Item, Any>>
133     ): List<SharedPreferences.Editor> {
134         val updatesPerPrefFile = updates.groupBy { getSharedPrefs(it.first) }.toMap()
135 
136         return updatesPerPrefFile.map { (sharedPref, itemList) ->
137             sharedPref.edit().apply { itemList.forEach { (item, value) -> putValue(item, value) } }
138         }
139     }
140 
141     /**
142      * Handles adding values to `SharedPreferences` regardless of type. This method is especially
143      * helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple
144      * types of Item values.
145      */
146     @Suppress("UNCHECKED_CAST")
147     internal fun SharedPreferences.Editor.putValue(
148         item: Item,
149         value: Any?,
150     ): SharedPreferences.Editor =
151         when {
152             item.type == String::class.java -> putString(item.sharedPrefKey, value as? String)
153             item.type == Boolean::class.java || item.type == java.lang.Boolean::class.java ->
154                 putBoolean(item.sharedPrefKey, value as Boolean)
155             item.type == Int::class.java || item.type == java.lang.Integer::class.java ->
156                 putInt(item.sharedPrefKey, value as Int)
157             item.type == Float::class.java || item.type == java.lang.Float::class.java ->
158                 putFloat(item.sharedPrefKey, value as Float)
159             item.type == Long::class.java || item.type == java.lang.Long::class.java ->
160                 putLong(item.sharedPrefKey, value as Long)
161             Set::class.java.isAssignableFrom(item.type) ->
162                 putStringSet(item.sharedPrefKey, value as? Set<String>)
163             else ->
164                 throw IllegalArgumentException(
165                     "item type: ${item.type} is not compatible with sharedPref methods"
166                 )
167         }
168 
169     /**
170      * After calling this method, the listener will be notified of any future updates to the
171      * `SharedPreferences` files associated with the provided list of items. The listener will need
172      * to filter update notifications so they don't activate for non-relevant updates.
173      */
174     fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
175         items
176             .map { getSharedPrefs(it) }
177             .distinct()
178             .forEach { it.registerOnSharedPreferenceChangeListener(listener) }
179     }
180 
181     /**
182      * Stops the listener from getting notified of any more updates to any of the
183      * `SharedPreferences` files associated with any of the provided list of [Item].
184      */
185     fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
186         // If a listener is not registered to a SharedPreference, unregistering it does nothing
187         items
188             .map { getSharedPrefs(it) }
189             .distinct()
190             .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
191     }
192 
193     /**
194      * Checks if all the provided [Item] have values stored in their corresponding
195      * `SharedPreferences` files.
196      */
197     fun has(vararg items: Item): Boolean {
198         items
199             .groupBy { getSharedPrefs(it) }
200             .forEach { (prefs, itemsSublist) ->
201                 if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
202             }
203         return true
204     }
205 
206     /**
207      * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
208      */
209     fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
210 
211     /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
212     fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
213 
214     /**
215      * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
216      * item is boot aware, this method removes the data from both the boot aware and encrypted
217      * files.
218      *
219      * @return a list of editors with all transactions added so that the caller can determine to use
220      *   .apply() or .commit()
221      */
222     private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
223         val itemsPerFile = items.groupBy { getSharedPrefs(it) }.toMap()
224 
225         return itemsPerFile.map { (prefs, items) ->
226             prefs.edit().also { editor ->
227                 items.forEach { item -> editor.remove(item.sharedPrefKey) }
228             }
229         }
230     }
231 
232     companion object {
233         @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
234 
235         @JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getLauncherPrefs)
236 
237         @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
238 
239         const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
240         const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY"
241         const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
242 
243         @JvmField
244         val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
245         @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
246         @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
247         @JvmField
248         val WORKSPACE_SIZE =
249             backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED)
250         @JvmField
251         val HOTSEAT_COUNT =
252             backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED)
253         @JvmField
254         val TASKBAR_PINNING =
255             backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
256         @JvmField
257         val TASKBAR_PINNING_IN_DESKTOP_MODE =
258             backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED)
259 
260         @JvmField
261         val DEVICE_TYPE =
262             backedUpItem(
263                 DeviceGridState.KEY_DEVICE_TYPE,
264                 InvariantDeviceProfile.TYPE_PHONE,
265                 EncryptionType.ENCRYPTED,
266             )
267         @JvmField
268         val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED)
269         @JvmField
270         val GRID_TYPE =
271             backedUpItem(DeviceGridState.KEY_GRID_TYPE, GRID_TYPE_ANY, EncryptionType.ENCRYPTED)
272         @JvmField
273         val SHOULD_SHOW_SMARTSPACE =
274             backedUpItem(
275                 SHOULD_SHOW_SMARTSPACE_KEY,
276                 WIDGET_ON_FIRST_SCREEN,
277                 EncryptionType.DEVICE_PROTECTED,
278             )
279         @JvmField
280         val RESTORE_DEVICE =
281             backedUpItem(
282                 RestoreDbTask.RESTORED_DEVICE_TYPE,
283                 InvariantDeviceProfile.TYPE_PHONE,
284                 EncryptionType.ENCRYPTED,
285             )
286         @JvmField
287         val NO_DB_FILES_RESTORED =
288             nonRestorableItem("no_db_files_restored", false, EncryptionType.DEVICE_PROTECTED)
289         @JvmField
290         val IS_FIRST_LOAD_AFTER_RESTORE =
291             nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED)
292         @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
293         @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
294 
295         @JvmField
296         val GRID_NAME =
297             ConstantItem(
298                 GRID_NAME_PREFS_KEY,
299                 isBackedUp = true,
300                 defaultValue = null,
301                 encryptionType = EncryptionType.ENCRYPTED,
302                 type = String::class.java,
303             )
304         @JvmField
305         val ALLOW_ROTATION =
306             backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
307                 RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
308             }
309 
310         @JvmField
311         val FIXED_LANDSCAPE_MODE = backedUpItem(SettingsActivity.FIXED_LANDSCAPE_MODE, false)
312 
313         @JvmField
314         val NON_FIXED_LANDSCAPE_GRID_NAME =
315             ConstantItem(
316                 NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY,
317                 isBackedUp = true,
318                 defaultValue = null,
319                 encryptionType = EncryptionType.ENCRYPTED,
320                 type = String::class.java,
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 
428 /**
429  * LauncherPrefs which delegates all lookup to [prefs] but uses the real prefs for initial values
430  */
431 class ProxyPrefs(context: Context, private val prefs: SharedPreferences) : LauncherPrefs(context) {
432 
433     private val copiedPrefs = ConcurrentHashMap<SharedPreferences, Boolean>()
434 
getSharedPrefsnull435     override fun getSharedPrefs(item: Item): SharedPreferences {
436         val originalPrefs = super.getSharedPrefs(item)
437         // Copy all existing values, when the pref is accessed for the first time
438         copiedPrefs.computeIfAbsent(originalPrefs) { op ->
439             val editor = prefs.edit()
440             op.all.forEach { (key, value) ->
441                 if (value != null) {
442                     editor.putValue(backedUpItem(key, value), value)
443                 }
444             }
445             editor.commit()
446         }
447         return prefs
448     }
449 }
450