Merge "[PS] Update PS tile icon." into main
diff --git a/Android.bp b/Android.bp
index c4ea48a..eed67f5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -339,6 +339,7 @@
     name: "Launcher3Go",
 
     static_libs: ["Launcher3GoLib"],
+    resource_dirs: [],
 
     platform_apis: true,
     min_sdk_version: "current",
@@ -374,6 +375,7 @@
     name: "Launcher3QuickStepGo",
 
     static_libs: ["Launcher3GoLib"],
+    resource_dirs: [],
 
     platform_apis: true,
     min_sdk_version: "current",
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/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index cceaf27..2fedb6f 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -38,6 +38,8 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
+import static com.android.launcher3.testing.shared.TestProtocol.SUCCESSFUL_GESTURE_MISMATCH_EVENTS;
+import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
@@ -1647,6 +1649,12 @@
                         int taskToLaunch = mRecentsView.getNextPage();
                         int runningTask = getLastAppearedTaskIndex();
                         boolean hasStartedNewTask = hasStartedNewTask();
+                        testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
+                                "taskToLaunch=" + taskToLaunch);
+                        testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
+                                "runningTask=" + runningTask);
+                        testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
+                                "hasStartedNewTask=" + hasStartedNewTask);
                         if (target == NEW_TASK && taskToLaunch == runningTask
                                 && !hasStartedNewTask) {
                             // We are about to launch the current running task, so use LAST_TASK
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 879fccb..711882c 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -97,6 +97,13 @@
                     RecentTasksList.this.onRunningTaskVanished(taskInfo);
                 });
             }
+
+            @Override
+            public void onRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+                mMainThreadExecutor.execute(() -> {
+                    RecentTasksList.this.onRunningTaskChanged(taskInfo);
+                });
+            }
         });
         // We may receive onRunningTaskAppeared events later for tasks which have already been
         // included in the list returned by mSysUiProxy.getRunningTasks(), or may receive
@@ -244,6 +251,20 @@
         }
     }
 
+    private void onRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        // Find the task from the list of running tasks, if it exists
+        for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) {
+            if (existingTask.taskId != taskInfo.taskId) continue;
+
+            mRunningTasks.remove(existingTask);
+            mRunningTasks.add(taskInfo);
+            if (mRunningTasksListener != null) {
+                mRunningTasksListener.onRunningTasksChanged();
+            }
+            return;
+        }
+    }
+
     /**
      * Loads and creates a list of all the recent tasks.
      */
@@ -390,4 +411,4 @@
             return mRequestId == requestId && (!mKeysOnly || loadKeysOnly);
         }
     }
-}
\ No newline at end of file
+}
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/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index e4d8e92..5b16c0f 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -33,8 +33,6 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification;
 import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -204,9 +202,6 @@
         mLauncher.getLaunchedAppState().switchToOverview();
     }
 
