Converting LauncherPrefs to dagger

Fixing ENABLE_TWOLINE_ALLAPPS_TOGGLE not properly tied to IDP:
http://recall/-/ep7WJ8pKwCEklUN5J1mAkM

Bug: 361850561
Flag: EXEMPT dagger-migration
Test: atest LauncherPrefsTest FakeLauncherPrefsTest
Change-Id: Iba63d060f4a8c2e31033fca2a4638c559c161338
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 5b9c2fa..ad592d8 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -23,59 +23,212 @@
 import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY
 import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
 import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.model.DeviceGridState
 import com.android.launcher3.pm.InstallSessionHelper
 import com.android.launcher3.provider.RestoreDbTask
 import com.android.launcher3.provider.RestoreDbTask.FIRST_LOAD_AFTER_RESTORE_KEY
 import com.android.launcher3.settings.SettingsActivity
 import com.android.launcher3.states.RotationHelper
+import com.android.launcher3.util.DaggerSingletonObject
 import com.android.launcher3.util.DisplayController
-import com.android.launcher3.util.MainThreadInitializedObject
-import com.android.launcher3.util.SafeCloseable
 import com.android.launcher3.util.Themes
+import javax.inject.Inject
 
 /**
  * Manages Launcher [SharedPreferences] through [Item] instances.
  *
  * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
  */
-abstract class LauncherPrefs : SafeCloseable {
+@LauncherAppSingleton
+open class LauncherPrefs
+@Inject
+constructor(@ApplicationContext private val encryptedContext: Context) {
+
+    private val deviceProtectedSharedPrefs: SharedPreferences by lazy {
+        encryptedContext
+            .createDeviceProtectedStorageContext()
+            .getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
+    }
+
+    open val Item.sharedPrefs: SharedPreferences
+        get() =
+            if (encryptionType == EncryptionType.DEVICE_PROTECTED) deviceProtectedSharedPrefs
+            else encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
 
     /** Returns the value with type [T] for [item]. */
-    abstract fun <T> get(item: ContextualItem<T>): T
+    fun <T> get(item: ContextualItem<T>): T =
+        getInner(item, item.defaultValueFromContext(encryptedContext))
 
     /** Returns the value with type [T] for [item]. */
-    abstract fun <T> get(item: ConstantItem<T>): T
+    fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
 
-    /** Stores the values for each item in preferences. */
-    abstract fun put(vararg itemsToValues: Pair<Item, Any>)
+    /**
+     * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
+     * default value type, and will throw an error if the type of the item provided is not a
+     * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
+     */
+    @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
+    private fun <T> getInner(item: Item, default: T): T {
+        val sp = item.sharedPrefs
 
-    /** Stores the [value] with type [T] for [item] in preferences. */
-    abstract fun <T : Any> put(item: Item, value: T)
+        return when (item.type) {
+            String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
+            Boolean::class.java,
+            java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
+            Int::class.java,
+            java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int)
+            Float::class.java,
+            java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
+            Long::class.java,
+            java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long)
+            Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
+            else ->
+                throw IllegalArgumentException(
+                    "item type: ${item.type}" + " is not compatible with sharedPref methods"
+                )
+        }
+            as T
+    }
 
-    /** Synchronous version of [put]. */
-    abstract fun putSync(vararg itemsToValues: Pair<Item, Any>)
+    /**
+     * Stores each of the values provided in `SharedPreferences` according to the configuration
+     * contained within the associated items provided. Internally, it uses apply, so the caller
+     * cannot assume that the values that have been put are immediately available for use.
+     *
+     * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from
+     * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
+     * provided item configurations.
+     */
+    fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
+        prepareToPutValues(itemsToValues).forEach { it.apply() }
 
-    /** Registers [listener] for [items]. */
-    abstract fun addListener(listener: LauncherPrefChangeListener, vararg items: Item)
+    /** See referenced `put` method above. */
+    fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
 
-    /** Unregisters [listener] for [items]. */
-    abstract fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item)
+    /**
+     * Synchronously stores all the values provided according to their associated Item
+     * configuration.
+     */
+    fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
+        prepareToPutValues(itemsToValues).forEach { it.commit() }
 
-    /** Returns `true` iff all [items] have a value. */
-    abstract fun has(vararg items: Item): Boolean
+    /**
+     * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If
+     * the item is boot aware, this method updates both the boot aware and the encrypted files. This
+     * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs
+     * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which
+     * already points to encrypted storage.
+     *
+     * Returns a list of editors with all transactions added so that the caller can determine to use
+     * .apply() or .commit()
+     */
+    private fun prepareToPutValues(
+        updates: Array<out Pair<Item, Any>>
+    ): List<SharedPreferences.Editor> {
+        val updatesPerPrefFile = updates.groupBy { it.first.sharedPrefs }.toMap()
 
-    /** Removes the value for each item in [items]. */
-    abstract fun remove(vararg items: Item)
+        return updatesPerPrefFile.map { (sharedPref, itemList) ->
+            sharedPref.edit().apply { itemList.forEach { (item, value) -> putValue(item, value) } }
+        }
+    }
 
