Migrate "isRotationAllowed" setting to LauncherPrefs.

This semi-complicated case requires use of a context in order to get the
default value. That is why it was migrated by itself.

Bug: 261635315
Test: Manually tested all the base functionality of the launcher.
Change-Id: I40d8ccd414402b55921a0a224264414d3245871b
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index db8c7f2..6722f3d 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -22,7 +22,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
-import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
+import static com.android.launcher3.LauncherPrefs.ALLOW_ROTATION;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
 import static com.android.quickstep.BaseActivityInterface.getTaskDimension;
@@ -116,7 +116,6 @@
                     | FLAG_SWIPE_UP_NOT_RUNNING;
 
     private final Context mContext;
-    private final SharedPreferences mSharedPrefs;
     private final OrientationEventListener mOrientationListener;
     private final SettingsCache mSettingsCache;
     private final SettingsCache.OnChangeListener mRotationChangeListener =
@@ -139,7 +138,6 @@
     public RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy,
             IntConsumer rotationChangeListener) {
         mContext = context;
-        mSharedPrefs = LauncherPrefs.getPrefs(context);
         mOrientationListener = new OrientationEventListener(context) {
             @Override
             public void onOrientationChanged(int degrees) {
@@ -278,7 +276,7 @@
 
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
-        if (ALLOW_ROTATION_PREFERENCE_KEY.equals(s)) {
+        if (LauncherPrefs.ALLOW_ROTATION.getSharedPrefKey().equals(s)) {
             updateHomeRotationSetting();
         }
     }
@@ -289,7 +287,7 @@
     }
 
     private void updateHomeRotationSetting() {
-        boolean homeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false);
+        boolean homeRotationEnabled = LauncherPrefs.get(mContext).get(ALLOW_ROTATION);
         setFlag(FLAG_HOME_ROTATION_ALLOWED_IN_PREFS, homeRotationEnabled);
         SystemUiProxy.INSTANCE.get(mContext).setHomeRotationEnabled(homeRotationEnabled);
     }
@@ -303,13 +301,13 @@
     }
 
     private void initMultipleOrientationListeners() {
-        mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
+        LauncherPrefs.get(mContext).addListener(this, ALLOW_ROTATION);
         mSettingsCache.register(ROTATION_SETTING_URI, mRotationChangeListener);
         updateAutoRotateSetting();
     }
 
     private void destroyMultipleOrientationListeners() {
-        mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
+        LauncherPrefs.get(mContext).removeListener(this, ALLOW_ROTATION);
         mSettingsCache.unregister(ROTATION_SETTING_URI, mRotationChangeListener);
     }
 
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 1fb2dce..2e07e30 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -8,6 +8,8 @@
 import com.android.launcher3.model.DeviceGridState
 import com.android.launcher3.pm.InstallSessionHelper
 import com.android.launcher3.provider.RestoreDbTask
+import com.android.launcher3.states.RotationHelper
+import com.android.launcher3.util.DisplayController
 import com.android.launcher3.util.MainThreadInitializedObject
 import com.android.launcher3.util.Themes
 
@@ -17,31 +19,36 @@
  */
 class LauncherPrefs(private val context: Context) {
 
+    /** Wrapper around `getInner` for a `ContextualItem` */
+    fun <T : Any> get(item: ContextualItem<T>): T =
+        getInner(item, item.defaultValueFromContext(context))
+
+    /** Wrapper around `getInner` for an `Item` */
+    fun <T : Any> 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")
-    fun <T : Any> get(item: Item<T>): T {
+    private fun <T : Any> getInner(item: Item, default: T): T {
         val sp = context.getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE)
 
-        return when (item.defaultValue::class.java) {
-            String::class.java -> sp.getString(item.sharedPrefKey, item.defaultValue as String)
+        return when (default::class.java) {
+            String::class.java -> sp.getString(item.sharedPrefKey, default as String)
             Boolean::class.java,
-            java.lang.Boolean::class.java ->
-                sp.getBoolean(item.sharedPrefKey, item.defaultValue as Boolean)
+            java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
             Int::class.java,
-            java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, item.defaultValue as Int)
+            java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int)
             Float::class.java,
-            java.lang.Float::class.java ->
-                sp.getFloat(item.sharedPrefKey, item.defaultValue as Float)
+            java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
             Long::class.java,
-            java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, item.defaultValue as Long)
-            Set::class.java -> sp.getStringSet(item.sharedPrefKey, item.defaultValue as Set<String>)
+            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.defaultValue::class.java}" +
+                    "item type: ${default::class.java}" +
                         " is not compatible with sharedPref methods"
                 )
         }
