Using resource overrides for PluginManager instead of code-swap

Bug: 330920490
Test: Presubmit
Flag: None
Change-Id: Ib1cd50f95873928b9ce104e8e95d21f1dca9c6e3
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index c4ee11a..28cdb99 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -34,6 +34,7 @@
     <string name="taskbar_model_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarModelCallbacksFactory</string>
     <string name="taskbar_view_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarViewCallbacksFactory</string>
     <string name="launcher_restore_event_logger_class" translatable="false">com.android.quickstep.LauncherRestoreEventLoggerImpl</string>
+    <string name="plugin_manager_wrapper_class" translatable="false">com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl</string>
 
     <string name="nav_handle_long_press_handler_class" translatable="false"></string>
     <string name="assist_utils_class" translatable="false"></string>
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
index 6b44ca7..e94fa61 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
@@ -16,8 +16,12 @@
 
 package com.android.launcher3.uioverrides.flags
 
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
 import android.os.Handler
 import android.provider.DeviceConfig
+import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
 import android.text.Html
 import android.view.inputmethod.EditorInfo
 import androidx.preference.Preference
@@ -26,9 +30,12 @@
 import androidx.preference.SwitchPreference
 import com.android.launcher3.ExtendedEditText
 import com.android.launcher3.R
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl
+import com.android.launcher3.util.PluginManagerWrapper
 import com.android.quickstep.util.DeviceConfigHelper
 import com.android.quickstep.util.DeviceConfigHelper.Companion.NAMESPACE_LAUNCHER
 import com.android.quickstep.util.DeviceConfigHelper.DebugInfo
