Make flags UI available on release build of launcher

The UI will only be shown on eng/userdebug platform builds.

Bug: 117223984
Change-Id: I27843f2d856a4a19f3fe53c4d306606eaa5714a2
diff --git a/go/src_flags/com/android/launcher3/config/FeatureFlags.java b/go/src_flags/com/android/launcher3/config/FeatureFlags.java
index 6be9de8..a90808c 100644
--- a/go/src_flags/com/android/launcher3/config/FeatureFlags.java
+++ b/go/src_flags/com/android/launcher3/config/FeatureFlags.java
@@ -22,14 +22,10 @@
  * Defines a set of flags used to control various launcher behaviors
  */
 public final class FeatureFlags extends BaseFlags {
-    private static FeatureFlags instance = new FeatureFlags();
-
-    public static FeatureFlags getInstance(Context context) {
-        return instance;
+    private FeatureFlags() {
+        // Prevent instantiation
     }
 
-    private FeatureFlags() {}
-
     // Features to control Launcher3Go behavior
     public static final boolean GO_DISABLE_WIDGETS = true;
     public static final boolean LAUNCHER3_SPRING_ICONS = false;
diff --git a/res/xml/flag_preferences.xml b/res/xml/flag_preferences.xml
new file mode 100644
index 0000000..aea1a6a
--- /dev/null
+++ b/res/xml/flag_preferences.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    android:key="feature_flags"
+    android:persistent="false">
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 3bba73a..1df7c2f 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -52,4 +52,10 @@
         android:defaultValue=""
         android:persistent="false" />
 
+    <PreferenceScreen
+        android:fragment="com.android.launcher3.config.FlagTogglerPreferenceFragment"
+        android:key="flag_toggler"
+        android:persistent="false"
+        android:title="Feature flags"/>
+
 </PreferenceScreen>
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4b181d8..55d6984 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1753,12 +1753,12 @@
     @Override
     public void bindScreens(IntArray orderedScreenIds) {
         // Make sure the first screen is always at the start.
-        if (FeatureFlags.getInstance(this).isQsbOnFirstScreenEnabled() &&
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() &&
                 orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
             orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
             orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
             LauncherModel.updateWorkspaceScreenOrder(this, orderedScreenIds);
-        } else if (!FeatureFlags.getInstance(this).isQsbOnFirstScreenEnabled()
+        } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()
                 && orderedScreenIds.isEmpty()) {
             // If there are no screens, we need to have an empty screen
             mWorkspace.addExtraEmptyScreen();
@@ -1775,8 +1775,7 @@
         int count = orderedScreenIds.size();
         for (int i = 0; i < count; i++) {
             int screenId = orderedScreenIds.get(i);
-            if (!FeatureFlags.getInstance(this).isQsbOnFirstScreenEnabled()
-                    || screenId != Workspace.FIRST_SCREEN_ID) {
+            if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || screenId != Workspace.FIRST_SCREEN_ID) {
                 // No need to bind the first screen, as its always bound.
                 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
             }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index a423a90..7d62ada 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -70,9 +70,6 @@
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
 
 public class LauncherProvider extends ContentProvider {
     private static final String TAG = "LauncherProvider";
@@ -793,7 +790,7 @@
                     convertShortcutsToLauncherActivities(db);
                 case 26:
                     // QSB was moved to the grid. Clear the first row on screen 0.
-                    if (FeatureFlags.getInstance(mContext).isQsbOnFirstScreenEnabled() &&
+                    if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() &&
                             !LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) {
                         break;
                     }
diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java
index 0028f97..a18dfde 100644
--- a/src/com/android/launcher3/MainProcessInitializer.java
+++ b/src/com/android/launcher3/MainProcessInitializer.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.IconShapeOverride;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.util.ResourceBasedOverride;
@@ -35,6 +36,7 @@
 
     protected void init(Context context) {
         FileLog.setDir(context.getApplicationContext().getFilesDir());
+        FeatureFlags.initialize(context);
         IconShapeOverride.apply(context);
         SessionCommitReceiver.applyDefaultUserPrefs(context);
     }
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index 1f80226..60edcda 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -43,6 +43,7 @@
 import android.widget.Adapter;
 import android.widget.ListView;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.IconShapeOverride;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.util.ListViewHighlighter;
@@ -57,6 +58,8 @@
 public class SettingsActivity extends Activity
         implements PreferenceFragment.OnPreferenceStartFragmentCallback {
 
+    private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
+
     private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging";
     /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
     public static final String NOTIFICATION_BADGING = "notification_badging";
@@ -126,6 +129,12 @@
             getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
             addPreferencesFromResource(R.xml.launcher_preferences);
 
+            // Only show flag toggler UI if this build variant implements that.
+            Preference flagToggler = findPreference(FLAGS_PREFERENCE_KEY);
+            if (flagToggler != null && !FeatureFlags.showFlagTogglerUi()) {
+                getPreferenceScreen().removePreference(flagToggler);
+            }
+
             ContentResolver resolver = getActivity().getContentResolver();
 
             ButtonPreference iconBadgingPref =
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 5e1c54c..11e601c 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -480,7 +480,7 @@
      * @param qsb an existing qsb to recycle or null.
      */
     public void bindAndInitFirstWorkspaceScreen(View qsb) {
-        if (!FeatureFlags.getInstance(getContext()).isQsbOnFirstScreenEnabled()) {
+        if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
             return;
         }
         // Add the first page
@@ -779,9 +779,7 @@
             int id = mWorkspaceScreens.keyAt(i);
             CellLayout cl = mWorkspaceScreens.valueAt(i);
             // FIRST_SCREEN_ID can never be removed.
-            boolean qsbFirstScreenEnabled = 
-                    FeatureFlags.getInstance(getContext()).isQsbOnFirstScreenEnabled();
-            if ((!qsbFirstScreenEnabled || id > FIRST_SCREEN_ID)
+            if ((!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || id > FIRST_SCREEN_ID)
                     && cl.getShortcutsAndWidgets().getChildCount() == 0) {
                 removeScreens.add(id);
             }
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index de842f5..dc60c8f 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -16,6 +16,21 @@
 
 package com.android.launcher3.config;
 
+import static androidx.core.util.Preconditions.checkNotNull;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.Keep;
+
+import com.android.launcher3.Utilities;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
 /**
  * Defines a set of flags used to control various launcher behaviors.
  *
@@ -23,11 +38,21 @@
  *
  * <p>This class is kept package-private to prevent direct access.
  */
+@Keep
 abstract class BaseFlags {
 
-    private static final String TAG = "FeatureFlags";
+    private static final Object sLock = new Object();
+    @GuardedBy("sLock")
+    private static final List<TogglableFlag> sFlags = new ArrayList<>();
+
+    static final String FLAGS_PREF_NAME = "featureFlags";
 
     BaseFlags() {
+        throw new UnsupportedOperationException("Don't instantiate BaseFlags");
+    }
+
+    public static boolean showFlagTogglerUi() {
+        return Utilities.IS_DEBUG_DEVICE;
     }
 
     public static final boolean IS_DOGFOOD_BUILD = false;
@@ -36,10 +61,12 @@
     // When enabled the promise icon is visible in all apps while installation an app.
     public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
 
-    /** Feature flag to enable moving the QSB on the 0th screen of the workspace. */
-    public boolean isQsbOnFirstScreenEnabled() {
-        return true;
-    }
+    public static final TogglableFlag QSB_ON_FIRST_SCREEN = new TogglableFlag("QSB_ON_FIRST_SCREEN",
+            true,
+            "Enable moving the QSB on the 0th screen of the workspace");
+
+    public static final TogglableFlag EXAMPLE_FLAG = new TogglableFlag("EXAMPLE_FLAG", true,
+            "An example flag that doesn't do anything. Useful for testing");
 
     //Feature flag to enable pulling down navigation shade from workspace.
     public static final boolean PULL_DOWN_STATUS_BAR = true;
@@ -56,4 +83,110 @@
     // When true, overview shows screenshots in the orientation they were taken rather than
     // trying to make them fit the orientation the device is in.
     public static final boolean OVERVIEW_USE_SCREENSHOT_ORIENTATION = true;
+
+    public static void initialize(Context context) {
+        // Avoid the disk read for builds without the flags UI.
+        if (showFlagTogglerUi()) {
+            SharedPreferences sharedPreferences =
+                    context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
+            synchronized (sLock) {
+                for (TogglableFlag flag : sFlags) {
+                    flag.currentValue = sharedPreferences.getBoolean(flag.key, flag.defaultValue);
+                }
+            }
+        } else {
+            synchronized (sLock) {
+                for (TogglableFlag flag : sFlags) {
+                    flag.currentValue = flag.defaultValue;
+                }
+            }
+        }
+    }
+
+    static List<TogglableFlag> getTogglableFlags() {
+        // By Java Language Spec 12.4.2
+        // https://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2, the
+        // TogglableFlag instances on BaseFlags will be created before those on the FeatureFlags
+        // subclass. This code handles flags that are redeclared in FeatureFlags, ensuring the
+        // FeatureFlags one takes priority.
+        SortedMap<String, TogglableFlag> flagsByKey = new TreeMap<>();
+        synchronized (sLock) {
+            for (TogglableFlag flag : sFlags) {
+                flagsByKey.put(flag.key, flag);
+            }
+        }
+        return new ArrayList<>(flagsByKey.values());
+    }
+
+    public static final class TogglableFlag {
+        private final String key;
+        private final boolean defaultValue;
+        private final String description;
+        private boolean currentValue;
+
+        TogglableFlag(
+                String key,
+                boolean defaultValue,
+                String description) {
+            this.key = checkNotNull(key);
+            this.defaultValue = defaultValue;
+            this.description = checkNotNull(description);
+            synchronized (sLock) {
+                sFlags.add(this);
+            }
+        }
+
+        String getKey() {
+            return key;
+        }
+
+        boolean getDefaultValue() {
+            return defaultValue;
+        }
+
+        /** Returns the value of the flag at process start, including any overrides present. */
+        public boolean get() {
+            return currentValue;
+        }
+
+        String getDescription() {
+            return description;
+        }
+
+        @Override
+        public String toString() {
+            return "TogglableFlag{"
+                    + "key=" + key + ", "
+                    + "defaultValue=" + defaultValue + ", "
+                    + "description=" + description
+                    + "}";
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o instanceof TogglableFlag) {
+                TogglableFlag that = (TogglableFlag) o;
+                return (this.key.equals(that.getKey()))
+                        && (this.defaultValue == that.getDefaultValue())
+                        && (this.description.equals(that.getDescription()));
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            int h$ = 1;
+            h$ *= 1000003;
+            h$ ^= key.hashCode();
+            h$ *= 1000003;
+            h$ ^= defaultValue ? 1231 : 1237;
+            h$ *= 1000003;
+            h$ ^= description.hashCode();
+            return h$;
+        }
+    }
+
 }
diff --git a/src/com/android/launcher3/config/FlagTogglerPreferenceFragment.java b/src/com/android/launcher3/config/FlagTogglerPreferenceFragment.java
new file mode 100644
index 0000000..0a1fd2f
--- /dev/null
+++ b/src/com/android/launcher3/config/FlagTogglerPreferenceFragment.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.config;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Process;
+import android.preference.PreferenceDataStore;
+import android.preference.PreferenceFragment;
+import android.preference.SwitchPreference;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import com.android.launcher3.R;
+import com.android.launcher3.config.BaseFlags.TogglableFlag;
+
+/**
+ * Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
+ */
+public final class FlagTogglerPreferenceFragment extends PreferenceFragment {
+    private static final String TAG = "FlagTogglerPrefFrag";
+
+    private SharedPreferences mSharedPreferences;
+    private MenuItem saveButton;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.flag_preferences);
+        mSharedPreferences = getContext().getSharedPreferences(
+                FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE);
+
+        // For flag overrides we only want to store when the engineer chose to override the
+        // flag with a different value than the default. That way, when we flip flags in
+        // future, engineers will pick up the new value immediately. To accomplish this, we use a
+        // custom preference data store.
+        getPreferenceManager().setPreferenceDataStore(new PreferenceDataStore() {
+            @Override
+            public void putBoolean(String key, boolean value) {
+                for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+                    if (flag.getKey().equals(key)) {
+                        if (value == flag.getDefaultValue()) {
+                            mSharedPreferences.edit().remove(key).apply();
+                        } else {
+                            mSharedPreferences.edit().putBoolean(key, value).apply();
+                        }
+                    }
+                }
+            }
+        });
+
+        for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+            SwitchPreference switchPreference = new SwitchPreference(getContext());
+            switchPreference.setKey(flag.getKey());
+            switchPreference.setDefaultValue(flag.getDefaultValue());
+            switchPreference.setChecked(getFlagStateFromSharedPrefs(flag));
+            switchPreference.setTitle(flag.getKey());
+            switchPreference.setSummaryOn(flag.getDefaultValue() ? "" : "overridden");
+            switchPreference.setSummaryOff(flag.getDefaultValue() ? "overridden" : "");
+            getPreferenceScreen().addPreference(switchPreference);
+        }
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        saveButton = menu.add("Apply");
+        saveButton.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item == saveButton) {
+            mSharedPreferences.edit().commit();
+            Log.e(TAG,
+                    "Killing launcher process " + Process.myPid() + " to apply new flag values");
+            System.exit(0);
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public void onStop() {
+        boolean anyChanged = false;
+        for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+            anyChanged = anyChanged ||
+                    getFlagStateFromSharedPrefs(flag) != flag.get();
+        }
+
+        if (anyChanged) {
+            Toast.makeText(
+                    getContext(),
+                    "Flag won't be applied until you restart launcher",
+                    Toast.LENGTH_LONG).show();
+        }
+        super.onStop();
+    }
+
+    private boolean getFlagStateFromSharedPrefs(TogglableFlag flag) {
+        return mSharedPreferences.getBoolean(flag.getKey(), flag.getDefaultValue());
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index 0115fd9..2c1aa74 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -12,6 +12,7 @@
 import android.graphics.Point;
 import android.net.Uri;
 import android.util.Log;
+
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
@@ -109,6 +110,7 @@
 
     /**
      * Applied all the pending DB operations
+     *
      * @return true if any DB operation was commited.
      */
     private boolean applyOperations() throws Exception {
@@ -135,6 +137,7 @@
      * entries is more than what can fit in the new hotseat, we drop the entries with least weight.
      * For weight calculation {@see #WT_SHORTCUT}, {@see #WT_APPLICATION}
      * & {@see #WT_FOLDER_FACTOR}.
+     *
      * @return true if any DB change was made
      */
     protected boolean migrateHotseat() throws Exception {
@@ -235,7 +238,8 @@
                 int screenId = allScreens.get(i);
                 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
                 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
-                mUpdateOperations.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
+                mUpdateOperations.add(ContentProviderOperation.newInsert(uri).withValues(
+                        v).build());
             }
         }
         return applyOperations();
@@ -254,8 +258,7 @@
     protected void migrateScreen(int screenId) {
         // If we are migrating the first screen, do not touch the first row.
         int startY =
-                (FeatureFlags.getInstance(mContext).isQsbOnFirstScreenEnabled()
-                        && screenId == Workspace.FIRST_SCREEN_ID)
+                (FeatureFlags.QSB_ON_FIRST_SCREEN.get() && screenId == Workspace.FIRST_SCREEN_ID)
                 ? 1 : 0;
 
         ArrayList<DbEntry> items = loadWorkspaceEntries(screenId);
@@ -280,9 +283,11 @@
             for (int y = mSrcY - 1; y >= startY; y--) {
                 // Use a deep copy when trying out a particular combination as it can change
                 // the underlying object.
-                ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, startY, deepCopy(items), outLoss);
+                ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, startY, deepCopy(items),
+                        outLoss);
 
-                if ((outLoss[0] < removeWt) || ((outLoss[0] == removeWt) && (outLoss[1] < moveWt))) {
+                if ((outLoss[0] < removeWt) || ((outLoss[0] == removeWt) && (outLoss[1]
+                        < moveWt))) {
                     removeWt = outLoss[0];
                     moveWt = outLoss[1];
                     removedCol = mShouldRemoveX ? x : removedCol;
@@ -363,6 +368,7 @@
 
     /**
      * Tries the remove the provided row and column.
+     *
      * @param items all the items on the screen under operation
      * @param outLoss array of size 2. The first entry is filled with weight loss, and the second
      * with the overall item movement.
@@ -438,6 +444,7 @@
 
         /**
          * Recursively finds a placement for the provided items.
+         *
          * @param index the position in {@link #itemsToPlace} to start looking at.
          * @param weightLoss total weight loss upto this point
          * @param moveCost total move cost upto this point
@@ -550,7 +557,8 @@
                     for (int x = 0; x < mTrgX; x++) {
                         if (!occupied.cells[x][y]) {
                             int dist = ignoreMove ? 0 :
-                                ((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY - y));
+                                    ((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY
+                                            - y));
                             if (dist < newDistance) {
                                 newX = x;
                                 newY = y;
@@ -815,7 +823,8 @@
 
         public float weight;
 
-        public DbEntry() { }
+        public DbEntry() {
+        }
 
         public DbEntry copy() {
             DbEntry entry = new DbEntry();
@@ -887,6 +896,7 @@
 
     /**
      * Migrates the workspace and hotseat in case their sizes changed.
+     *
      * @return false if the migration failed.
      */
     public static boolean migrateGridIfNeeded(Context context) {
@@ -896,7 +906,8 @@
         String gridSizeString = getPointString(idp.numColumns, idp.numRows);
 
         if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) &&
-                idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)) {
+                idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
+                        idp.numHotseatIcons)) {
             // Skip if workspace and hotseat sizes have not changed.
             return true;
         }
@@ -907,7 +918,8 @@
 
             HashSet<String> validPackages = getValidPackages(context);
             // Hotseat
-            int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons);
+            int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
+                    idp.numHotseatIcons);
             if (srcHotseatCount != idp.numHotseatIcons) {
                 // Migrate hotseat.
 
@@ -920,7 +932,8 @@
             Point sourceSize = parsePoint(prefs.getString(
                     KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
 
-            if (new MultiStepMigrationTask(validPackages, context).migrate(sourceSize, targetSize)) {
+            if (new MultiStepMigrationTask(validPackages, context).migrate(sourceSize,
+                    targetSize)) {
                 dbChanged = true;
             }
 
@@ -970,9 +983,11 @@
 
     /**
      * Removes any broken item from the hotseat.
+     *
      * @return a map with occupied hotseat position set to non-null value.
      */
-    public static IntSparseArrayMap<Object> removeBrokenHotseatItems(Context context) throws Exception {
+    public static IntSparseArrayMap<Object> removeBrokenHotseatItems(Context context)
+            throws Exception {
         GridSizeMigrationTask task = new GridSizeMigrationTask(
                 context, LauncherAppState.getIDP(context), getValidPackages(context),
                 Integer.MAX_VALUE, Integer.MAX_VALUE);
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index bb3a760..94cf5c2 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -441,7 +441,7 @@
                 // Mark the first row as occupied (if the feature is enabled)
                 // in order to account for the QSB.
                 screen.markCells(0, 0, countX + 1, 1,
-                    FeatureFlags.getInstance(mContext).isQsbOnFirstScreenEnabled());
+                    FeatureFlags.QSB_ON_FIRST_SCREEN.get());
             }
             occupied.put(item.screenId, screen);
         }
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
index 4edd30f..e1b2698 100644
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ b/src/com/android/launcher3/provider/ImportDataTask.java
@@ -136,7 +136,7 @@
                 .getSerialNumberForUser(Process.myUserHandle()));
 
         boolean createEmptyRowOnFirstScreen;
-        if (FeatureFlags.getInstance(mContext).isQsbOnFirstScreenEnabled()) {
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
             try (Cursor c = mContext.getContentResolver().query(mOtherFavoritesUri, null,
                     // get items on the first row of the first screen
                     "profileId = ? AND container = -100 AND screen = ? AND cellY = 0",
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index 82ab15c..ac1fafb 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -213,7 +213,7 @@
         }
 
         public boolean isQsbEnabled() {
-            return FeatureFlags.getInstance(getContext()).isQsbOnFirstScreenEnabled();
+            return FeatureFlags.QSB_ON_FIRST_SCREEN.get();
         }
 
         protected Bundle createBindOptions() {
diff --git a/src_flags/com/android/launcher3/config/FeatureFlags.java b/src_flags/com/android/launcher3/config/FeatureFlags.java
index f02f227..73c6996 100644
--- a/src_flags/com/android/launcher3/config/FeatureFlags.java
+++ b/src_flags/com/android/launcher3/config/FeatureFlags.java
@@ -22,11 +22,7 @@
  * Defines a set of flags used to control various launcher behaviors
  */
 public final class FeatureFlags extends BaseFlags {
-    private static FeatureFlags instance = new FeatureFlags();
-
-    public static FeatureFlags getInstance(Context context) {
-        return instance;
+    private FeatureFlags() {
+        // Prevent instantiation
     }
-
-    private FeatureFlags() {}
 }