Merge "Adding test methods" into ub-launcher3-master
diff --git a/build.gradle b/build.gradle
index b59f264..1b9df53 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@
         google()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.2.0-rc03'
+        classpath 'com.android.tools.build:gradle:3.2.1'
         classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6'
     }
 }
@@ -16,7 +16,7 @@
 
 android {
     compileSdkVersion 28
-    buildToolsVersion '28.0.2'
+    buildToolsVersion '28.0.3'
 
     defaultConfig {
         minSdkVersion 21
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java
index e9fac26..eaf4183 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java
@@ -21,7 +21,11 @@
 import com.android.launcher3.Utilities;
 import com.android.systemui.shared.plugins.PluginEnabler;
 
-public class PluginEnablerImpl implements PluginEnabler {
+import androidx.preference.PreferenceDataStore;
+
+public class PluginEnablerImpl extends PreferenceDataStore implements PluginEnabler {
+
+    private static final String PREFIX_PLUGIN_ENABLED = "PLUGIN_ENABLED_";
 
     final private SharedPreferences mSharedPrefs;
 
@@ -31,15 +35,25 @@
 
     @Override
     public void setEnabled(ComponentName component, boolean enabled) {
-        mSharedPrefs.edit().putBoolean(toPrefString(component), enabled).apply();
+        putBoolean(pluginEnabledKey(component), enabled);
     }
 
     @Override
     public boolean isEnabled(ComponentName component) {
-        return mSharedPrefs.getBoolean(toPrefString(component), true);
+        return getBoolean(pluginEnabledKey(component), true);
     }
 
-    private String toPrefString(ComponentName component) {
-        return "PLUGIN_ENABLED_" + component.flattenToString();
+    @Override
+    public void putBoolean(String key, boolean value) {
+        mSharedPrefs.edit().putBoolean(key, value).apply();
+    }
+
+    @Override
+    public boolean getBoolean(String key, boolean defValue) {
+        return mSharedPrefs.getBoolean(key, defValue);
+    }
+
+    static String pluginEnabledKey(ComponentName cn) {
+        return PREFIX_PLUGIN_ENABLED + cn.flattenToString();
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
index 8a6aa05..910fa0d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
@@ -18,7 +18,6 @@
 import android.os.Looper;
 
 import com.android.launcher3.LauncherModel;
-import com.android.systemui.shared.plugins.PluginEnabler;
 import com.android.systemui.shared.plugins.PluginInitializer;
 
 public class PluginInitializerImpl implements PluginInitializer {
@@ -37,7 +36,7 @@
     }
 
     @Override
-    public PluginEnabler getPluginEnabler(Context context) {
+    public PluginEnablerImpl getPluginEnabler(Context context) {
         return new PluginEnablerImpl(context);
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index 88c362d..6e7c087 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -14,31 +14,37 @@
 
 package com.android.launcher3.uioverrides.plugins;
 
+import android.content.ComponentName;
 import android.content.Context;
 
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginEnabler;
-import com.android.systemui.shared.plugins.PluginInitializer;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginManagerImpl;
+import com.android.systemui.shared.plugins.PluginPrefs;
+
+import java.util.Set;
 
 public class PluginManagerWrapper {
 
     public static final MainThreadInitializedObject<PluginManagerWrapper> INSTANCE =
             new MainThreadInitializedObject<>(PluginManagerWrapper::new);
 
+    public static final String PLUGIN_CHANGED = PluginManager.PLUGIN_CHANGED;
+
+    private final Context mContext;
     private final PluginManager mPluginManager;
-    private final PluginEnabler mPluginEnabler;
+    private final PluginEnablerImpl mPluginEnabler;
 
     private PluginManagerWrapper(Context c) {
-        PluginInitializer pluginInitializer  = new PluginInitializerImpl();
+        mContext = c;
+        PluginInitializerImpl pluginInitializer  = new PluginInitializerImpl();
         mPluginManager = new PluginManagerImpl(c, pluginInitializer);
         mPluginEnabler = pluginInitializer.getPluginEnabler(c);
     }
 
-    PluginEnabler getPluginEnabler() {
+    public PluginEnablerImpl getPluginEnabler() {
         return mPluginEnabler;
     }
 
@@ -54,4 +60,19 @@
     public void removePluginListener(PluginListener<? extends Plugin> listener) {
         mPluginManager.removePluginListener(listener);
     }
+
+    public Set<String> getPluginActions() {
+        return new PluginPrefs(mContext).getPluginList();
+    }
+
+    /**
+     * Returns the string key used to store plugin enabled/disabled setting
+     */
+    public static String pluginEnabledKey(ComponentName cn) {
+        return PluginEnablerImpl.pluginEnabledKey(cn);
+    }
+
+    public static boolean hasPlugins(Context context) {
+        return PluginPrefs.hasPlugins(context);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginPreferencesFragment.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginPreferencesFragment.java
deleted file mode 100644
index 3da4f84..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginPreferencesFragment.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * 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.uioverrides.plugins;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.os.Bundle;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.preference.SwitchPreference;
-import android.provider.Settings;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.view.View;
-
-import com.android.launcher3.R;
-import com.android.systemui.shared.plugins.PluginEnabler;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.shared.plugins.PluginPrefs;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * This class is copied from System UI Tuner, except using our PluginEnablerImpl. The reason we
- * can't share a common base class in the shared lib is because the androidx preference dependency
- * interferes with our recyclerview and fragment dependencies.
- */
-public class PluginPreferencesFragment extends PreferenceFragment {
-    public static final String ACTION_PLUGIN_SETTINGS
-            = "com.android.systemui.action.PLUGIN_SETTINGS";
-
-    private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
-
-    private PluginPrefs mPluginPrefs;
-    private PluginEnabler mPluginEnabler;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
-        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        filter.addDataScheme("package");
-        getContext().registerReceiver(mReceiver, filter);
-        filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
-        getContext().registerReceiver(mReceiver, filter);
-
-        mPluginEnabler = PluginManagerWrapper.INSTANCE.get(getContext()).getPluginEnabler();
-        loadPrefs();
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        getContext().unregisterReceiver(mReceiver);
-    }
-
-    private void loadPrefs() {
-        PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(getContext());
-        screen.setOrderingAsAdded(false);
-        Context prefContext = getContext();
-        mPluginPrefs = new PluginPrefs(getContext());
-        PackageManager pm = getContext().getPackageManager();
-
-        Set<String> pluginActions = mPluginPrefs.getPluginList();
-        ArrayMap<String, ArraySet<String>> plugins = new ArrayMap<>();
-        for (String action : pluginActions) {
-            String name = toName(action);
-            List<ResolveInfo> result = pm.queryIntentServices(
-                    new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS);
-            for (ResolveInfo info : result) {
-                String packageName = info.serviceInfo.packageName;
-                if (!plugins.containsKey(packageName)) {
-                    plugins.put(packageName, new ArraySet<>());
-                }
-                plugins.get(packageName).add(name);
-            }
-        }
-
-        List<PackageInfo> apps = pm.getPackagesHoldingPermissions(new String[]{PLUGIN_PERMISSION},
-                PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES);
-        apps.forEach(app -> {
-            if (!plugins.containsKey(app.packageName)) return;
-            SwitchPreference pref = new PluginPreference(prefContext, app, mPluginEnabler);
-            pref.setSummary("Plugins: " + toString(plugins.get(app.packageName)));
-            screen.addPreference(pref);
-        });
-        setPreferenceScreen(screen);
-    }
-
-    private String toString(ArraySet<String> plugins) {
-        StringBuilder b = new StringBuilder();
-        for (String string : plugins) {
-            if (b.length() != 0) {
-                b.append(", ");
-            }
-            b.append(string);
-        }
-        return b.toString();
-    }
-
-    private String toName(String action) {
-        String str = action.replace("com.android.systemui.action.PLUGIN_", "");
-        StringBuilder b = new StringBuilder();
-        for (String s : str.split("_")) {
-            if (b.length() != 0) {
-                b.append(' ');
-            }
-            b.append(s.substring(0, 1));
-            b.append(s.substring(1).toLowerCase());
-        }
-        return b.toString();
-    }
-
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            loadPrefs();
-        }
-    };
-
-    private static class PluginPreference extends SwitchPreference {
-        private final boolean mHasSettings;
-        private final PackageInfo mInfo;
-        private final PluginEnabler mPluginEnabler;
-
-        public PluginPreference(Context prefContext, PackageInfo info, PluginEnabler pluginEnabler) {
-            super(prefContext);
-            PackageManager pm = prefContext.getPackageManager();
-            mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS)
-                    .setPackage(info.packageName), 0) != null;
-            mInfo = info;
-            mPluginEnabler = pluginEnabler;
-            setTitle(info.applicationInfo.loadLabel(pm));
-            setChecked(isPluginEnabled());
-            setWidgetLayoutResource(R.layout.switch_preference_with_settings);
-        }
-
-        private boolean isPluginEnabled() {
-            for (int i = 0; i < mInfo.services.length; i++) {
-                ComponentName componentName = new ComponentName(mInfo.packageName,
-                        mInfo.services[i].name);
-                if (!mPluginEnabler.isEnabled(componentName)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        @Override
-        protected boolean persistBoolean(boolean isEnabled) {
-            boolean shouldSendBroadcast = false;
-            for (int i = 0; i < mInfo.services.length; i++) {
-                ComponentName componentName = new ComponentName(mInfo.packageName,
-                        mInfo.services[i].name);
-
-                if (mPluginEnabler.isEnabled(componentName) != isEnabled) {
-                    mPluginEnabler.setEnabled(componentName, isEnabled);
-                    shouldSendBroadcast = true;
-                }
-            }
-            if (shouldSendBroadcast) {
-                final String pkg = mInfo.packageName;
-                final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
-                        pkg != null ? Uri.fromParts("package", pkg, null) : null);
-                getContext().sendBroadcast(intent);
-            }
-            setChecked(isEnabled);
-            return true;
-        }
-
-        @Override
-        protected void onBindView(View view) {
-            super.onBindView(view);
-            view.findViewById(R.id.settings).setVisibility(mHasSettings ? View.VISIBLE
-                    : View.GONE);
-            view.findViewById(R.id.divider).setVisibility(mHasSettings ? View.VISIBLE
-                    : View.GONE);
-            view.findViewById(R.id.settings).setOnClickListener(v -> {
-                ResolveInfo result = v.getContext().getPackageManager().resolveActivity(
-                        new Intent(ACTION_PLUGIN_SETTINGS).setPackage(
-                                mInfo.packageName), 0);
-                if (result != null) {
-                    v.getContext().startActivity(new Intent().setComponent(
-                            new ComponentName(result.activityInfo.packageName,
-                                    result.activityInfo.name)));
-                }
-            });
-            view.setOnLongClickListener(v -> {
-                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
-                intent.setData(Uri.fromParts("package", mInfo.packageName, null));
-                getContext().startActivity(intent);
-                return true;
-            });
-        }
-    }
-}
diff --git a/res/values/config.xml b/res/values/config.xml
index 946afec..5e83ab7 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -19,7 +19,7 @@
     <string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;end</string>
 
     <!-- String representing the fragment class for settings activity.-->
-    <string name="settings_fragment_name" translatable="false">com.android.launcher3.SettingsActivity$LauncherSettingsFragment</string>
+    <string name="settings_fragment_name" translatable="false">com.android.launcher3.settings.SettingsActivity$LauncherSettingsFragment</string>
 
     <!-- Values for icon shape overrides. These should correspond to entries defined
      in icon_shape_override_paths_names -->
@@ -105,7 +105,10 @@
     <item type="id" name="view_unhighlight_background" />
     <item type="id" name="view_highlighted" />
 
-<!-- Popup items -->
+    <!-- Menu id for feature flags -->
+    <item type="id" name="menu_apply_flags" />
+
+    <!-- Popup items -->
     <integer name="config_popupOpenCloseDuration">150</integer>
     <integer name="config_popupArrowOpenCloseDuration">40</integer>
     <integer name="config_removeNotificationViewDuration">300</integer>
diff --git a/res/xml/flag_preferences.xml b/res/xml/flag_preferences.xml
deleted file mode 100644
index aea1a6a..0000000
--- a/res/xml/flag_preferences.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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 c55cc49..2c86f8e 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -54,9 +54,9 @@
         android:persistent="false" />
 
     <androidx.preference.PreferenceScreen
-        android:fragment="com.android.launcher3.config.FlagTogglerPreferenceFragment"
-        android:key="flag_toggler"
+        android:key="pref_developer_options"
         android:persistent="false"
-        android:title="Feature flags"/>
+        android:title="Developer Options"
+        android:fragment="com.android.launcher3.settings.DeveloperOptionsFragment"/>
 
 </androidx.preference.PreferenceScreen>
diff --git a/src/com/android/launcher3/config/FlagTogglerPrefUi.java b/src/com/android/launcher3/config/FlagTogglerPrefUi.java
new file mode 100644
index 0000000..d3be51d
--- /dev/null
+++ b/src/com/android/launcher3/config/FlagTogglerPrefUi.java
@@ -0,0 +1,134 @@
+/*
+ * 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.Process;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import com.android.launcher3.R;
+import com.android.launcher3.config.BaseFlags.TogglableFlag;
+
+import androidx.preference.PreferenceDataStore;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.SwitchPreference;
+
+/**
+ * Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
+ */
+public final class FlagTogglerPrefUi {
+
+    private static final String TAG = "FlagTogglerPrefFrag";
+
+    private final PreferenceFragment mFragment;
+    private final Context mContext;
+    private final SharedPreferences mSharedPreferences;
+
+    private final PreferenceDataStore mDataStore = 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();
+                    }
+                    updateMenu();
+                }
+            }
+        }
+
+        @Override
+        public boolean getBoolean(String key, boolean defValue) {
+            return mSharedPreferences.getBoolean(key, defValue);
+        }
+    };
+
+    public FlagTogglerPrefUi(PreferenceFragment fragment) {
+        mFragment = fragment;
+        mContext = fragment.getActivity();
+        mSharedPreferences = mContext.getSharedPreferences(
+                FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE);
+    }
+
+    public void applyTo(PreferenceGroup parent) {
+        // 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.
+        for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+            SwitchPreference switchPreference = new SwitchPreference(mContext);
+            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" : "");
+            switchPreference.setPreferenceDataStore(mDataStore);
+            parent.addPreference(switchPreference);
+        }
+        updateMenu();
+    }
+
+    private void updateMenu() {
+        mFragment.setHasOptionsMenu(anyChanged());
+        mFragment.getActivity().invalidateOptionsMenu();
+    }
+
+    public void onCreateOptionsMenu(Menu menu) {
+        if (anyChanged()) {
+            menu.add(0, R.id.menu_apply_flags, 0, "Apply")
+                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+        }
+    }
+
+    public void onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == R.id.menu_apply_flags) {
+            mSharedPreferences.edit().commit();
+            Log.e(TAG,
+                    "Killing launcher process " + Process.myPid() + " to apply new flag values");
+            System.exit(0);
+        }
+    }
+
+    public void onStop() {
+        if (anyChanged()) {
+            Toast.makeText(mContext, "Flag won't be applied until you restart launcher",
+                    Toast.LENGTH_LONG).show();
+        }
+    }
+
+    private boolean getFlagStateFromSharedPrefs(TogglableFlag flag) {
+        return mDataStore.getBoolean(flag.getKey(), flag.getDefaultValue());
+    }
+
+    private boolean anyChanged() {
+        for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+            if (getFlagStateFromSharedPrefs(flag) != flag.get()) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/config/FlagTogglerPreferenceFragment.java b/src/com/android/launcher3/config/FlagTogglerPreferenceFragment.java
deleted file mode 100644
index 0a1fd2f..0000000
--- a/src/com/android/launcher3/config/FlagTogglerPreferenceFragment.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
new file mode 100644
index 0000000..a9242f9
--- /dev/null
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -0,0 +1,287 @@
+/*
+ * 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.settings;
+
+import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
+import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
+
+import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.config.FlagTogglerPrefUi;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+
+import java.util.List;
+import java.util.Set;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceDataStore;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.PreferenceViewHolder;
+import androidx.preference.SwitchPreference;
+
+/**
+ * Dev-build only UI allowing developers to toggle flag settings and plugins.
+ * See {@link FeatureFlags}.
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class DeveloperOptionsFragment extends PreferenceFragment {
+
+    private static final String ACTION_PLUGIN_SETTINGS = "com.android.systemui.action.PLUGIN_SETTINGS";
+    private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
+
+    private final BroadcastReceiver mPluginReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            loadPluginPrefs();
+        }
+    };
+
+    private PreferenceScreen mPreferenceScreen;
+
+    private PreferenceCategory mPluginsCategory;
+    private FlagTogglerPrefUi mFlagTogglerPrefUi;
+
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addDataScheme("package");
+        getContext().registerReceiver(mPluginReceiver, filter);
+        getContext().registerReceiver(mPluginReceiver,
+                new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+
+        mPreferenceScreen = getPreferenceManager().createPreferenceScreen(getContext());
+        setPreferenceScreen(mPreferenceScreen);
+
+        initFlags();
+        loadPluginPrefs();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        getContext().unregisterReceiver(mPluginReceiver);
+    }
+
+    private PreferenceCategory newCategory(String title) {
+        PreferenceCategory category = new PreferenceCategory(getContext());
+        category.setOrder(Preference.DEFAULT_ORDER);
+        category.setTitle(title);
+        mPreferenceScreen.addPreference(category);
+        return category;
+    }
+
+    private void initFlags() {
+        if (!FeatureFlags.showFlagTogglerUi(getContext())) {
+            return;
+        }
+
+        mFlagTogglerPrefUi = new FlagTogglerPrefUi(this);
+        mFlagTogglerPrefUi.applyTo(newCategory("Feature flags"));
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        if (mFlagTogglerPrefUi != null) {
+            mFlagTogglerPrefUi.onCreateOptionsMenu(menu);
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (mFlagTogglerPrefUi != null) {
+            mFlagTogglerPrefUi.onOptionsItemSelected(item);
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public void onStop() {
+        if (mFlagTogglerPrefUi != null) {
+            mFlagTogglerPrefUi.onStop();
+        }
+        super.onStop();
+    }
+
+    private void loadPluginPrefs() {
+        if (mPluginsCategory != null) {
+            mPreferenceScreen.removePreference(mPluginsCategory);
+        }
+        if (!PluginManagerWrapper.hasPlugins(getActivity())) {
+            mPluginsCategory = null;
+            return;
+        }
+        mPluginsCategory = newCategory("Plugins");
+
+        PluginManagerWrapper manager = PluginManagerWrapper.INSTANCE.get(getContext());
+        Context prefContext = getContext();
+        PackageManager pm = getContext().getPackageManager();
+
+        Set<String> pluginActions = manager.getPluginActions();
+        ArrayMap<String, ArraySet<String>> plugins = new ArrayMap<>();
+        for (String action : pluginActions) {
+            String name = toName(action);
+            List<ResolveInfo> result = pm.queryIntentServices(
+                    new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS);
+            for (ResolveInfo info : result) {
+                String packageName = info.serviceInfo.packageName;
+                if (!plugins.containsKey(packageName)) {
+                    plugins.put(packageName, new ArraySet<>());
+                }
+                plugins.get(packageName).add(name);
+            }
+        }
+
+        List<PackageInfo> apps = pm.getPackagesHoldingPermissions(new String[]{PLUGIN_PERMISSION},
+                PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES);
+        PreferenceDataStore enabled = manager.getPluginEnabler();
+        apps.forEach(app -> {
+            if (!plugins.containsKey(app.packageName)) return;
+            SwitchPreference pref = new PluginPreference(prefContext, app, enabled);
+            pref.setSummary("Plugins: " + toString(plugins.get(app.packageName)));
+            mPluginsCategory.addPreference(pref);
+        });
+    }
+
+    private String toString(ArraySet<String> plugins) {
+        StringBuilder b = new StringBuilder();
+        for (String string : plugins) {
+            if (b.length() != 0) {
+                b.append(", ");
+            }
+            b.append(string);
+        }
+        return b.toString();
+    }
+
+    private String toName(String action) {
+        String str = action.replace("com.android.systemui.action.PLUGIN_", "");
+        StringBuilder b = new StringBuilder();
+        for (String s : str.split("_")) {
+            if (b.length() != 0) {
+                b.append(' ');
+            }
+            b.append(s.substring(0, 1));
+            b.append(s.substring(1).toLowerCase());
+        }
+        return b.toString();
+    }
+
+    private static class PluginPreference extends SwitchPreference {
+        private final boolean mHasSettings;
+        private final PackageInfo mInfo;
+        private final PreferenceDataStore mPluginEnabler;
+
+        public PluginPreference(Context prefContext, PackageInfo info,
+                PreferenceDataStore pluginEnabler) {
+            super(prefContext);
+            PackageManager pm = prefContext.getPackageManager();
+            mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS)
+                    .setPackage(info.packageName), 0) != null;
+            mInfo = info;
+            mPluginEnabler = pluginEnabler;
+            setTitle(info.applicationInfo.loadLabel(pm));
+            setChecked(isPluginEnabled());
+            setWidgetLayoutResource(R.layout.switch_preference_with_settings);
+        }
+
+        private boolean isEnabled(ComponentName cn) {
+            return mPluginEnabler.getBoolean(pluginEnabledKey(cn), true);
+
+        }
+
+        private boolean isPluginEnabled() {
+            for (int i = 0; i < mInfo.services.length; i++) {
+                ComponentName componentName = new ComponentName(mInfo.packageName,
+                        mInfo.services[i].name);
+                if (!isEnabled(componentName)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        protected boolean persistBoolean(boolean isEnabled) {
+            boolean shouldSendBroadcast = false;
+            for (int i = 0; i < mInfo.services.length; i++) {
+                ComponentName componentName = new ComponentName(mInfo.packageName,
+                        mInfo.services[i].name);
+
+                if (isEnabled(componentName) != isEnabled) {
+                    mPluginEnabler.putBoolean(pluginEnabledKey(componentName), isEnabled);
+                    shouldSendBroadcast = true;
+                }
+            }
+            if (shouldSendBroadcast) {
+                final String pkg = mInfo.packageName;
+                final Intent intent = new Intent(PLUGIN_CHANGED,
+                        pkg != null ? Uri.fromParts("package", pkg, null) : null);
+                getContext().sendBroadcast(intent);
+            }
+            setChecked(isEnabled);
+            return true;
+        }
+
+        @Override
+        public void onBindViewHolder(PreferenceViewHolder holder) {
+            super.onBindViewHolder(holder);
+            holder.findViewById(R.id.settings).setVisibility(mHasSettings ? View.VISIBLE
+                    : View.GONE);
+            holder.findViewById(R.id.divider).setVisibility(mHasSettings ? View.VISIBLE
+                    : View.GONE);
+            holder.findViewById(R.id.settings).setOnClickListener(v -> {
+                ResolveInfo result = v.getContext().getPackageManager().resolveActivity(
+                        new Intent(ACTION_PLUGIN_SETTINGS).setPackage(
+                                mInfo.packageName), 0);
+                if (result != null) {
+                    v.getContext().startActivity(new Intent().setComponent(
+                            new ComponentName(result.activityInfo.packageName,
+                                    result.activityInfo.name)));
+                }
+            });
+            holder.itemView.setOnLongClickListener(v -> {
+                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+                intent.setData(Uri.fromParts("package", mInfo.packageName, null));
+                getContext().startActivity(intent);
+                return true;
+            });
+        }
+    }
+}
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 4c022b4..7c158d9 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -24,17 +24,16 @@
 import android.app.Activity;
 import android.app.DialogFragment;
 import android.app.Fragment;
-import android.content.Intent;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.util.Log;
 
 import com.android.launcher3.LauncherFiles;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.IconShapeOverride;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.SecureSettingsObserver;
 
 import androidx.preference.ListPreference;
@@ -52,6 +51,7 @@
 public class SettingsActivity extends Activity
         implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
 
+    private static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options";
     private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
 
     private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging";
@@ -204,6 +204,11 @@
                 case FLAGS_PREFERENCE_KEY:
                     // Only show flag toggler UI if this build variant implements that.
                     return FeatureFlags.showFlagTogglerUi(getContext());
+
+                case DEVELOPER_OPTIONS_KEY:
+                    // Show if plugins are enabled or flag UI is enabled.
+                    return FeatureFlags.showFlagTogglerUi(getContext()) ||
+                            PluginManagerWrapper.hasPlugins(getContext());
             }
 
             return true;
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index 31dbb34..e1a35c9 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -14,17 +14,26 @@
 
 package com.android.launcher3.uioverrides.plugins;
 
+import android.content.ComponentName;
 import android.content.Context;
 
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
 
+import java.util.Collections;
+import java.util.Set;
+
+import androidx.preference.PreferenceDataStore;
+
 public class PluginManagerWrapper {
 
     public static final MainThreadInitializedObject<PluginManagerWrapper> INSTANCE =
             new MainThreadInitializedObject<>(PluginManagerWrapper::new);
 
+    private static final String PREFIX_PLUGIN_ENABLED = "PLUGIN_ENABLED_";
+    public static final String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED";
+
     private PluginManagerWrapper(Context c) {
     }
 
@@ -35,6 +44,21 @@
             boolean allowMultiple) {
     }
 
-    public void removePluginListener(PluginListener<? extends Plugin> listener) {
+    public void removePluginListener(PluginListener<? extends Plugin> listener) { }
+
+    public Set<String> getPluginActions() {
+        return Collections.emptySet();
+    }
+
+    public PreferenceDataStore getPluginEnabler() {
+        return new PreferenceDataStore() { };
+    }
+
+    public static String pluginEnabledKey(ComponentName cn) {
+        return PREFIX_PLUGIN_ENABLED + cn.flattenToString();
+    }
+
+    public static boolean hasPlugins(Context context) {
+        return false;
     }
 }