-    /** Synchronous version of [remove]. */
-    abstract fun removeSync(vararg items: Item)
+    /**
+     * Handles adding values to `SharedPreferences` regardless of type. This method is especially
+     * helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple
+     * types of Item values.
+     */
+    @Suppress("UNCHECKED_CAST")
+    private fun SharedPreferences.Editor.putValue(
+        item: Item,
+        value: Any?,
+    ): SharedPreferences.Editor =
+        when (item.type) {
+            String::class.java -> putString(item.sharedPrefKey, value as? String)
+            Boolean::class.java,
+            java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
+            Int::class.java,
+            java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int)
+            Float::class.java,
+            java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
+            Long::class.java,
+            java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
+            Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>)
+            else ->
+                throw IllegalArgumentException(
+                    "item type: ${item.type} is not compatible with sharedPref methods"
+                )
+        }
+
+    /**
+     * After calling this method, the listener will be notified of any future updates to the
+     * `SharedPreferences` files associated with the provided list of items. The listener will need
+     * to filter update notifications so they don't activate for non-relevant updates.
+     */
+    fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
+        items
+            .map { it.sharedPrefs }
+            .distinct()
+            .forEach { it.registerOnSharedPreferenceChangeListener(listener) }
+    }
+
+    /**
+     * Stops the listener from getting notified of any more updates to any of the
+     * `SharedPreferences` files associated with any of the provided list of [Item].
+     */
+    fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
+        // If a listener is not registered to a SharedPreference, unregistering it does nothing
+        items
+            .map { it.sharedPrefs }
+            .distinct()
+            .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
+    }
+
+    /**
+     * Checks if all the provided [Item] have values stored in their corresponding
+     * `SharedPreferences` files.
+     */
+    fun has(vararg items: Item): Boolean {
+        items
+            .groupBy { it.sharedPrefs }
+            .forEach { (prefs, itemsSublist) ->
+                if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
+            }
+        return true
+    }
+
+    /**
+     * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
+     */
+    fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
+
+    /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
+    fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
+
+    /**
+     * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
+     * item is boot aware, this method removes the data from both the boot aware and encrypted
+     * files.
+     *
+     * @return a list of editors with all transactions added so that the caller can determine to use
+     *   .apply() or .commit()
+     */
+    private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
+        val itemsPerFile = items.groupBy { it.sharedPrefs }.toMap()
+
+        return itemsPerFile.map { (prefs, items) ->
+            prefs.edit().also { editor ->
+                items.forEach { item -> editor.remove(item.sharedPrefKey) }
+            }
+        }
+    }
 
     companion object {
         @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
 
-        @JvmField
-        var INSTANCE = MainThreadInitializedObject<LauncherPrefs> { LauncherPrefsImpl(it) }
+        @JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getLauncherPrefs)
 
         @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
 
@@ -212,214 +365,6 @@
     }
 }
 