+import com.android.systemui.shared.plugins.PluginEnabler
 
 /** Helper class to generate UI for Device Config */
 class DevOptionsUiHelper {
@@ -142,7 +149,92 @@
             .getInt(this.key, DeviceConfig.getInt(NAMESPACE_LAUNCHER, this.key, this.valueInCode))
             .toString()
 
+    /**
+     * Inflates the preferences for plugins
+     *
+     * A single pref is added for a plugin-group. A plugin-group is a collection of plugins in a
+     * single apk which have the same android:process tags defined. The apk should also hold the
+     * PLUGIN_PERMISSION. We collect all the plugin intents which Launcher listens for and fetch all
+     * corresponding plugins on the device. When a plugin-group is enabled/disabled we also need to
+     * notify the pluginManager manually since the broadcast-mechanism only works in sysui process
+     */
+    fun inflatePluginPrefs(parent: PreferenceGroup) {
+        val context = parent.context
+        val manager = PluginManagerWrapper.INSTANCE[context] as PluginManagerWrapperImpl
+        val pm = context.packageManager
+
+        val pluginPermissionApps =
+            pm.getPackagesHoldingPermissions(
+                    arrayOf(PLUGIN_PERMISSION),
+                    PackageManager.MATCH_DISABLED_COMPONENTS
+                )
+                .map { it.packageName }
+
+        manager.pluginActions
+            .flatMap { action ->
+                pm.queryIntentServices(
+                        Intent(action),
+                        PackageManager.MATCH_DISABLED_COMPONENTS or
+                            PackageManager.GET_RESOLVED_FILTER
+                    )
+                    .filter { pluginPermissionApps.contains(it.serviceInfo.packageName) }
+            }
+            .groupBy { "${it.serviceInfo.packageName}-${it.serviceInfo.processName}" }
+            .values
+            .forEach { infoList ->
+                val pluginInfo = infoList[0]!!
+                val pluginUri = Uri.fromParts("package", pluginInfo.serviceInfo.packageName, null)
+
+                object : SwitchPreference(context) {
+                        override fun onBindViewHolder(holder: PreferenceViewHolder) {
+                            super.onBindViewHolder(holder)
+                            holder.itemView.setOnLongClickListener {
+                                context.startActivity(
+                                    Intent(ACTION_APPLICATION_DETAILS_SETTINGS, pluginUri)
+                                )
+                                true
+                            }
+                        }
+                    }
+                    .apply {
+                        isPersistent = true
+                        title = pluginInfo.loadLabel(pm)
+                        isChecked =
+                            infoList.all {
+                                manager.pluginEnabler.isEnabled(it.serviceInfo.componentName)
+                            }
+                        summary =
+                            infoList
+                                .map { it.filter }
+                                .filter { it?.countActions() ?: 0 > 0 }
+                                .joinToString(prefix = "Plugins: ") {
+                                    it.getAction(0)
+                                        .replace("com.android.systemui.action.PLUGIN_", "")
+                                        .replace("com.android.launcher3.action.PLUGIN_", "")
+                                }
+
+                        setOnPreferenceChangeListener { _, newVal ->
+                            val disabledState =
+                                if (newVal as Boolean) PluginEnabler.ENABLED
+                                else PluginEnabler.DISABLED_MANUALLY
+                            infoList.forEach {
+                                manager.pluginEnabler.setDisabled(
+                                    it.serviceInfo.componentName,
+                                    disabledState
+                                )
+                            }
+                            manager.notifyChange(Intent(Intent.ACTION_PACKAGE_CHANGED, pluginUri))
+                            true
+                        }
+
+                        parent.addPreference(this)
+                    }
+            }
+    }
+
     companion object {
         const val TAG = "DeviceConfigUIHelper"
+
+        const val PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN"
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
index 3f818ac..ca8da08 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
@@ -15,13 +15,7 @@
  */
 package com.android.launcher3.uioverrides.flags;
 
-import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
-import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-
 import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_HIGHLIGHT_KEY;
-import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
 import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
 import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_COUNT;
 import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN;
@@ -29,18 +23,11 @@
 import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN;
 import static com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP;
 
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.provider.Settings;
 import android.text.Editable;
 import android.text.TextWatcher;
-import android.util.ArrayMap;
-import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -52,20 +39,12 @@
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceScreen;
-import androidx.preference.PreferenceViewHolder;
-import androidx.preference.SwitchPreference;
 
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-import com.android.systemui.shared.plugins.PluginEnabler;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
+import com.android.systemui.shared.plugins.PluginPrefs;
 
 /**
  * Dev-build only UI allowing developers to toggle flag settings and plugins.
@@ -73,15 +52,9 @@
  */
 public class DeveloperOptionsUI {
 
-    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 PreferenceFragmentCompat mFragment;
     private final PreferenceScreen mPreferenceScreen;
 
-    private PreferenceCategory mPluginsCategory;
-
     public DeveloperOptionsUI(PreferenceFragmentCompat fragment, PreferenceCategory flags) {
         mFragment = fragment;
         mPreferenceScreen = fragment.getPreferenceScreen();
@@ -97,8 +70,10 @@
 
         DevOptionsUiHelper uiHelper = new DevOptionsUiHelper();
         uiHelper.inflateServerFlags(newCategory("Server flags"));
+        if (PluginPrefs.hasPlugins(getContext())) {
+            uiHelper.inflatePluginPrefs(newCategory("Plugins"));
+        }
 
-        loadPluginPrefs();
         maybeAddSandboxCategory();
         addOnboardingPrefsCatergory();
     }
@@ -160,65 +135,6 @@
         return mFragment.requireContext();
     }
 
-    private void loadPluginPrefs() {
-        if (mPluginsCategory != null) {
-            mPreferenceScreen.removePreference(mPluginsCategory);
-        }
-        if (!PluginManagerWrapper.hasPlugins(getContext())) {
-            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<Pair<String, String>, ArrayList<Pair<String, ResolveInfo>>> plugins =
-                new ArrayMap<>();
-
-        Set<String> pluginPermissionApps = pm.getPackagesHoldingPermissions(
-                        new String[]{PLUGIN_PERMISSION}, MATCH_DISABLED_COMPONENTS)
-                .stream()
-                .map(pi -> pi.packageName)
-                .collect(Collectors.toSet());
-
-        for (String action : pluginActions) {
-            String name = toName(action);
-            List<ResolveInfo> result = pm.queryIntentServices(
-                    new Intent(action), MATCH_DISABLED_COMPONENTS | GET_RESOLVED_FILTER);
-            for (ResolveInfo info : result) {
-                String packageName = info.serviceInfo.packageName;
-                if (!pluginPermissionApps.contains(packageName)) {
-                    continue;
-                }
-
-                Pair<String, String> key = Pair.create(packageName, info.serviceInfo.processName);
-                if (!plugins.containsKey(key)) {
-                    plugins.put(key, new ArrayList<>());
-                }
-                plugins.get(key).add(Pair.create(name, info));
-            }
-        }
-
-        PluginEnabler enabler = manager.getPluginEnabler();
-        plugins.forEach((key, si) -> {
-            String packageName = key.first;
-            List<ComponentName> componentNames = si.stream()
-                    .map(p -> new ComponentName(packageName, p.second.serviceInfo.name))
-                    .collect(Collectors.toList());
-            if (!componentNames.isEmpty()) {
-                SwitchPreference pref = new PluginPreference(
-                        prefContext, si.get(0).second, enabler, componentNames);
-                pref.setSummary("Plugins: "
-                        + si.stream().map(p -> p.first).collect(Collectors.joining(", ")));
-                mPluginsCategory.addPreference(pref);
-            }
-        });
-    }
-
     private void maybeAddSandboxCategory() {
         Context context = getContext();
         if (context == null) {
@@ -321,102 +237,4 @@
         });
         return onboardingPref;
     }
-
-    private String toName(String action) {
-        String str = action.replace("com.android.systemui.action.PLUGIN_", "")
-                .replace("com.android.launcher3.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 String mPackageName;
-        private final ResolveInfo mSettingsInfo;
-        private final PluginEnabler mPluginEnabler;
-        private final List<ComponentName> mComponentNames;
-
-        PluginPreference(Context prefContext, ResolveInfo pluginInfo,
-                PluginEnabler pluginEnabler, List<ComponentName> componentNames) {
-            super(prefContext);
-            PackageManager pm = prefContext.getPackageManager();
-            mPackageName = pluginInfo.serviceInfo.applicationInfo.packageName;
-            Intent settingsIntent = new Intent(ACTION_PLUGIN_SETTINGS).setPackage(mPackageName);
-            // If any Settings activity in app has category filters, set plugin action as category.
-            List<ResolveInfo> settingsInfos =
-                    pm.queryIntentActivities(settingsIntent, GET_RESOLVED_FILTER);
-            if (pluginInfo.filter != null) {
-                for (ResolveInfo settingsInfo : settingsInfos) {
-                    if (settingsInfo.filter != null && settingsInfo.filter.countCategories() > 0) {
-                        settingsIntent.addCategory(pluginInfo.filter.getAction(0));
-                        break;
-                    }
-                }
-            }
-
-            mSettingsInfo = pm.resolveActivity(settingsIntent, 0);
-            mPluginEnabler = pluginEnabler;
-            mComponentNames = componentNames;
-            setTitle(pluginInfo.loadLabel(pm));
-            setChecked(isPluginEnabled());
-            setWidgetLayoutResource(R.layout.switch_preference_with_settings);
-        }
-
-        private boolean isPluginEnabled() {
-            for (ComponentName componentName : mComponentNames) {
-                if (!mPluginEnabler.isEnabled(componentName)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        @Override
-        protected boolean persistBoolean(boolean isEnabled) {
-            boolean shouldSendBroadcast = false;
-            for (ComponentName componentName : mComponentNames) {
-                if (mPluginEnabler.isEnabled(componentName) != isEnabled) {
-                    mPluginEnabler.setDisabled(componentName,
-                            isEnabled ? PluginEnabler.ENABLED : PluginEnabler.DISABLED_MANUALLY);
-                    shouldSendBroadcast = true;
-                }
-            }
-            if (shouldSendBroadcast) {
-                final String pkg = mPackageName;
-                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);
-            boolean hasSettings = mSettingsInfo != null;
-            holder.findViewById(R.id.settings).setVisibility(hasSettings ? VISIBLE : GONE);
-            holder.findViewById(R.id.divider).setVisibility(hasSettings ? VISIBLE : GONE);
-            holder.findViewById(R.id.settings).setOnClickListener(v -> {
-                if (hasSettings) {
-                    v.getContext().startActivity(new Intent().setComponent(
-                            new ComponentName(mSettingsInfo.activityInfo.packageName,
-                                    mSettingsInfo.activityInfo.name)));
-                }
-            });
-            holder.itemView.setOnLongClickListener(v -> {
-                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
-                intent.setData(Uri.fromParts("package", mPackageName, null));
-                getContext().startActivity(intent);
-                return true;
-            });
-        }
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapperImpl.java
similarity index 74%
rename from quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
rename to quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapperImpl.java
index a09d0a1..74572c4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapperImpl.java
@@ -1,15 +1,17 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2024 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
+ * 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.
+ * 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;
@@ -24,11 +26,10 @@
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.BuildConfig;
+import com.android.launcher3.util.PluginManagerWrapper;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginActionManager;
 import com.android.systemui.shared.plugins.PluginInstance;
 import com.android.systemui.shared.plugins.PluginManagerImpl;
@@ -41,35 +42,30 @@
 import java.util.List;
 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;
+public class PluginManagerWrapperImpl extends PluginManagerWrapper {
 
     private static final UncaughtExceptionPreHandlerManager UNCAUGHT_EXCEPTION_PRE_HANDLER_MANAGER =
             new UncaughtExceptionPreHandlerManager();
 
     private final Context mContext;
-    private final PluginManager mPluginManager;
+    private final PluginManagerImpl mPluginManager;
     private final PluginEnablerImpl mPluginEnabler;
 
-    private PluginManagerWrapper(Context c) {
+    public PluginManagerWrapperImpl(Context c) {
         mContext = c;
         mPluginEnabler = new PluginEnablerImpl(c);
         List<String> privilegedPlugins = Collections.emptyList();
         PluginInstance.Factory instanceFactory = new PluginInstance.Factory(
                 getClass().getClassLoader(), new PluginInstance.InstanceFactory<>(),
                 new PluginInstance.VersionCheckerImpl(), privilegedPlugins,
-                Utilities.IS_DEBUG_DEVICE);
+                BuildConfig.IS_DEBUG_DEVICE);
         PluginActionManager.Factory instanceManagerFactory = new PluginActionManager.Factory(
                 c, c.getPackageManager(), c.getMainExecutor(), MODEL_EXECUTOR,
                 c.getSystemService(NotificationManager.class), mPluginEnabler,
                 privilegedPlugins, instanceFactory);
 
         mPluginManager = new PluginManagerImpl(c, instanceManagerFactory,
-                Utilities.IS_DEBUG_DEVICE,
+                BuildConfig.IS_DEBUG_DEVICE,
                 UNCAUGHT_EXCEPTION_PRE_HANDLER_MANAGER, mPluginEnabler,
                 new PluginPrefs(c), privilegedPlugins);
     }
@@ -78,18 +74,13 @@
         return mPluginEnabler;
     }
 
-    /** */
-    public <T extends Plugin> void addPluginListener(
-            PluginListener<T> listener, Class<T> pluginClass) {
-        addPluginListener(listener, pluginClass, false);
-    }
-
-    /** */
+    @Override
     public <T extends Plugin> void addPluginListener(
             PluginListener<T> listener, Class<T> pluginClass, boolean allowMultiple) {
         mPluginManager.addPluginListener(listener, pluginClass, allowMultiple);
     }
 
+    @Override
     public void removePluginListener(PluginListener<? extends Plugin> listener) {
         mPluginManager.removePluginListener(listener);
     }
@@ -98,10 +89,12 @@
         return new PluginPrefs(mContext).getPluginList();
     }
 
-    public static boolean hasPlugins(Context context) {
-        return PluginPrefs.hasPlugins(context);
+    /** Notifies that a plugin state has changed */
+    public void notifyChange(Intent intent) {
+        mPluginManager.onReceive(mContext, intent);
     }
 
+    @Override
     public void dump(PrintWriter pw) {
         final List<ComponentName> enabledPlugins = new ArrayList<>();
         final List<ComponentName> disabledPlugins = new ArrayList<>();
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 7035d6e..6635ee4 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -95,9 +95,9 @@
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.PluginManagerWrapper;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.TraceHelper;
diff --git a/res/layout/switch_preference_with_settings.xml b/res/layout/switch_preference_with_settings.xml
deleted file mode 100644
index cd51833..0000000
--- a/res/layout/switch_preference_with_settings.xml
+++ /dev/null
@@ -1,46 +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.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:gravity="center_vertical">
-
-    <ImageView
-        android:id="@+id/settings"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:src="@drawable/ic_setting"
-        android:forceDarkAllowed="true"
-        android:padding="12dp"
-        android:background="?android:attr/selectableItemBackgroundBorderless" />
-
-    <View
-        android:id="@+id/divider"
-        android:layout_width="1dp"
-        android:layout_height="30dp"
-        android:layout_marginEnd="8dp"
-        android:background="?android:attr/listDivider" />
-
-    <!-- Note: seems we need focusable="false" and clickable="false" when moving to androidx -->
-    <Switch
-        android:id="@android:id/switch_widget"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:background="@null" />
-</LinearLayout>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index f820e76..393a197 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -84,7 +84,7 @@
     <string name="wallpaper_picker_package" translatable="false"></string>
     <string name="local_colors_extraction_class" translatable="false"></string>
     <string name="search_session_manager_class" translatable="false"></string>
-
+    <string name="plugin_manager_wrapper_class" translatable="false"></string>
 
     <!-- Scalable Grid configuration -->
     <!-- This is a float because it is converted to dp later in DeviceProfile -->
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d2d5ba9..c91d4d0 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -222,7 +222,6 @@
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.AllAppsSwipeController;
 import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.ActivityResultInfo;
 import com.android.launcher3.util.ActivityTracker;
 import com.android.launcher3.util.BackPressHandler;
@@ -235,6 +234,7 @@
 import com.android.launcher3.util.LockedUserState;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.util.PluginManagerWrapper;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
@@ -576,8 +576,8 @@
                 Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
 
         mOverlayManager = getDefaultOverlay();
-        PluginManagerWrapper.INSTANCE.get(this).addPluginListener(this,
-                LauncherOverlayPlugin.class, false /* allowedMultiple */);
+        PluginManagerWrapper.INSTANCE.get(this)
+                .addPluginListener(this, LauncherOverlayPlugin.class);
 
         mRotationHelper.initialize();
         TraceHelper.INSTANCE.endSection();
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index a1f6ebe..e25a2eb 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -34,7 +34,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.PluginManagerWrapper;
 import com.android.launcher3.views.ActivityContext;
 import com.android.systemui.plugins.AllAppsRow;
 import com.android.systemui.plugins.AllAppsRow.OnHeightUpdatedListener;
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 6b3bb51..0e4b48e 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -90,12 +90,12 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.PluginManagerWrapper;
 import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.views.ActivityContext;
diff --git a/src/com/android/launcher3/util/DynamicResource.java b/src/com/android/launcher3/util/DynamicResource.java
index e6ee186..1008ebb 100644
--- a/src/com/android/launcher3/util/DynamicResource.java
+++ b/src/com/android/launcher3/util/DynamicResource.java
@@ -22,7 +22,6 @@
 import androidx.annotation.FractionRes;
 import androidx.annotation.IntegerRes;
 
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.ResourceProvider;
 
diff --git a/src/com/android/launcher3/util/PluginManagerWrapper.java b/src/com/android/launcher3/util/PluginManagerWrapper.java
new file mode 100644
index 0000000..b27aa12
--- /dev/null
+++ b/src/com/android/launcher3/util/PluginManagerWrapper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.util;
+
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+
+import com.android.launcher3.R;
+import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginListener;
+
+import java.io.PrintWriter;
+
+public class PluginManagerWrapper implements ResourceBasedOverride, SafeCloseable {
+
+    public static final MainThreadInitializedObject<PluginManagerWrapper> INSTANCE =
+            forOverride(PluginManagerWrapper.class, R.string.plugin_manager_wrapper_class);
+
+    public <T extends Plugin> void addPluginListener(
+            PluginListener<T> listener, Class<T> pluginClass) {
+        addPluginListener(listener, pluginClass, false);
+    }
+
+    public <T extends Plugin> void addPluginListener(
+            PluginListener<T> listener, Class<T> pluginClass, boolean allowMultiple) {
+    }
+
+    public void removePluginListener(PluginListener<? extends Plugin> listener) { }
+
+    @Override
+    public void close() { }
+
+    public void dump(PrintWriter pw) { }
+}
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
index 2fdf354..05fe8e3 100644
--- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -32,9 +32,9 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.PluginManagerWrapper;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
diff --git a/src_no_quickstep/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/src_no_quickstep/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
deleted file mode 100644
index e1a35c9..0000000
--- a/src_no_quickstep/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ /dev/null
@@ -1,64 +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.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) {
-    }
-
-    public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
-    }
-
-    public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass,
-            boolean allowMultiple) {
-    }
-
-    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;
-    }
-}
diff --git a/src_plugins/com/android/systemui/plugins/NetworkFetcherPlugin.java b/src_plugins/com/android/systemui/plugins/NetworkFetcherPlugin.java
new file mode 100644
index 0000000..e327648
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/NetworkFetcherPlugin.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.systemui.plugins;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this plugin to proxy network requests
+ */
+@ProvidesInterface(action = NetworkFetcherPlugin.ACTION, version = NetworkFetcherPlugin.VERSION)
+public interface NetworkFetcherPlugin extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_NETWORK_FETCHER_ACTIONS";
+    int VERSION = 1;
+
+    /** Fetches the provided user and return all byte contents */
+    byte[] fetchUrl(String url) throws Exception;
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
index e806d1d..0009a4e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -60,7 +60,6 @@
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.testing.TestInformationProvider;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.widget.custom.CustomWidgetManager;