-    // Staging; will be promoted to presubmit if stable
-    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
-
     //@NavigationModeSwitch
     @Test
     public void goToOverviewFromApp() {
diff --git a/res/drawable/ic_install_to_private.xml b/res/drawable/ic_install_to_private.xml
index 45723b0..a16d35a 100644
--- a/res/drawable/ic_install_to_private.xml
+++ b/res/drawable/ic_install_to_private.xml
@@ -22,10 +22,16 @@
     android:viewportHeight="24"
     android:tint="?attr/materialColorOnSurface">
 
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M19,5V19H5V5H19ZM19,3H5C3.9,3 3,3.9 3,5V19C3,20.1 3.9,21 5,21H19C20.1,21 21,20.1 21,19V5C21,3.9 20.1,3 19,3Z" />
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M12.93,12.27L13.5,15.5H10.5L11.07,12.27C10.43,11.94 10,11.27 10,10.5C10,9.4 10.9,8.5 12,8.5C13.1,8.5 14,9.4 14,10.5C14,11.27 13.57,11.94 12.93,12.27Z" />
+    <group>
+        <clip-path
+            android:pathData="M0,0h24v24h-24z"/>
+        <path
+            android:pathData="M12.001,1.999L4.001,4.999V11.089C4.001,16.139 7.411,20.849 12.001,21.999C16.591,20.849 20.001,16.139 20.001,11.089V4.999L12.001,1.999ZM18.001,11.089C18.001,15.089 15.451,18.789 12.001,19.919C8.551,18.789 6.001,15.099 6.001,11.089V6.389L12.001,4.139L18.001,6.389V11.089Z"
+            android:fillColor="@android:color/white"
+            android:fillType="evenOdd"/>
+        <path
+            android:pathData="M8.501,9.5C8.501,11.08 9.561,12.41 11.001,12.84V18H13.001V17H15.001V15H13.001V12.84C14.441,12.41 15.501,11.09 15.501,9.5C15.501,7.57 13.931,6 12.001,6C10.071,6 8.501,7.57 8.501,9.5ZM13.501,9.5C13.501,10.33 12.831,11 12.001,11C11.171,11 10.501,10.33 10.501,9.5C10.501,8.67 11.171,8 12.001,8C12.831,8 13.501,8.67 13.501,9.5Z"
+            android:fillColor="@android:color/white"
+            android:fillType="evenOdd"/>
+    </group>
 </vector>
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/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index c255eb5..8026d4a 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -191,7 +191,6 @@
     private float mBottomSheetAlpha = 1f;
     private boolean mForceBottomSheetVisible;
     private int mTabsProtectionAlpha;
-    private float mTotalHeaderProtectionHeight;
     @Nullable private AllAppsTransitionController mAllAppsTransitionController;
 
     public ActivityAllAppsContainerView(Context context) {
@@ -778,7 +777,7 @@
     protected void updateHeaderScroll(int scrolledOffset) {
         float prog1 = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f);
         int headerColor = getHeaderColor(prog1);
-        int tabsAlpha = mHeader.getPeripheralProtectionHeight() == 0 ? 0
+        int tabsAlpha = mHeader.getPeripheralProtectionHeight(/* expectedHeight */ false) == 0 ? 0
                 : (int) (Utilities.boundToRange(
                         (scrolledOffset + mHeader.mSnappedScrolledY) / mHeaderThreshold, 0f, 1f)
                         * 255);
@@ -1448,15 +1447,13 @@
                 mTmpPath.reset();
                 mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
                 canvas.drawPath(mTmpPath, mHeaderPaint);
-                mTotalHeaderProtectionHeight = headerBottomWithScaleOnTablet;
             }
         } else {
             canvas.drawRect(0, 0, canvas.getWidth(), headerBottomWithScaleOnPhone, mHeaderPaint);
-            mTotalHeaderProtectionHeight = headerBottomWithScaleOnPhone;
         }
 
         // If tab exist (such as work profile), extend header with tab height
-        final int tabsHeight = headerView.getPeripheralProtectionHeight();
+        final int tabsHeight = headerView.getPeripheralProtectionHeight(/* expectedHeight */ false);
         if (mTabsProtectionAlpha > 0 && tabsHeight != 0) {
             if (DEBUG_HEADER_PROTECTION) {
                 mHeaderPaint.setColor(Color.BLUE);
@@ -1482,16 +1479,19 @@
                     right,
                     tabBottomWithScale,
                     mHeaderPaint);
-            mTotalHeaderProtectionHeight = tabBottomWithScale;
         }
     }
 
     /**
-     * The height of the header protection is dynamically calculated during the time of drawing the
-     * header.
+     * The height of the header protection as if the user scrolled down the app list.
      */
     float getHeaderProtectionHeight() {
-        return mTotalHeaderProtectionHeight;
+        float headerBottom = getHeaderBottom() - getTranslationY();
+        if (mUsingTabs) {
+            return headerBottom + mHeader.getPeripheralProtectionHeight(/* expectedHeight */ true);
+        } else {
+            return headerBottom;
+        }
     }
 
     /**
@@ -1515,6 +1515,10 @@
         return bottom + mHeader.getTop();
     }
 
+    boolean isUsingTabs() {
+        return mUsingTabs;
+    }
+
     /**
      * Returns a view that denotes the visible part of all apps container view.
      */
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index a1f6ebe..92c589c 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;
@@ -466,9 +466,14 @@
     }
 
     /**
-     * Returns visible height of FloatingHeaderView contents requiring header protection
+     * Returns visible height of FloatingHeaderView contents requiring header protection or the
+     * expected header protection height.
      */