-private class LauncherPrefsImpl(private val encryptedContext: Context) : LauncherPrefs() {
-    private val deviceProtectedStorageContext =
-        encryptedContext.createDeviceProtectedStorageContext()
-
-    private val bootAwarePrefs
-        get() =
-            deviceProtectedStorageContext.getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
-
-    private val Item.encryptedPrefs
-        get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
-
-    private fun chooseSharedPreferences(item: Item): SharedPreferences =
-        if (item.encryptionType == EncryptionType.DEVICE_PROTECTED) bootAwarePrefs
-        else item.encryptedPrefs
-
-    /** Wrapper around `getInner` for a `ContextualItem` */
-    override fun <T> get(item: ContextualItem<T>): T =
-        getInner(item, item.defaultValueFromContext(encryptedContext))
-
-    /** Wrapper around `getInner` for an `Item` */
-    override fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
-
-    /**
-     * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
-     * default value type, and will throw an error if the type of the item provided is not a
-     * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
-     */
-    @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
-    private fun <T> getInner(item: Item, default: T): T {
-        val sp = chooseSharedPreferences(item)
-
-        return when (item.type) {
-            String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
-            Boolean::class.java,
-            java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
-            Int::class.java,
-            java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int)
-            Float::class.java,
-            java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
-            Long::class.java,
-            java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long)
-            Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
-            else ->
-                throw IllegalArgumentException(
-                    "item type: ${item.type}" + " is not compatible with sharedPref methods"
-                )
-        }
-            as T
-    }
-
-    /**
-     * Stores each of the values provided in `SharedPreferences` according to the configuration
-     * contained within the associated items provided. Internally, it uses apply, so the caller
-     * cannot assume that the values that have been put are immediately available for use.
-     *
-     * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from
-     * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
-     * provided item configurations.
-     */
-    override fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
-        prepareToPutValues(itemsToValues).forEach { it.apply() }
-
-    /** See referenced `put` method above. */
-    override fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
-
-    /**
-     * Synchronously stores all the values provided according to their associated Item
-     * configuration.
-     */
-    override fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
-        prepareToPutValues(itemsToValues).forEach { it.commit() }
-
-    /**
-     * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If
-     * the item is boot aware, this method updates both the boot aware and the encrypted files. This
-     * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs
-     * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which
-     * already points to encrypted storage.
-     *
-     * Returns a list of editors with all transactions added so that the caller can determine to use
-     * .apply() or .commit()
-     */
-    private fun prepareToPutValues(
-        updates: Array<out Pair<Item, Any>>
-    ): List<SharedPreferences.Editor> {
-        val updatesPerPrefFile =
-            updates
-                .filter { it.first.encryptionType != EncryptionType.DEVICE_PROTECTED }
-                .groupBy { it.first.encryptedPrefs }
-                .toMutableMap()
-
-        val bootAwareUpdates =
-            updates.filter { it.first.encryptionType == EncryptionType.DEVICE_PROTECTED }
-        if (bootAwareUpdates.isNotEmpty()) {
-            updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
-        }
-
-        return updatesPerPrefFile.map { prefToItemValueList ->
-            prefToItemValueList.key.edit().apply {
-                prefToItemValueList.value.forEach { itemToValue: Pair<Item, Any> ->
-                    putValue(itemToValue.first, itemToValue.second)
-                }
-            }
-        }
-    }
-
-    /**
-     * Handles adding values to `SharedPreferences` regardless of type. This method is especially
-     * helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple
-     * types of Item values.
-     */
-    @Suppress("UNCHECKED_CAST")
-    private fun SharedPreferences.Editor.putValue(
-        item: Item,
-        value: Any?,
-    ): SharedPreferences.Editor =
-        when (item.type) {
-            String::class.java -> putString(item.sharedPrefKey, value as? String)
-            Boolean::class.java,
-            java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
-            Int::class.java,
-            java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int)
-            Float::class.java,
-            java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
-            Long::class.java,
-            java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
-            Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>)
-            else ->
-                throw IllegalArgumentException(
-                    "item type: ${item.type} is not compatible with sharedPref methods"
-                )
-        }
-
-    /**
-     * After calling this method, the listener will be notified of any future updates to the
-     * `SharedPreferences` files associated with the provided list of items. The listener will need
-     * to filter update notifications so they don't activate for non-relevant updates.
-     */
-    override fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
-        items
-            .map { chooseSharedPreferences(it) }
-            .distinct()
-            .forEach { it.registerOnSharedPreferenceChangeListener(listener) }
-    }
-
-    /**
-     * Stops the listener from getting notified of any more updates to any of the
-     * `SharedPreferences` files associated with any of the provided list of [Item].
-     */
-    override fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
-        // If a listener is not registered to a SharedPreference, unregistering it does nothing
-        items
-            .map { chooseSharedPreferences(it) }
-            .distinct()
-            .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
-    }
-
-    /**
-     * Checks if all the provided [Item] have values stored in their corresponding
-     * `SharedPreferences` files.
-     */
-    override fun has(vararg items: Item): Boolean {
-        items
-            .groupBy { chooseSharedPreferences(it) }
-            .forEach { (prefs, itemsSublist) ->
-                if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
-            }
-        return true
-    }
-
-    /**
-     * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
-     */
-    override fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
-
-    /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
-    override fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
-
-    /**
-     * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
-     * item is boot aware, this method removes the data from both the boot aware and encrypted
-     * files.
-     *
-     * @return a list of editors with all transactions added so that the caller can determine to use
-     *   .apply() or .commit()
-     */
-    private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
-        val itemsPerFile =
-            items
-                .filter { it.encryptionType != EncryptionType.DEVICE_PROTECTED }
-                .groupBy { it.encryptedPrefs }
-                .toMutableMap()
-
-        val bootAwareUpdates = items.filter { it.encryptionType == EncryptionType.DEVICE_PROTECTED }
-        if (bootAwareUpdates.isNotEmpty()) {
-            itemsPerFile[bootAwarePrefs] = bootAwareUpdates
-        }
-
-        return itemsPerFile.map { (prefs, items) ->
-            prefs.edit().also { editor ->
-                items.forEach { item -> editor.remove(item.sharedPrefKey) }
-            }
-        }
-    }
-
-    override fun close() {}
-}
-
 abstract class Item {
     abstract val sharedPrefKey: String
     abstract val isBackedUp: Boolean