@@ -57,14 +64,14 @@
      * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
      * provided item configurations.
      */
-    fun put(vararg itemsToValues: Pair<Item<*>, Any>): Unit =
+    fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
         prepareToPutValues(itemsToValues).forEach { it.apply() }
 
     /**
      * Stores the value provided in `SharedPreferences` according to the item configuration provided
      * It is asynchronous, so the caller can't assume that the value put is immediately available.
      */
-    fun <T : Any> put(item: Item<T>, value: T): Unit =
+    fun <T : Any> put(item: Item, value: T): Unit =
         context
             .getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE)
             .edit()
@@ -75,7 +82,7 @@
      * Synchronously stores all the values provided according to their associated Item
      * configuration.
      */
-    fun putSync(vararg itemsToValues: Pair<Item<*>, Any>): Unit =
+    fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
         prepareToPutValues(itemsToValues).forEach { it.commit() }
 
     /**
@@ -86,7 +93,7 @@
      * items given as part of the itemsToValues parameter
      */
     private fun prepareToPutValues(
-        itemsToValues: Array<out Pair<Item<*>, Any>>
+        itemsToValues: Array<out Pair<Item, Any>>
     ): List<SharedPreferences.Editor> =
         itemsToValues
             .groupBy { it.first.sharedPrefFile }
@@ -108,7 +115,7 @@
      */
     @Suppress("UNCHECKED_CAST")
     private fun SharedPreferences.Editor.putValue(
-        item: Item<*>,
+        item: Item,
         value: Any
     ): SharedPreferences.Editor =
         when (value::class.java) {
@@ -124,8 +131,7 @@
             Set::class.java -> putStringSet(item.sharedPrefKey, value as Set<String>)
             else ->
                 throw IllegalArgumentException(
-                    "item type: " +
-                        "${item.defaultValue!!::class} is not compatible with sharedPref methods"
+                    "item type: ${value::class} is not compatible with sharedPref methods"
                 )
         }
 