-    int getPeripheralProtectionHeight() {
+    int getPeripheralProtectionHeight(boolean expected) {
+        if (expected) {
+            return getTabLayout().getBottom() - getPaddingTop() + getPaddingBottom()
+                    - mMaxTranslation;
+        }
         // we only want to show protection when work tab is available and header is either
         // collapsed or animating to/from collapsed state
         if (mTabsHidden || mFloatingRowsCollapsed || !mHeaderCollapsed) {
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 1def8a3..fc186e8 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -495,9 +495,12 @@
                 if (rowToExpandToWithRespectToHeader == -1) {
                     rowToExpandToWithRespectToHeader = currentItem.rowIndex;
                 }
+                // If there are no tabs, decrease the row to scroll to by 1 since the header
+                // may be cut off slightly.
                 int rowToScrollTo =
                         (int) Math.floor((double) (mAllApps.getHeight() - psHeaderHeight
-                                - mAllApps.getHeaderProtectionHeight()) / allAppsCellHeight);
+                                - mAllApps.getHeaderProtectionHeight()) / allAppsCellHeight)
+                                - (mAllApps.isUsingTabs() ? 0 : 1);
                 int currentRowDistance = currentItem.rowIndex - rowToExpandToWithRespectToHeader;
                 // rowToScrollTo - 1 since the item to scroll to is 0 indexed.
                 if (currentRowDistance == rowToScrollTo - 1) {
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/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index af704a8..329f717 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -572,7 +572,7 @@
         try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
             final BitmapInfo tempBitmap = li.createBadgedIconBitmap(
                     mContext.getDrawable(widgetSection.mSectionDrawable),
-                    new BaseIconFactory.IconOptions().setShrinkNonAdaptiveIcons(false));
+                    new BaseIconFactory.IconOptions());
             mWidgetCategoryBitmapInfos.put(infoInOut.widgetCategory, tempBitmap);
             infoInOut.bitmap = getBadgedIcon(tempBitmap, infoInOut.user);
         } catch (Exception e) {
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index d702e51..d5f1e18 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -75,9 +75,11 @@
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.LogConfig;
 
+import java.io.File;
 import java.io.InvalidObjectException;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
@@ -125,38 +127,53 @@
 
         if (Flags.enableNarrowGridRestore()) {
             String oldPhoneFileName = idp.dbFile;
+            List<String> previousDbs = existingDbs();
             removeOldDBs(context, oldPhoneFileName);
             // The idp before this contains data about the old phone, after this it becomes the idp
             // of the current phone.
             idp.reset(context);
-            trySettingPreviousGidAsCurrent(context, idp, oldPhoneFileName);
+            trySettingPreviousGidAsCurrent(context, idp, oldPhoneFileName, previousDbs);
         } else {
             idp.reinitializeAfterRestore(context);
         }
     }
 
+
     /**
      * Try setting the gird used in the previous phone to the new one. If the current device doesn't
      * support the previous grid option it will not be set.
      */
     private static void trySettingPreviousGidAsCurrent(Context context, InvariantDeviceProfile idp,
-            String oldPhoneDbFileName) {
-        InvariantDeviceProfile.GridOption gridOption = idp.getGridOptionFromFileName(context,
-                oldPhoneDbFileName);
-        if (gridOption != null) {
+            String oldPhoneDbFileName, List<String> previousDbs) {
+        InvariantDeviceProfile.GridOption oldPhoneGridOption = idp.getGridOptionFromFileName(
+                context, oldPhoneDbFileName);
+        // The grid option could be null if current phone doesn't support the previous db.
+        if (oldPhoneGridOption != null) {
+            /* If the user only used the default db on the previous phone and the new default db is
+             * bigger than or equal to the previous one, then keep the new default db */
+            if (previousDbs.size() == 1 && oldPhoneGridOption.numColumns <= idp.numColumns
+                    && oldPhoneGridOption.numRows <= idp.numRows) {
+                /* Keep the user in default grid */
+                return;
+            }
             /*
-             * We do this because in some cases different devices have different names for grid
-             * options, in one device the grid option "normal" can be 4x4 while in other it
-             * could be "practical". Calling this changes the current device grid to the same
-             * we had in the other phone, in the case the current phone doesn't support the grid
-             * option we use the default and migrate the db to the default. Migration occurs on
-             * {@code GridSizeMigrationUtil#migrateGridIfNeeded}
+             * Here we are setting the previous db as the current one.
              */
-            idp.setCurrentGrid(context, gridOption.name);
+            idp.setCurrentGrid(context, oldPhoneGridOption.name);
         }
     }
 
     /**
+     * Returns a list of paths of the existing launcher dbs.
+     */
+    private static List<String> existingDbs() {
+        // At this point idp.dbFile contains the name of the dbFile from the previous phone
+        return LauncherFiles.GRID_DB_FILES.stream()
+                .filter(dbName -> new File(dbName).exists())
+                .toList();
+    }
+
+    /**
      * Only keep the last database used on the previous device.
      */
     private static void removeOldDBs(Context context, String oldPhoneDbFileName) {
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;
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
index 351b921..9363ce8 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
+++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
@@ -290,6 +290,7 @@
         doReturn(0).when(privateProfileManager).addSystemAppsDivider(any());
         when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT);
         when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT);
+        when(mAllApps.isUsingTabs()).thenReturn(true);
         mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore,
                 null, privateProfileManager);
         mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS);
@@ -311,6 +312,43 @@
     }
 
     @Test