@@ -134,7 +140,7 @@
      * `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: OnSharedPreferenceChangeListener, vararg items: Item<*>) {
+    fun addListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
         items
             .map { it.sharedPrefFile }
             .distinct()
@@ -149,7 +155,7 @@
      * 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: OnSharedPreferenceChangeListener, vararg items: Item<*>) {
+    fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
         // If a listener is not registered to a SharedPreference, unregistering it does nothing
         items
             .map { it.sharedPrefFile }
@@ -165,7 +171,7 @@
      * Checks if all the provided [Item] have values stored in their corresponding
      * `SharedPreferences` files.
      */
-    fun has(vararg items: Item<*>): Boolean {
+    fun has(vararg items: Item): Boolean {
         items
             .groupBy { it.sharedPrefFile }
             .forEach { (file, itemsSublist) ->
@@ -179,17 +185,17 @@
     /**
      * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
      */
-    fun remove(vararg items: Item<*>) = prepareToRemove(items).forEach { it.apply() }
+    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() }
+    fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
 
     /**
      * Creates `SharedPreferences.Editor` transactions for removing all the provided [Item] values
      * from their respective `SharedPreferences` files. These returned `Editors` can then be
      * committed or applied for synchronous or async behavior.
      */
-    private fun prepareToRemove(items: Array<out Item<*>>): List<SharedPreferences.Editor> =
+    private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> =
         items
             .groupBy { it.sharedPrefFile }
             .map { (file, items) ->
@@ -218,16 +224,32 @@
             backedUpItem(RestoreDbTask.RESTORED_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE)
         @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
         @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
+        @JvmField
+        val ALLOW_ROTATION =
+            backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY) {
+                RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
+            }
 
         @VisibleForTesting
         @JvmStatic
-        fun <T> backedUpItem(sharedPrefKey: String, defaultValue: T): Item<T> =
-            Item(sharedPrefKey, LauncherFiles.SHARED_PREFERENCES_KEY, defaultValue)
+        fun <T> backedUpItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
+            ConstantItem(sharedPrefKey, LauncherFiles.SHARED_PREFERENCES_KEY, defaultValue)
+
+        @JvmStatic
+        fun <T> backedUpItem(
+            sharedPrefKey: String,
+            defaultValueFromContext: (c: Context) -> T
+        ): ContextualItem<T> =
+            ContextualItem(
+                sharedPrefKey,
+                LauncherFiles.SHARED_PREFERENCES_KEY,
+                defaultValueFromContext
+            )
 
         @VisibleForTesting
         @JvmStatic
-        fun <T> nonRestorableItem(sharedPrefKey: String, defaultValue: T): Item<T> =
-            Item(sharedPrefKey, LauncherFiles.DEVICE_PREFERENCES_KEY, defaultValue)
+        fun <T> nonRestorableItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
+            ConstantItem(sharedPrefKey, LauncherFiles.DEVICE_PREFERENCES_KEY, defaultValue)
 
         @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
         @JvmStatic
@@ -251,6 +273,30 @@
     }
 }
 
-data class Item<T>(val sharedPrefKey: String, val sharedPrefFile: String, val defaultValue: T) {
-    fun to(value: T): Pair<Item<T>, T> = Pair(this, value)
+abstract class Item {
+    abstract val sharedPrefKey: String
+    abstract val sharedPrefFile: String
+
+    fun <T> to(value: T): Pair<Item, T> = Pair(this, value)
+}
+
+data class ConstantItem<T>(
+    override val sharedPrefKey: String,
+    override val sharedPrefFile: String,
+    val defaultValue: T
+) : Item()
+
+data class ContextualItem<T>(
+    override val sharedPrefKey: String,
+    override val sharedPrefFile: String,
+    private val defaultSupplier: (c: Context) -> T
+) : Item() {
+    private var default: T? = null
+
+    fun defaultValueFromContext(context: Context): T {
+        if (default == null) {
+            default = defaultSupplier(context)
+        }
+        return default!!
+    }
 }
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index e5b4eba..7b4e248 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -20,6 +20,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
 
+import static com.android.launcher3.LauncherPrefs.ALLOW_ROTATION;
 import static com.android.launcher3.Utilities.dpiFromPx;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
@@ -63,7 +64,6 @@
 
     @Nullable
     private BaseActivity mActivity;
-    private SharedPreferences mSharedPrefs = null;
     private final Handler mRequestOrientationHandler;
 
     private boolean mIgnoreAutoRotateSettings;
@@ -103,17 +103,10 @@
         // On large devices we do not handle auto-rotate differently.
         mIgnoreAutoRotateSettings = ignoreAutoRotateSettings;
         if (!mIgnoreAutoRotateSettings) {
-            if (mSharedPrefs == null) {
-                mSharedPrefs = LauncherPrefs.getPrefs(mActivity);
-                mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
-            }
-            mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
-                    getAllowRotationDefaultValue(info));
+            mHomeRotationEnabled = LauncherPrefs.get(mActivity).get(ALLOW_ROTATION);
+            LauncherPrefs.get(mActivity).addListener(this, ALLOW_ROTATION);
         } else {
-            if (mSharedPrefs != null) {
-                mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
-                mSharedPrefs = null;
-            }
+            LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION);
         }
     }
 
@@ -121,8 +114,7 @@
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
         if (mDestroyed || mIgnoreAutoRotateSettings) return;
         boolean wasRotationEnabled = mHomeRotationEnabled;
-        mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
-                getAllowRotationDefaultValue(mActivity.getDeviceProfile().getDisplayInfo()));
+        mHomeRotationEnabled = LauncherPrefs.get(mActivity).get(ALLOW_ROTATION);
         if (mHomeRotationEnabled != wasRotationEnabled) {
             notifyChange();
         }
@@ -179,10 +171,8 @@
         if (!mDestroyed) {
             mDestroyed = true;
             DisplayController.INSTANCE.get(mActivity).removeChangeListener(this);
+            LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION);
             mActivity = null;
-            if (mSharedPrefs != null) {
-                mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
-            }
         }
     }
 
diff --git a/tests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
index 151abf1..31e8d30 100644
--- a/tests/src/com/android/launcher3/LauncherPrefsTest.kt
+++ b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
@@ -13,6 +13,7 @@
 private val TEST_BOOLEAN_ITEM = LauncherPrefs.nonRestorableItem("1", false)
 private val TEST_STRING_ITEM = LauncherPrefs.nonRestorableItem("2", "( ͡❛ ͜ʖ ͡❛)")
 private val TEST_INT_ITEM = LauncherPrefs.nonRestorableItem("3", -1)
+private val TEST_CONTEXTUAL_ITEM = LauncherPrefs.backedUpItem("4") { true }
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -145,4 +146,9 @@
             assertThat(get(TEST_BOOLEAN_ITEM)).isEqualTo(TEST_BOOLEAN_ITEM.defaultValue)
         }
     }
+
+    @Test
+    fun get_contextualItem_returnsCorrectDefault() {
+        assertThat(launcherPrefs.get(TEST_CONTEXTUAL_ITEM)).isTrue()
+    }
 }