+    public void scrollForViewToBeVisibleInContainer_withHeaderNoTabs() {
+        when(mAllAppsStore.getApps()).thenReturn(createAppInfoList());
+        PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
+        when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+        when(privateProfileManager.splitIntoUserInstalledAndSystemApps())
+                .thenReturn(iteminfo -> iteminfo.componentName == null
+                        || !iteminfo.componentName.getPackageName()
+                        .equals(CAMERA_PACKAGE_NAME));
+        doReturn(0).when(privateProfileManager).addPrivateSpaceHeader(any());
+        doAnswer(answer(this::addPrivateSpaceHeader)).when(privateProfileManager)
+                .addPrivateSpaceHeader(any());
+        doNothing().when(privateProfileManager).addPrivateSpaceInstallAppButton(any());
+        doReturn(0).when(privateProfileManager).addSystemAppsDivider(any());
+        when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT);
+        when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT);
+        when(mAllApps.isUsingTabs()).thenReturn(false);
+        mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore,
+                null, privateProfileManager);
+        mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS);
+        mAlphabeticalAppsList.updateItemFilter(info -> info != null
+                && info.user.equals(MAIN_HANDLE));
+
+        int rows = (int) (ALL_APPS_HEIGHT - PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT) - 1;
+        int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+
+        // The number of adapterItems should be the private space apps + one main app + header.
+        assertEquals(NUM_PRIVATE_SPACE_APPS + 1 + 1,
+                mAlphabeticalAppsList.getAdapterItems().size());
+        assertEquals(position,
+                privateProfileManager.scrollForHeaderToBeVisibleInContainer(
+                        new AllAppsRecyclerView(mContext),
+                        mAlphabeticalAppsList.getAdapterItems(),
+                        PS_HEADER_HEIGHT,
+                        ALL_APPS_CELL_HEIGHT));
+    }
+
+    @Test
     public void scrollForViewToBeVisibleInContainer_withHeaderAndLessAppRowSpace() {
         when(mAllAppsStore.getApps()).thenReturn(createAppInfoList());
         PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
@@ -325,6 +363,7 @@
         doNothing().when(privateProfileManager).addPrivateSpaceInstallAppButton(any());
         doReturn(0).when(privateProfileManager).addSystemAppsDivider(any());
         when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT);
+        when(mAllApps.isUsingTabs()).thenReturn(true);
         when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT);
         mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore,
                 null, privateProfileManager);
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index df71ec0..b7933c8 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -39,6 +39,7 @@
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
 import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.rule.ScreenRecordRule;
 import com.android.launcher3.util.rule.TestStabilityRule;
 
 import org.junit.Test;
@@ -132,6 +133,7 @@
      */
     @Test
     @PlatinumTest(focusArea = "launcher")
+    @ScreenRecordRule.ScreenRecord // b/319501259
     public void uninstallWorkspaceIcon() throws IOException {
         Point[] gridPositions = TestUtil.getCornersAndCenterPositions(mLauncher);
         StringBuilder sb = new StringBuilder();
@@ -162,12 +164,10 @@
             mLauncher.getWorkspace().verifyWorkspaceAppIconIsGone(
                     DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
 
-            if (!TestStabilityRule.isPresubmit()) { // b/315847371
-                Log.d(UIOBJECT_STALE_ELEMENT, "second getWorkspaceIconsPositions()");
-                Map<String, Point> finalPositions =
-                        mLauncher.getWorkspace().getWorkspaceIconsPositions();
-                assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
-            }
+            Log.d(UIOBJECT_STALE_ELEMENT, "second getWorkspaceIconsPositions()");
+            Map<String, Point> finalPositions =
+                    mLauncher.getWorkspace().getWorkspaceIconsPositions();
+            assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
         } finally {
             TestUtil.uninstallDummyApp();
         }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 7109083..99e15ba 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -300,7 +300,8 @@
         if (userManager != null) {
             for (UserHandle userHandle : userManager.getUserProfiles()) {
                 if (!userHandle.isSystem()) {
-                    mDevice.executeShellCommand("pm remove-user " + userHandle.getIdentifier());
+                    mDevice.executeShellCommand(
+                            "pm remove-user --wait " + userHandle.getIdentifier());
                 }
             }
         }
diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
index cccda88..e08d37c 100644
--- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
@@ -114,7 +114,7 @@
         mLauncher.runToState(
                 () -> {
                     try {
-                        mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
+                        mDevice.executeShellCommand("pm remove-user --wait " + mProfileUserId);
                     } catch (IOException e) {
                         throw new RuntimeException(e);
                     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index d176136..4fa93ef 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -347,6 +347,7 @@
      */
     public Map<String, Point> getWorkspaceIconsPositions() {
         final UiObject2 workspace = verifyActiveContainer();
+        mLauncher.waitForLauncherInitialized(); // b/319501259
         List<UiObject2> workspaceIcons =
                 mLauncher.waitForObjectsInContainer(workspace, AppIcon.getAnyAppIconSelector());
         return workspaceIcons.stream()