Merge "Reduce animation time for some common TAPL actions (affects jank/perf tests)" into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index 3d1d996..5def65f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -200,7 +200,7 @@
     $(LOCAL_PATH)/quickstep/recents_ui_overrides/res
 
 LOCAL_FULL_LIBS_MANIFEST_FILES := \
-    $(LOCAL_PATH)/AndroidManifest.xml \
+    $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
     $(LOCAL_PATH)/AndroidManifest-common.xml
 
 LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
@@ -247,7 +247,7 @@
 
 LOCAL_FULL_LIBS_MANIFEST_FILES := \
     $(LOCAL_PATH)/go/AndroidManifest.xml \
-    $(LOCAL_PATH)/AndroidManifest.xml \
+    $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
     $(LOCAL_PATH)/AndroidManifest-common.xml
 
 LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
@@ -293,7 +293,7 @@
 
 LOCAL_FULL_LIBS_MANIFEST_FILES := \
     $(LOCAL_PATH)/go/AndroidManifest.xml \
-    $(LOCAL_PATH)/AndroidManifest.xml \
+    $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
     $(LOCAL_PATH)/AndroidManifest-common.xml
 
 LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/go/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
new file mode 100644
index 0000000..0c60468
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (C) 2019 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;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
+import com.android.launcher3.uioverrides.touchcontrollers.LandscapeStatesTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
+import com.android.launcher3.util.TouchController;
+import com.android.quickstep.SysUINavigationMode;
+
+import java.util.ArrayList;
+
+public class QuickstepLauncher extends BaseQuickstepLauncher {
+
+    public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true;
+
+    @Override
+    public TouchController[] createTouchControllers() {
+        ArrayList<TouchController> list = new ArrayList<>();
+        list.add(getDragController());
+
+        if (getDeviceProfile().isVerticalBarLayout()) {
+            list.add(new LandscapeStatesTouchController(this));
+            list.add(new LandscapeEdgeSwipeController(this));
+        } else {
+            boolean allowDragToOverview = SysUINavigationMode.INSTANCE.get(this)
+                    .getMode().hasGestures;
+            list.add(new PortraitStatesTouchController(this, allowDragToOverview));
+        }
+        return list.toArray(new TouchController[list.size()]);
+    }
+}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
deleted file mode 100644
index d5ea1ec..0000000
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2019 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;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.graphics.RotationMode;
-import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
-import com.android.launcher3.uioverrides.touchcontrollers.LandscapeStatesTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
-import com.android.launcher3.util.TouchController;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.views.IconRecentsView;
-
-import java.util.ArrayList;
-
-/**
- * Provides recents-related {@link UiFactory} logic and classes.
- */
-public abstract class RecentsUiFactory {
-
-    public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true;
-
-    public static TouchController[] createTouchControllers(Launcher launcher) {
-        ArrayList<TouchController> list = new ArrayList<>();
-        list.add(launcher.getDragController());
-
-        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
-            list.add(new LandscapeStatesTouchController(launcher));
-            list.add(new LandscapeEdgeSwipeController(launcher));
-        } else {
-            boolean allowDragToOverview = SysUINavigationMode.INSTANCE.get(launcher)
-                    .getMode().hasGestures;
-            list.add(new PortraitStatesTouchController(launcher, allowDragToOverview));
-        }
-        if (Utilities.IS_DEBUG_DEVICE
-                && !launcher.getDeviceProfile().isMultiWindowMode
-                && !launcher.getDeviceProfile().isVerticalBarLayout()) {
-            list.add(new StatusBarTouchController(launcher));
-        }
-        return list.toArray(new TouchController[list.size()]);
-    }
-
-    /**
-     * Creates and returns the controller responsible for recents view state transitions.
-     *
-     * @param launcher the launcher activity
-     * @return state handler for recents
-     */
-    public static StateHandler createRecentsViewStateController(Launcher launcher) {
-        return new RecentsViewStateController(launcher);
-    }
-
-    /**
-     * Clean-up logic that occurs when recents is no longer in use/visible.
-     *
-     * @param launcher the launcher activity
-     */
-    public static void resetOverview(Launcher launcher) {
-        IconRecentsView recentsView = launcher.getOverviewPanel();
-        recentsView.setTransitionedFromApp(false);
-    }
-
-    /**
-     * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
-     *
-     * @param launcher the launcher activity
-     */
-    public static void onLauncherStateOrResumeChanged(Launcher launcher) {}
-
-    public static RotationMode getRotationMode(DeviceProfile dp) {
-        return RotationMode.NORMAL;
-    }
-}
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
new file mode 100644
index 0000000..60afddb
--- /dev/null
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2019, 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.
+*/
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.launcher3">
+    <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
+    <!--
+    Manifest entries specific to Launcher3. This is merged with AndroidManifest-common.xml.
+    Refer comments around specific entries on how to extend individual components.
+    -->
+
+    <application
+        android:backupAgent="com.android.launcher3.LauncherBackupAgent"
+        android:fullBackupOnly="true"
+        android:fullBackupContent="@xml/backupscheme"
+        android:hardwareAccelerated="true"
+        android:icon="@drawable/ic_launcher_home"
+        android:label="@string/derived_app_name"
+        android:theme="@style/AppTheme"
+        android:largeHeap="@bool/config_largeHeap"
+        android:restoreAnyVersion="true"
+        android:supportsRtl="true" >
+
+        <!--
+        Main launcher activity. When extending only change the name, and keep all the
+        attributes and intent filters the same
+        -->
+        <activity
+            android:name="com.android.launcher3.uioverrides.QuickstepLauncher"
+            android:launchMode="singleTask"
+            android:clearTaskOnLaunch="true"
+            android:stateNotNeeded="true"
+            android:windowSoftInputMode="adjustPan"
+            android:screenOrientation="unspecified"
+            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+            android:resizeableActivity="true"
+            android:resumeWhilePausing="true"
+            android:taskAffinity=""
+            android:enabled="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.HOME" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.MONKEY"/>
+                <category android:name="android.intent.category.LAUNCHER_APP" />
+            </intent-filter>
+            <meta-data
+                android:name="com.android.launcher3.grid.control"
+                android:value="${packageName}.grid_control" />
+        </activity>
+
+    </application>
+</manifest>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
similarity index 76%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
rename to quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 2a22e9d..43cdbdb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -1,4 +1,4 @@
-/*
+/**
  * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherState.NORMAL;
@@ -21,13 +20,14 @@
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.view.Gravity;
 
+import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
@@ -49,21 +49,15 @@
 
 import java.util.ArrayList;
 
-/**
- * Provides recents-related {@link UiFactory} logic and classes.
- */
-public abstract class RecentsUiFactory {
-
-    private static final String TAG = RecentsUiFactory.class.getSimpleName();
+public class QuickstepLauncher extends BaseQuickstepLauncher {
 
     public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
 
     /**
      * Reusable command for applying the shelf height on the background thread.
      */
-    public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) -> {
-        SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
-    };
+    public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
+            SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
 
     public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
         @Override
@@ -138,71 +132,78 @@
         }
     };
 
-    public static RotationMode getRotationMode(DeviceProfile dp) {
+    @Override
+    protected RotationMode getFakeRotationMode(DeviceProfile dp) {
         return !dp.isVerticalBarLayout() ? RotationMode.NORMAL
                 : (dp.isSeascape() ? ROTATION_SEASCAPE : ROTATION_LANDSCAPE);
     }
 
-    public static TouchController[] createTouchControllers(Launcher launcher) {
-        Mode mode = SysUINavigationMode.getMode(launcher);
-
-        ArrayList<TouchController> list = new ArrayList<>();
-        list.add(launcher.getDragController());
-        if (mode == NO_BUTTON) {
-            list.add(new QuickSwitchTouchController(launcher));
-            list.add(new NavBarToHomeTouchController(launcher));
-            list.add(new FlingAndHoldTouchController(launcher));
-        } else {
-            if (launcher.getDeviceProfile().isVerticalBarLayout()) {
-                list.add(new OverviewToAllAppsTouchController(launcher));
-                list.add(new LandscapeEdgeSwipeController(launcher));
-                if (mode.hasGestures) {
-                    list.add(new TransposedQuickSwitchTouchController(launcher));
-                }
-            } else {
-                list.add(new PortraitStatesTouchController(launcher,
-                        mode.hasGestures /* allowDragToOverview */));
-                if (mode.hasGestures) {
-                    list.add(new QuickSwitchTouchController(launcher));
-                }
-            }
-        }
-
-        if (!launcher.getDeviceProfile().isMultiWindowMode) {
-            list.add(new StatusBarTouchController(launcher));
-        }
-
-        list.add(new LauncherTaskViewController(launcher));
-        return list.toArray(new TouchController[list.size()]);
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        onStateOrResumeChanged();
     }
 
-    /**
-     * Creates and returns the controller responsible for recents view state transitions.
-     *
-     * @param launcher the launcher activity
-     * @return state handler for recents
-     */
-    public static StateHandler createRecentsViewStateController(Launcher launcher) {
-        return new RecentsViewStateController(launcher);
+    @Override
+    protected void onActivityFlagsChanged(int changeBits) {
+        super.onActivityFlagsChanged(changeBits);
+
+        if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED
+                | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0
+                && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0) {
+            onStateOrResumeChanged();
+        }
     }
 
     /**
      * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
-     *
-     * @param launcher the launcher activity
      */
-    public static void onLauncherStateOrResumeChanged(Launcher launcher) {
-        LauncherState state = launcher.getStateManager().getState();
-        DeviceProfile profile = launcher.getDeviceProfile();
-        boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
+    private void onStateOrResumeChanged() {
+        LauncherState state = getStateManager().getState();
+        DeviceProfile profile = getDeviceProfile();
+        boolean visible = (state == NORMAL || state == OVERVIEW) && isUserActive()
                 && !profile.isVerticalBarLayout();
-        UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT, visible ? 1 : 0,
+        UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0,
                 profile.hotseatBarSizePx);
         if (state == NORMAL) {
-            launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
+            ((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
         }
     }
 
+    @Override
+    public TouchController[] createTouchControllers() {
+        Mode mode = SysUINavigationMode.getMode(this);
+
+        ArrayList<TouchController> list = new ArrayList<>();
+        list.add(getDragController());
+        if (mode == NO_BUTTON) {
+            list.add(new QuickSwitchTouchController(this));
+            list.add(new NavBarToHomeTouchController(this));
+            list.add(new FlingAndHoldTouchController(this));
+        } else {
+            if (getDeviceProfile().isVerticalBarLayout()) {
+                list.add(new OverviewToAllAppsTouchController(this));
+                list.add(new LandscapeEdgeSwipeController(this));
+                if (mode.hasGestures) {
+                    list.add(new TransposedQuickSwitchTouchController(this));
+                }
+            } else {
+                list.add(new PortraitStatesTouchController(this,
+                        mode.hasGestures /* allowDragToOverview */));
+                if (mode.hasGestures) {
+                    list.add(new QuickSwitchTouchController(this));
+                }
+            }
+        }
+
+        if (!getDeviceProfile().isMultiWindowMode) {
+            list.add(new StatusBarTouchController(this));
+        }
+
+        list.add(new LauncherTaskViewController(this));
+        return list.toArray(new TouchController[list.size()]);
+    }
+
     private static final class LauncherTaskViewController extends
             TaskViewTouchController<Launcher> {
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index 17457aa..298b0ff 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -19,11 +19,11 @@
 import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 
 import android.graphics.Matrix;
-import android.view.View;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
+import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.quickstep.views.TaskThumbnailView;
@@ -40,25 +40,24 @@
 public class TaskOverlayFactory implements ResourceBasedOverride {
 
     /** Note that these will be shown in order from top to bottom, if available for the task. */
-    private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{
-            new TaskSystemShortcut.AppInfo(),
-            new TaskSystemShortcut.SplitScreen(),
-            new TaskSystemShortcut.Pin(),
-            new TaskSystemShortcut.Install(),
-            new TaskSystemShortcut.Freeform()
+    private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
+            TaskShortcutFactory.APP_INFO,
+            TaskShortcutFactory.SPLIT_SCREEN,
+            TaskShortcutFactory.PIN,
+            TaskShortcutFactory.INSTALL,
+            TaskShortcutFactory.FREE_FORM
     };
 
     public static final MainThreadInitializedObject<TaskOverlayFactory> INSTANCE =
             forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class);
 
-    public List<TaskSystemShortcut> getEnabledShortcuts(TaskView taskView) {
-        final ArrayList<TaskSystemShortcut> shortcuts = new ArrayList<>();
+    public List<SystemShortcut> getEnabledShortcuts(TaskView taskView) {
+        final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
         final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
-        for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
-            View.OnClickListener onClickListener =
-                    menuOption.getOnClickListener(activity, taskView);
-            if (onClickListener != null) {
-                shortcuts.add(menuOption);
+        for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
+            SystemShortcut shortcut = menuOption.getShortcut(activity, taskView);
+            if (shortcut != null) {
+                shortcuts.add(shortcut);
             }
         }
         return shortcuts;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
new file mode 100644
index 0000000..a3a1d6d
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
@@ -0,0 +1,312 @@
+/*
+ * 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.quickstep;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.view.View;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.popup.SystemShortcut.AppInfo;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.InstantAppResolver;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskThumbnailView;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.ActivityCompat;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Represents a system shortcut that can be shown for a recent task.
+ */
+public interface TaskShortcutFactory {
+
+    SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view);
+
+    static WorkspaceItemInfo dummyInfo(TaskView view) {
+        Task task = view.getTask();
+
+        WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
+        dummyInfo.intent = new Intent();
+        ComponentName component = task.getTopComponent();
+        dummyInfo.intent.setComponent(component);
+        dummyInfo.user = UserHandle.of(task.key.userId);
+        dummyInfo.title = TaskUtils.getTitle(view.getContext(), task);
+        return dummyInfo;
+    }
+
+    TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, dummyInfo(view));
+
+    abstract class MultiWindowFactory implements TaskShortcutFactory {
+
+        private final int mIconRes;
+        private final int mTextRes;
+
+        MultiWindowFactory(int iconRes, int textRes) {
+            mIconRes = iconRes;
+            mTextRes = textRes;
+        }
+
+        protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId);
+        protected abstract ActivityOptions makeLaunchOptions(Activity activity);
+        protected abstract boolean onActivityStarted(BaseDraggingActivity activity);
+
+        @Override
+        public SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView taskView) {
+            final Task task  = taskView.getTask();
+            if (!task.isDockable) {
+                return null;
+            }
+            if (!isAvailable(activity, task.key.displayId)) {
+                return null;
+            }
+            return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskView, this);
+        }
+    }
+
+    class MultiWindowSystemShortcut extends SystemShortcut {
+
+        private Handler mHandler;
+
+        private final RecentsView mRecentsView;
+        private final TaskThumbnailView mThumbnailView;
+        private final TaskView mTaskView;
+        private final MultiWindowFactory mFactory;
+
+        public MultiWindowSystemShortcut(int iconRes, int textRes,
+                BaseDraggingActivity activity, TaskView taskView, MultiWindowFactory factory) {
+            super(iconRes, textRes, activity, dummyInfo(taskView));
+
+            mHandler = new Handler(Looper.getMainLooper());
+            mTaskView = taskView;
+            mRecentsView = activity.getOverviewPanel();
+            mThumbnailView = taskView.getThumbnail();
+            mFactory = factory;
+        }
+
+        @Override
+        public void onClick(View view) {
+            Task.TaskKey taskKey = mTaskView.getTask().key;
+            final int taskId = taskKey.id;
+
+            final View.OnLayoutChangeListener onLayoutChangeListener =
+                    new View.OnLayoutChangeListener() {
+                        @Override
+                        public void onLayoutChange(View v, int l, int t, int r, int b,
+                                int oldL, int oldT, int oldR, int oldB) {
+                            mTaskView.getRootView().removeOnLayoutChangeListener(this);
+                            mRecentsView.clearIgnoreResetTask(taskId);
+
+                            // Start animating in the side pages once launcher has been resized
+                            mRecentsView.dismissTask(mTaskView, false, false);
+                        }
+                    };
+
+            final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener =
+                    new DeviceProfile.OnDeviceProfileChangeListener() {
+                        @Override
+                        public void onDeviceProfileChanged(DeviceProfile dp) {
+                            mTarget.removeOnDeviceProfileChangeListener(this);
+                            if (dp.isMultiWindowMode) {
+                                mTaskView.getRootView().addOnLayoutChangeListener(
+                                        onLayoutChangeListener);
+                            }
+                        }
+                    };
+
+            dismissTaskMenuView(mTarget);
+
+            ActivityOptions options = mFactory.makeLaunchOptions(mTarget);
+            if (options != null
+                    && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
+                            options)) {
+                if (!mFactory.onActivityStarted(mTarget)) {
+                    return;
+                }
+                // Add a device profile change listener to kick off animating the side tasks
+                // once we enter multiwindow mode and relayout
+                mTarget.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener);
+
+                final Runnable animStartedListener = () -> {
+                    // Hide the task view and wait for the window to be resized
+                    // TODO: Consider animating in launcher and do an in-place start activity
+                    //       afterwards
+                    mRecentsView.setIgnoreResetTask(taskId);
+                    mTaskView.setAlpha(0f);
+                };
+
+                final int[] position = new int[2];
+                mThumbnailView.getLocationOnScreen(position);
+                final int width = (int) (mThumbnailView.getWidth() * mTaskView.getScaleX());
+                final int height = (int) (mThumbnailView.getHeight() * mTaskView.getScaleY());
+                final Rect taskBounds = new Rect(position[0], position[1],
+                        position[0] + width, position[1] + height);
+
+                // Take the thumbnail of the task without a scrim and apply it back after
+                float alpha = mThumbnailView.getDimAlpha();
+                mThumbnailView.setDimAlpha(0);
+                Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
+                        taskBounds.width(), taskBounds.height(), mThumbnailView, 1f,
+                        Color.BLACK);
+                mThumbnailView.setDimAlpha(alpha);
+
+                AppTransitionAnimationSpecsFuture future =
+                        new AppTransitionAnimationSpecsFuture(mHandler) {
+                    @Override
+                    public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+                        return Collections.singletonList(new AppTransitionAnimationSpecCompat(
+                                taskId, thumbnail, taskBounds));
+                    }
+                };
+                WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
+                        future, animStartedListener, mHandler, true /* scaleUp */,
+                        taskKey.displayId);
+            }
+        }
+    }
+
+    TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(
+            R.drawable.ic_split_screen, R.string.recent_task_option_split_screen) {
+
+        @Override
+        protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
+            // Don't show menu-item if already in multi-window and the task is from
+            // the secondary display.
+            // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new
+            // implementation is enabled
+            return !activity.getDeviceProfile().isMultiWindowMode
+                    && (displayId == -1 || displayId == DEFAULT_DISPLAY);
+        }
+
+        @Override
+        protected ActivityOptions makeLaunchOptions(Activity activity) {
+            final ActivityCompat act = new ActivityCompat(activity);
+            final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
+                    act.getDisplayId());
+            if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) {
+                return null;
+            }
+            boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT;
+            return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft);
+        }
+
+        @Override
+        protected boolean onActivityStarted(BaseDraggingActivity activity) {
+            SystemUiProxy.INSTANCE.get(activity).onSplitScreenInvoked();
+            activity.getUserEventDispatcher().logActionOnControl(TAP,
+                    LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET);
+            return true;
+        }
+    };
+
+    TaskShortcutFactory FREE_FORM = new MultiWindowFactory(
+            R.drawable.ic_split_screen, R.string.recent_task_option_freeform) {
+
+        @Override
+        protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
+            return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity);
+        }
+
+        @Override
+        protected ActivityOptions makeLaunchOptions(Activity activity) {
+            ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions();
+            // Arbitrary bounds only because freeform is in dev mode right now
+            Rect r = new Rect(50, 50, 200, 200);
+            activityOptions.setLaunchBounds(r);
+            return activityOptions;
+        }
+
+        @Override
+        protected boolean onActivityStarted(BaseDraggingActivity activity) {
+            activity.returnToHomescreen();
+            return true;
+        }
+    };
+
+    TaskShortcutFactory PIN = (activity, tv) -> {
+        if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
+            return null;
+        }
+        if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
+            return null;
+        }
+        if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
+            // We shouldn't be able to pin while an app is locked.
+            return null;
+        }
+        return new PinSystemShortcut(activity, tv);
+    };
+
+    class PinSystemShortcut extends SystemShortcut {
+
+        private static final String TAG = "PinSystemShortcut";
+
+        private final TaskView mTaskView;
+
+        public PinSystemShortcut(BaseDraggingActivity target, TaskView tv) {
+            super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, dummyInfo(tv));
+            mTaskView = tv;
+        }
+
+        @Override
+        public void onClick(View view) {
+            Consumer<Boolean> resultCallback = success -> {
+                if (success) {
+                    SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(
+                            mTaskView.getTask().key.id);
+                } else {
+                    mTaskView.notifyTaskLaunchFailed(TAG);
+                }
+            };
+            mTaskView.launchTask(true, resultCallback, Executors.MAIN_EXECUTOR.getHandler());
+            dismissTaskMenuView(mTarget);
+        }
+    }
+
+    TaskShortcutFactory INSTALL = (activity, view) ->
+            InstantAppResolver.newInstance(activity).isInstantApp(activity,
+                 view.getTask().getTopComponent().getPackageName())
+                    ? new SystemShortcut.Install(activity, dummyInfo(view)) : null;
+
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
deleted file mode 100644
index 5a2e3ff..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
+++ /dev/null
@@ -1,327 +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.quickstep;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.view.View;
-
-import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.InstantAppResolver;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskThumbnailView;
-import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
-import com.android.systemui.shared.recents.view.RecentsTransition;
-import com.android.systemui.shared.system.ActivityCompat;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * Represents a system shortcut that can be shown for a recent task.
- */
-public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut {
-
-    private static final String TAG = "TaskSystemShortcut";
-
-    protected T mSystemShortcut;
-
-    public TaskSystemShortcut(T systemShortcut) {
-        super(systemShortcut);
-        mSystemShortcut = systemShortcut;
-    }
-
-    protected TaskSystemShortcut(int iconResId, int labelResId) {
-        super(iconResId, labelResId);
-    }
-
-    @Override
-    public View.OnClickListener getOnClickListener(
-            BaseDraggingActivity activity, ItemInfo itemInfo) {
-        return null;
-    }
-
-    public View.OnClickListener getOnClickListener(BaseDraggingActivity activity, TaskView view) {
-        Task task = view.getTask();
-
-        WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
-        dummyInfo.intent = new Intent();
-        ComponentName component = task.getTopComponent();
-        dummyInfo.intent.setComponent(component);
-        dummyInfo.user = UserHandle.of(task.key.userId);
-        dummyInfo.title = TaskUtils.getTitle(activity, task);
-
-        return getOnClickListenerForTask(activity, task, dummyInfo);
-    }
-
-    protected View.OnClickListener getOnClickListenerForTask(
-            BaseDraggingActivity activity, Task task, ItemInfo dummyInfo) {
-        return mSystemShortcut.getOnClickListener(activity, dummyInfo);
-    }
-
-    public static class AppInfo extends TaskSystemShortcut<SystemShortcut.AppInfo> {
-        public AppInfo() {
-            super(new SystemShortcut.AppInfo());
-        }
-    }
-
-    public static abstract class MultiWindow extends TaskSystemShortcut {
-
-        private Handler mHandler;
-
-        public MultiWindow(int iconRes, int textRes) {
-            super(iconRes, textRes);
-            mHandler = new Handler(Looper.getMainLooper());
-        }
-
-        protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId);
-        protected abstract ActivityOptions makeLaunchOptions(Activity activity);
-        protected abstract boolean onActivityStarted(BaseDraggingActivity activity);
-
-        @Override
-        public View.OnClickListener getOnClickListener(
-                BaseDraggingActivity activity, TaskView taskView) {
-            final Task task  = taskView.getTask();
-            final int taskId = task.key.id;
-            final int displayId = task.key.displayId;
-            if (!task.isDockable) {
-                return null;
-            }
-            if (!isAvailable(activity, displayId)) {
-                return null;
-            }
-            final RecentsView recentsView = activity.getOverviewPanel();
-
-            final TaskThumbnailView thumbnailView = taskView.getThumbnail();
-            return (v -> {
-                final View.OnLayoutChangeListener onLayoutChangeListener =
-                        new View.OnLayoutChangeListener() {
-                            @Override
-                            public void onLayoutChange(View v, int l, int t, int r, int b,
-                                    int oldL, int oldT, int oldR, int oldB) {
-                                taskView.getRootView().removeOnLayoutChangeListener(this);
-                                recentsView.clearIgnoreResetTask(taskId);
-
-                                // Start animating in the side pages once launcher has been resized
-                                recentsView.dismissTask(taskView, false, false);
-                            }
-                        };
-
-                final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener =
-                        new DeviceProfile.OnDeviceProfileChangeListener() {
-                            @Override
-                            public void onDeviceProfileChanged(DeviceProfile dp) {
-                                activity.removeOnDeviceProfileChangeListener(this);
-                                if (dp.isMultiWindowMode) {
-                                    taskView.getRootView().addOnLayoutChangeListener(
-                                            onLayoutChangeListener);
-                                }
-                            }
-                        };
-
-                dismissTaskMenuView(activity);
-
-                ActivityOptions options = makeLaunchOptions(activity);
-                if (options != null
-                        && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
-                                options)) {
-                    if (!onActivityStarted(activity)) {
-                        return;
-                    }
-                    // Add a device profile change listener to kick off animating the side tasks
-                    // once we enter multiwindow mode and relayout
-                    activity.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener);
-
-                    final Runnable animStartedListener = () -> {
-                        // Hide the task view and wait for the window to be resized
-                        // TODO: Consider animating in launcher and do an in-place start activity
-                        //       afterwards
-                        recentsView.setIgnoreResetTask(taskId);
-                        taskView.setAlpha(0f);
-                    };
-
-                    final int[] position = new int[2];
-                    thumbnailView.getLocationOnScreen(position);
-                    final int width = (int) (thumbnailView.getWidth() * taskView.getScaleX());
-                    final int height = (int) (thumbnailView.getHeight() * taskView.getScaleY());
-                    final Rect taskBounds = new Rect(position[0], position[1],
-                            position[0] + width, position[1] + height);
-
-                    // Take the thumbnail of the task without a scrim and apply it back after
-                    float alpha = thumbnailView.getDimAlpha();
-                    thumbnailView.setDimAlpha(0);
-                    Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
-                            taskBounds.width(), taskBounds.height(), thumbnailView, 1f,
-                            Color.BLACK);
-                    thumbnailView.setDimAlpha(alpha);
-
-                    AppTransitionAnimationSpecsFuture future =
-                            new AppTransitionAnimationSpecsFuture(mHandler) {
-                        @Override
-                        public List<AppTransitionAnimationSpecCompat> composeSpecs() {
-                            return Collections.singletonList(new AppTransitionAnimationSpecCompat(
-                                    taskId, thumbnail, taskBounds));
-                        }
-                    };
-                    WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
-                            future, animStartedListener, mHandler, true /* scaleUp */, displayId);
-                }
-            });
-        }
-    }
-
-    public static class SplitScreen extends MultiWindow {
-        public SplitScreen() {
-            super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen);
-        }
-
-        @Override
-        protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
-            // Don't show menu-item if already in multi-window and the task is from
-            // the secondary display.
-            // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new
-            // implementation is enabled
-            return !activity.getDeviceProfile().isMultiWindowMode
-                    && (displayId == -1 || displayId == DEFAULT_DISPLAY);
-        }
-
-        @Override
-        protected ActivityOptions makeLaunchOptions(Activity activity) {
-            final ActivityCompat act = new ActivityCompat(activity);
-            final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
-                    act.getDisplayId());
-            if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) {
-                return null;
-            }
-            boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT;
-            return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft);
-        }
-
-        @Override
-        protected boolean onActivityStarted(BaseDraggingActivity activity) {
-            SystemUiProxy.INSTANCE.get(activity).onSplitScreenInvoked();
-            activity.getUserEventDispatcher().logActionOnControl(TAP,
-                    LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET);
-            return true;
-        }
-    }
-
-    public static class Freeform extends MultiWindow {
-        public Freeform() {
-            super(R.drawable.ic_split_screen, R.string.recent_task_option_freeform);
-        }
-
-        @Override
-        protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
-            return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity);
-        }
-
-        @Override
-        protected ActivityOptions makeLaunchOptions(Activity activity) {
-            ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions();
-            // Arbitrary bounds only because freeform is in dev mode right now
-            Rect r = new Rect(50, 50, 200, 200);
-            activityOptions.setLaunchBounds(r);
-            return activityOptions;
-        }
-
-        @Override
-        protected boolean onActivityStarted(BaseDraggingActivity activity) {
-            activity.returnToHomescreen();
-            return true;
-        }
-    }
-
-    public static class Pin extends TaskSystemShortcut {
-
-        private static final String TAG = Pin.class.getSimpleName();
-
-        private Handler mHandler;
-
-        public Pin() {
-            super(R.drawable.ic_pin, R.string.recent_task_option_pin);
-            mHandler = new Handler(Looper.getMainLooper());
-        }
-
-        @Override
-        public View.OnClickListener getOnClickListener(
-                BaseDraggingActivity activity, TaskView taskView) {
-            if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
-                return null;
-            }
-            if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
-                return null;
-            }
-            if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
-                // We shouldn't be able to pin while an app is locked.
-                return null;
-            }
-            return view -> {
-                Consumer<Boolean> resultCallback = success -> {
-                    if (success) {
-                        SystemUiProxy.INSTANCE.get(activity).startScreenPinning(
-                                taskView.getTask().key.id);
-                    } else {
-                        taskView.notifyTaskLaunchFailed(TAG);
-                    }
-                };
-                taskView.launchTask(true, resultCallback, mHandler);
-                dismissTaskMenuView(activity);
-            };
-        }
-    }
-
-    public static class Install extends TaskSystemShortcut<SystemShortcut.Install> {
-        public Install() {
-            super(new SystemShortcut.Install());
-        }
-
-        @Override
-        protected View.OnClickListener getOnClickListenerForTask(
-                BaseDraggingActivity activity, Task task, ItemInfo itemInfo) {
-            if (InstantAppResolver.newInstance(activity).isInstantApp(activity,
-                        task.getTopComponent().getPackageName())) {
-                return mSystemShortcut.createOnClickListener(activity, itemInfo);
-            }
-            return null;
-        }
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
index bbb318a..e2524b1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
@@ -15,8 +15,8 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_LANDSCAPE;
-import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_SEASCAPE;
+import static com.android.launcher3.uioverrides.QuickstepLauncher.ROTATION_LANDSCAPE;
+import static com.android.launcher3.uioverrides.QuickstepLauncher.ROTATION_SEASCAPE;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 
 import android.content.Context;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
index 07d0796..b810c4a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,7 +16,6 @@
 
 package com.android.quickstep.views;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
 
 import android.animation.Animator;
@@ -26,7 +25,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
@@ -41,16 +39,13 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.TaskOverlayFactory;
-import com.android.quickstep.TaskSystemShortcut;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.views.IconView.OnScaleUpdateListener;
 
-import java.util.List;
-
 /**
  * Contains options for a recent task when long-pressing its icon.
  */
@@ -197,22 +192,16 @@
         params.topMargin = (int) -mThumbnailTopMargin;
         mTaskIcon.setLayoutParams(params);
 
-        final BaseDraggingActivity activity = BaseDraggingActivity.fromContext(getContext());
-        final List<TaskSystemShortcut> shortcuts =
-                TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(taskView);
-        final int count = shortcuts.size();
-        for (int i = 0; i < count; ++i) {
-            final TaskSystemShortcut menuOption = shortcuts.get(i);
-            addMenuOption(menuOption, menuOption.getOnClickListener(activity, taskView));
-        }
+        TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(taskView)
+                .forEach(this::addMenuOption);
     }
 
-    private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) {
+    private void addMenuOption(SystemShortcut menuOption) {
         ViewGroup menuOptionView = (ViewGroup) mActivity.getLayoutInflater().inflate(
                 R.layout.task_view_menu_option, this, false);
         menuOption.setIconAndLabelFor(
                 menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
-        menuOptionView.setOnClickListener(onClickListener);
+        menuOptionView.setOnClickListener(menuOption);
         mOptionLayout.addView(menuOptionView);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index bfb9613..3af0f70 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -53,6 +53,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -61,7 +62,6 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskIconCache;
 import com.android.quickstep.TaskOverlayFactory;
-import com.android.quickstep.TaskSystemShortcut;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.TaskCornerRadius;
@@ -713,15 +713,9 @@
                         getContext().getText(R.string.accessibility_close_task)));
 
         final Context context = getContext();
-        final List<TaskSystemShortcut> shortcuts =
-                TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(this);
-        final int count = shortcuts.size();
-        for (int i = 0; i < count; ++i) {
-            final TaskSystemShortcut menuOption = shortcuts.get(i);
-            OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, this);
-            if (onClickListener != null) {
-                info.addAction(menuOption.createAccessibilityAction(context));
-            }
+        for (SystemShortcut s : TaskOverlayFactory.INSTANCE.get(getContext())
+                .getEnabledShortcuts(this)) {
+            info.addAction(s.createAccessibilityAction(context));
         }
 
         if (mDigitalWellBeingToast.hasLimit()) {
@@ -752,16 +746,10 @@
             return true;
         }
 
-        final List<TaskSystemShortcut> shortcuts =
-                TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(this);
-        final int count = shortcuts.size();
-        for (int i = 0; i < count; ++i) {
-            final TaskSystemShortcut menuOption = shortcuts.get(i);
-            if (menuOption.hasHandlerForAction(action)) {
-                OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, this);
-                if (onClickListener != null) {
-                    onClickListener.onClick(this);
-                }
+        for (SystemShortcut s : TaskOverlayFactory.INSTANCE.get(getContext())
+                .getEnabledShortcuts(this)) {
+            if (s.hasHandlerForAction(action)) {
+                s.onClick(this);
                 return true;
             }
         }
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
new file mode 100644
index 0000000..f55ef94
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.allapps.DiscoveryBounce.BOUNCE_MAX_COUNT;
+import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT;
+import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
+import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT;
+import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN;
+
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.proxy.ProxyActivityStarter;
+import com.android.launcher3.proxy.StartActivityParams;
+import com.android.launcher3.uioverrides.BackButtonAlphaHandler;
+import com.android.launcher3.uioverrides.RecentsViewStateController;
+import com.android.launcher3.util.UiThreadHelper;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.RemoteFadeOutAnimationListener;
+
+/**
+ * Extension of Launcher activity to provide quickstep specific functionality
+ */
+public abstract class BaseQuickstepLauncher extends Launcher
+        implements NavigationModeChangeListener {
+
+    /**
+     * Reusable command for applying the back button alpha on the background thread.
+     */
+    public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA =
+            (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha(
+                    Float.intBitsToFloat(arg1), arg2 != 0);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        SysUINavigationMode.Mode mode = SysUINavigationMode.INSTANCE.get(this)
+                .addModeChangeListener(this);
+        getRotationHelper().setRotationHadDifferentUI(mode != Mode.NO_BUTTON);
+
+        if (!getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) {
+            getStateManager().addStateListener(new LauncherStateManager.StateListener() {
+                @Override
+                public void onStateTransitionStart(LauncherState toState) { }
+
+                @Override
+                public void onStateTransitionComplete(LauncherState finalState) {
+                    boolean swipeUpEnabled = SysUINavigationMode.INSTANCE
+                            .get(BaseQuickstepLauncher.this).getMode().hasGestures;
+                    LauncherState prevState = getStateManager().getLastState();
+
+                    if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled
+                            && finalState == ALL_APPS && prevState == NORMAL) || BOUNCE_MAX_COUNT
+                            <= getSharedPrefs().getInt(HOME_BOUNCE_COUNT, 0))) {
+                        getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply();
+                        getStateManager().removeStateListener(this);
+                    }
+                }
+            });
+        }
+
+        if (!getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)) {
+            getStateManager().addStateListener(new LauncherStateManager.StateListener() {
+                @Override
+                public void onStateTransitionStart(LauncherState toState) { }
+
+                @Override
+                public void onStateTransitionComplete(LauncherState finalState) {
+                    LauncherState prevState = getStateManager().getLastState();
+
+                    if ((finalState == ALL_APPS && prevState == OVERVIEW) || BOUNCE_MAX_COUNT
+                            <= getSharedPrefs().getInt(SHELF_BOUNCE_COUNT, 0)) {
+                        getSharedPrefs().edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply();
+                        getStateManager().removeStateListener(this);
+                    }
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
+        super.onDestroy();
+    }
+
+    @Override
+    public void onNavigationModeChanged(Mode newMode) {
+        getDragLayer().recreateControllers();
+        getRotationHelper().setRotationHadDifferentUI(newMode != Mode.NO_BUTTON);
+    }
+
+    @Override
+    public void onEnterAnimationComplete() {
+        super.onEnterAnimationComplete();
+        // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
+        // as a part of quickstep, so that high-res thumbnails can load the next time we enter
+        // overview
+        RecentsModel.INSTANCE.get(this).getThumbnailCache()
+                .getHighResLoadingState().setVisible(true);
+    }
+
+    @Override
+    public void onTrimMemory(int level) {
+        super.onTrimMemory(level);
+        RecentsModel.INSTANCE.get(this).onTrimMemory(level);
+    }
+
+    @Override
+    public void startIntentSenderForResult(IntentSender intent, int requestCode,
+            Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
+        if (requestCode != -1) {
+            mPendingActivityRequestCode = requestCode;
+            StartActivityParams params = new StartActivityParams(this, requestCode);
+            params.intentSender = intent;
+            params.fillInIntent = fillInIntent;
+            params.flagsMask = flagsMask;
+            params.flagsValues = flagsValues;
+            params.extraFlags = extraFlags;
+            params.options = options;
+            startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
+        } else {
+            super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
+                    flagsValues, extraFlags, options);
+        }
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
+        if (requestCode != -1) {
+            mPendingActivityRequestCode = -1;
+            StartActivityParams params = new StartActivityParams(this, requestCode);
+            params.intent = intent;
+            params.options = options;
+            startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
+        } else {
+            super.startActivityForResult(intent, requestCode, options);
+        }
+    }
+
+    @Override
+    protected void onDeferredResumed() {
+        if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) {
+            // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher.
+            onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null);
+            // ProxyActivityStarter is started with clear task to reset the task after which it
+            // removes the task itself.
+            startActivity(ProxyActivityStarter.getLaunchIntent(this, null));
+        }
+    }
+
+    @Override
+    protected StateHandler[] createStateHandlers() {
+        return new StateHandler[] {
+                getAllAppsController(),
+                getWorkspace(),
+                new RecentsViewStateController(this),
+                new BackButtonAlphaHandler(this)};
+    }
+
+    @Override
+    protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() {
+        if (SysUINavigationMode.getMode(this) == Mode.NO_BUTTON) {
+            float offscreenTranslationX = getDeviceProfile().widthPx
+                    - getOverviewPanel().getPaddingStart();
+            return new ScaleAndTranslation(1f, offscreenTranslationX, 0f);
+        }
+        return super.getOverviewScaleAndTranslationForNormalState();
+    }
+
+    @Override
+    public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
+        QuickstepAppTransitionManagerImpl appTransitionManager =
+                (QuickstepAppTransitionManagerImpl) getAppTransitionManager();
+        appTransitionManager.setRemoteAnimationProvider((appTargets, wallpaperTargets) -> {
+
+            // On the first call clear the reference.
+            signal.cancel();
+
+            ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
+            fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets,
+                    wallpaperTargets));
+            AnimatorSet anim = new AnimatorSet();
+            anim.play(fadeAnimation);
+            return anim;
+        }, signal);
+    }
+
+    @Override
+    public void onDragLayerHierarchyChanged() {
+        onLauncherStateOrFocusChanged();
+    }
+
+    @Override
+    protected void onActivityFlagsChanged(int changeBits) {
+        if ((changeBits
+                & (ACTIVITY_STATE_WINDOW_FOCUSED | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) {
+            onLauncherStateOrFocusChanged();
+        }
+
+        super.onActivityFlagsChanged(changeBits);
+    }
+
+    /**
+     * Sets the back button visibility based on the current state/window focus.
+     */
+    private void onLauncherStateOrFocusChanged() {
+        Mode mode = SysUINavigationMode.getMode(this);
+        boolean shouldBackButtonBeHidden = mode.hasGestures
+                && getStateManager().getState().hideBackButton
+                && hasWindowFocus()
+                && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0;
+        if (shouldBackButtonBeHidden) {
+            // Show the back button if there is a floating view visible.
+            shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(this,
+                    TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null;
+        }
+        UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA,
+                shouldBackButtonBeHidden ? 0f : 1f, true /* animate */);
+        if (getDragLayer() != null) {
+            getRootView().setDisallowBackGesture(shouldBackButtonBeHidden);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
new file mode 100644
index 0000000..965b5f0
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.app.Activity;
+import android.app.Person;
+import android.content.pm.ShortcutInfo;
+import android.util.Base64;
+
+import com.android.launcher3.Utilities;
+import com.android.systemui.shared.system.ActivityCompat;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.util.zip.Deflater;
+
+public class ApiWrapper {
+
+    public static boolean dumpActivity(Activity activity, PrintWriter writer) {
+        if (!Utilities.IS_DEBUG_DEVICE) {
+            return false;
+        }
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        if (!(new ActivityCompat(activity).encodeViewHierarchy(out))) {
+            return false;
+        }
+
+        Deflater deflater = new Deflater();
+        deflater.setInput(out.toByteArray());
+        deflater.finish();
+
+        out.reset();
+        byte[] buffer = new byte[1024];
+        while (!deflater.finished()) {
+            int count = deflater.deflate(buffer); // returns the generated code... index
+            out.write(buffer, 0, count);
+        }
+
+        writer.println("--encoded-view-dump-v0--");
+        writer.println(Base64.encodeToString(
+                out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING));
+        return true;
+    }
+
+    public static Person[] getPersons(ShortcutInfo si) {
+        Person[] persons = si.getPersons();
+        return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
index aa0dfc3..43dc882 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
@@ -16,11 +16,9 @@
 
 package com.android.launcher3.uioverrides;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.anim.AnimatorSetBuilder;
@@ -30,18 +28,14 @@
 
 public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler {
 
-    private static final String TAG = "BackButtonAlphaHandler";
+    private final BaseQuickstepLauncher mLauncher;
 
-    private final Launcher mLauncher;
-
-    public BackButtonAlphaHandler(Launcher launcher) {
+    public BackButtonAlphaHandler(BaseQuickstepLauncher launcher) {
         mLauncher = launcher;
     }
 
     @Override
-    public void setState(LauncherState state) {
-        UiFactory.onLauncherStateOrFocusChanged(mLauncher);
-    }
+    public void setState(LauncherState state) { }
 
     @Override
     public void setStateWithAnimation(LauncherState toState,
@@ -52,8 +46,8 @@
 
         if (!SysUINavigationMode.getMode(mLauncher).hasGestures) {
             // If the nav mode is not gestural, then force back button alpha to be 1
-            UiThreadHelper.setBackButtonAlphaAsync(mLauncher, UiFactory.SET_BACK_BUTTON_ALPHA, 1f,
-                    true /* animate */);
+            UiThreadHelper.setBackButtonAlphaAsync(mLauncher,
+                    BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, 1f, true /* animate */);
             return;
         }
 
@@ -64,15 +58,8 @@
             anim.setDuration(config.duration);
             anim.addUpdateListener(valueAnimator -> {
                 final float alpha = (float) valueAnimator.getAnimatedValue();
-                UiThreadHelper.setBackButtonAlphaAsync(mLauncher, UiFactory.SET_BACK_BUTTON_ALPHA,
-                        alpha, false /* animate */);
-            });
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    // Reapply the final alpha in case some state (e.g. window focus) changed.
-                    UiFactory.onLauncherStateOrFocusChanged(mLauncher);
-                }
+                UiThreadHelper.setBackButtonAlphaAsync(mLauncher,
+                        BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, alpha, false /* animate */);
             });
             builder.play(anim);
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
deleted file mode 100644
index 17c681b..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import static android.app.Activity.RESULT_CANCELED;
-
-import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
-import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.allapps.DiscoveryBounce.BOUNCE_MAX_COUNT;
-import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT;
-import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
-import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT;
-import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN;
-
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.app.Activity;
-import android.app.Person;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ShortcutInfo;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.util.Base64;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.QuickstepAppTransitionManagerImpl;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.proxy.ProxyActivityStarter;
-import com.android.launcher3.proxy.StartActivityParams;
-import com.android.launcher3.util.UiThreadHelper;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
-import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.util.RemoteFadeOutAnimationListener;
-import com.android.systemui.shared.system.ActivityCompat;
-
-import java.io.ByteArrayOutputStream;
-import java.io.PrintWriter;
-import java.util.zip.Deflater;
-
-public class UiFactory extends RecentsUiFactory {
-
-    /**
-     * Reusable command for applying the back button alpha on the background thread.
-     */
-    public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA =
-            (context, arg1, arg2) -> {
-        SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha(Float.intBitsToFloat(arg1),
-                arg2 != 0);
-    };
-
-    public static Runnable enableLiveUIChanges(Launcher launcher) {
-        NavigationModeChangeListener listener = m -> {
-            launcher.getDragLayer().recreateControllers();
-            launcher.getRotationHelper().setRotationHadDifferentUI(m != Mode.NO_BUTTON);
-        };
-        SysUINavigationMode mode = SysUINavigationMode.INSTANCE.get(launcher);
-        SysUINavigationMode.Mode m = mode.addModeChangeListener(listener);
-        launcher.getRotationHelper().setRotationHadDifferentUI(m != Mode.NO_BUTTON);
-        return () -> mode.removeModeChangeListener(listener);
-    }
-
-    public static StateHandler[] getStateHandler(Launcher launcher) {
-        return new StateHandler[] {
-                launcher.getAllAppsController(),
-                launcher.getWorkspace(),
-                createRecentsViewStateController(launcher),
-                new BackButtonAlphaHandler(launcher)};
-    }
-
-    /**
-     * Sets the back button visibility based on the current state/window focus.
-     */
-    public static void onLauncherStateOrFocusChanged(Launcher launcher) {
-        Mode mode = SysUINavigationMode.getMode(launcher);
-        boolean shouldBackButtonBeHidden = mode.hasGestures
-                && launcher != null
-                && launcher.getStateManager().getState().hideBackButton
-                && launcher.hasWindowFocus();
-        if (shouldBackButtonBeHidden) {
-            // Show the back button if there is a floating view visible.
-            shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(launcher,
-                    TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null;
-        }
-        UiThreadHelper.setBackButtonAlphaAsync(launcher, UiFactory.SET_BACK_BUTTON_ALPHA,
-                shouldBackButtonBeHidden ? 0f : 1f, true /* animate */);
-        if (launcher != null && launcher.getDragLayer() != null) {
-            launcher.getRootView().setDisallowBackGesture(shouldBackButtonBeHidden);
-        }
-    }
-
-    public static void onCreate(Launcher launcher) {
-        if (!launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) {
-            launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() {
-                @Override
-                public void onStateTransitionStart(LauncherState toState) {
-                }
-
-                @Override
-                public void onStateTransitionComplete(LauncherState finalState) {
-                    boolean swipeUpEnabled = SysUINavigationMode.INSTANCE.get(launcher).getMode()
-                            .hasGestures;
-                    LauncherState prevState = launcher.getStateManager().getLastState();
-
-                    if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled
-                            && finalState == ALL_APPS && prevState == NORMAL) || BOUNCE_MAX_COUNT <=
-                            launcher.getSharedPrefs().getInt(HOME_BOUNCE_COUNT, 0))) {
-                        launcher.getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply();
-                        launcher.getStateManager().removeStateListener(this);
-                    }
-                }
-            });
-        }
-
-        if (!launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)) {
-            launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() {
-                @Override
-                public void onStateTransitionStart(LauncherState toState) {
-                }
-
-                @Override
-                public void onStateTransitionComplete(LauncherState finalState) {
-                    LauncherState prevState = launcher.getStateManager().getLastState();
-
-                    if ((finalState == ALL_APPS && prevState == OVERVIEW) || BOUNCE_MAX_COUNT <=
-                            launcher.getSharedPrefs().getInt(SHELF_BOUNCE_COUNT, 0)) {
-                        launcher.getSharedPrefs().edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply();
-                        launcher.getStateManager().removeStateListener(this);
-                    }
-                }
-            });
-        }
-    }
-
-    public static void onEnterAnimationComplete(Context context) {
-        // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
-        // as a part of quickstep, so that high-res thumbnails can load the next time we enter
-        // overview
-        RecentsModel.INSTANCE.get(context).getThumbnailCache()
-                .getHighResLoadingState().setVisible(true);
-    }
-
-    public static void onTrimMemory(Context context, int level) {
-        RecentsModel model = RecentsModel.INSTANCE.get(context);
-        if (model != null) {
-            model.onTrimMemory(level);
-        }
-    }
-
-    public static void useFadeOutAnimationForLauncherStart(Launcher launcher,
-            CancellationSignal cancellationSignal) {
-        QuickstepAppTransitionManagerImpl appTransitionManager =
-                (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
-        appTransitionManager.setRemoteAnimationProvider((appTargets, wallpaperTargets) -> {
-
-            // On the first call clear the reference.
-            cancellationSignal.cancel();
-
-            ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
-            fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets,
-                    wallpaperTargets));
-            AnimatorSet anim = new AnimatorSet();
-            anim.play(fadeAnimation);
-            return anim;
-        }, cancellationSignal);
-    }
-
-    public static boolean dumpActivity(Activity activity, PrintWriter writer) {
-        if (!Utilities.IS_DEBUG_DEVICE) {
-            return false;
-        }
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        if (!(new ActivityCompat(activity).encodeViewHierarchy(out))) {
-            return false;
-        }
-
-        Deflater deflater = new Deflater();
-        deflater.setInput(out.toByteArray());
-        deflater.finish();
-
-        out.reset();
-        byte[] buffer = new byte[1024];
-        while (!deflater.finished()) {
-            int count = deflater.deflate(buffer); // returns the generated code... index
-            out.write(buffer, 0, count);
-        }
-
-        writer.println("--encoded-view-dump-v0--");
-        writer.println(Base64.encodeToString(
-                out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING));
-        return true;
-    }
-
-    public static boolean startIntentSenderForResult(Activity activity, IntentSender intent,
-            int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
-            Bundle options) {
-        StartActivityParams params = new StartActivityParams(activity, requestCode);
-        params.intentSender = intent;
-        params.fillInIntent = fillInIntent;
-        params.flagsMask = flagsMask;
-        params.flagsValues = flagsValues;
-        params.extraFlags = extraFlags;
-        params.options = options;
-        ((Context) activity).startActivity(ProxyActivityStarter.getLaunchIntent(activity, params));
-        return true;
-    }
-
-    public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode,
-            Bundle options) {
-        StartActivityParams params = new StartActivityParams(activity, requestCode);
-        params.intent = intent;
-        params.options = options;
-        activity.startActivity(ProxyActivityStarter.getLaunchIntent(activity, params));
-        return true;
-    }
-
-    /**
-     * Removes any active ProxyActivityStarter task and sends RESULT_CANCELED to Launcher.
-     *
-     * ProxyActivityStarter is started with clear task to reset the task after which it removes the
-     * task itself.
-     */
-    public static void resetPendingActivityResults(Launcher launcher, int requestCode) {
-        launcher.onActivityResult(requestCode, RESULT_CANCELED, null);
-        launcher.startActivity(ProxyActivityStarter.getLaunchIntent(launcher, null));
-    }
-
-    public static ScaleAndTranslation getOverviewScaleAndTranslationForNormalState(Launcher l) {
-        if (SysUINavigationMode.getMode(l) == Mode.NO_BUTTON) {
-            float offscreenTranslationX = l.getDeviceProfile().widthPx
-                    - l.getOverviewPanel().getPaddingStart();
-            return new ScaleAndTranslation(1f, offscreenTranslationX, 0f);
-        }
-        return new ScaleAndTranslation(1.1f, 0f, 0f);
-    }
-
-    public static Person[] getPersons(ShortcutInfo si) {
-        Person[] persons = si.getPersons();
-        return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
index 71833ad..5fcdc19 100644
--- a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
@@ -27,7 +27,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.ActivityTracker;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
@@ -122,13 +121,17 @@
     @Override
     public void onEnterAnimationComplete() {
         super.onEnterAnimationComplete();
-        UiFactory.onEnterAnimationComplete(this);
+        // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
+        // as a part of quickstep, so that high-res thumbnails can load the next time we enter
+        // overview
+        RecentsModel.INSTANCE.get(this).getThumbnailCache()
+                .getHighResLoadingState().setVisible(true);
     }
 
     @Override
     public void onTrimMemory(int level) {
         super.onTrimMemory(level);
-        UiFactory.onTrimMemory(this, level);
+        RecentsModel.INSTANCE.get(this).onTrimMemory(level);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 289a129..4f7d53b 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -15,7 +15,7 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.uioverrides.RecentsUiFactory.GO_LOW_RAM_RECENTS_ENABLED;
+import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.content.ComponentName;
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index b17ed4c..13731b6 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -31,6 +31,9 @@
 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.PLATFORM_PRESUBMIT;
+import static com.android.launcher3.util.rule.TestStabilityRule.RUN_FLAFOR;
+import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_PRESUBMIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -99,7 +102,7 @@
         }
 
         mOrderSensitiveRules = RuleChain.outerRule(new NavigationModeSwitchRule(mLauncher))
-                        .around(new FailureWatcher(mDevice));
+                .around(new FailureWatcher(mDevice));
 
         mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
                 getHomeIntentInPackage(context),
@@ -131,7 +134,8 @@
     @Test
     public void goToOverviewFromHome() {
         // b/142828227
-        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess()) {
+        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() &&
+                (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) {
             return;
         }
         mDevice.pressHome();
@@ -145,7 +149,8 @@
     @Test
     public void goToOverviewFromApp() {
         // b/142828227
-        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess()) {
+        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() &&
+                (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) {
             return;
         }
         startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
@@ -183,7 +188,8 @@
     @Test
     public void testOverview() {
         // b/142828227
-        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess()) {
+        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() &&
+                (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) {
             return;
         }
         startAppFastAndWaitForRecentTask(getAppPackageName());
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 25224b0..50cab74 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -18,6 +18,9 @@
 
 import static com.android.launcher3.util.RaceConditionReproducer.enterEvt;
 import static com.android.launcher3.util.RaceConditionReproducer.exitEvt;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_PRESUBMIT;
+import static com.android.launcher3.util.rule.TestStabilityRule.RUN_FLAFOR;
+import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_PRESUBMIT;
 
 import android.content.Intent;
 
@@ -93,7 +96,8 @@
     @NavigationModeSwitch
     public void testStressSwipeToOverview() {
         // b/142828227
-        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess()) {
+        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() &&
+                (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) {
             return;
         }
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index b28077f..382bfdf 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -35,7 +35,7 @@
 import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate;
-import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.ViewCache;
@@ -81,19 +81,39 @@
     protected StatsLogManager mStatsLogManager;
     protected SystemUiController mSystemUiController;
 
-    private static final int ACTIVITY_STATE_STARTED = 1 << 0;
-    private static final int ACTIVITY_STATE_RESUMED = 1 << 1;
+
+    public static final int ACTIVITY_STATE_STARTED = 1 << 0;
+    public static final int ACTIVITY_STATE_RESUMED = 1 << 1;
+
     /**
-     * State flag indicating if the user is active or the actitvity when to background as a result
+     * State flags indicating that the activity has received one frame after resume, and was
+     * not immediately paused.
+     */
+    public static final int ACTIVITY_STATE_DEFERRED_RESUMED = 1 << 2;
+
+    public static final int ACTIVITY_STATE_WINDOW_FOCUSED = 1 << 3;
+
+    /**
+     * State flag indicating if the user is active or the activity when to background as a result
      * of user action.
      * @see #isUserActive()
      */
-    private static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 2;
+    public static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 4;
+
+    /**
+     * State flag indicating that a state transition is in progress
+     */
+    public static final int ACTIVITY_STATE_TRANSITION_ACTIVE = 1 << 5;
 
     @Retention(SOURCE)
     @IntDef(
             flag = true,
-            value = {ACTIVITY_STATE_STARTED, ACTIVITY_STATE_RESUMED, ACTIVITY_STATE_USER_ACTIVE})
+            value = {ACTIVITY_STATE_STARTED,
+                    ACTIVITY_STATE_RESUMED,
+                    ACTIVITY_STATE_DEFERRED_RESUMED,
+                    ACTIVITY_STATE_WINDOW_FOCUSED,
+                    ACTIVITY_STATE_USER_ACTIVE,
+                    ACTIVITY_STATE_TRANSITION_ACTIVE})
     public @interface ActivityFlags{}
 
     @ActivityFlags
@@ -146,19 +166,19 @@
 
     @Override
     protected void onStart() {
-        mActivityFlags |= ACTIVITY_STATE_STARTED;
+        addActivityFlags(ACTIVITY_STATE_STARTED);
         super.onStart();
     }
 
     @Override
     protected void onResume() {
-        mActivityFlags |= ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE;
+        addActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE);
         super.onResume();
     }
 
     @Override
     protected void onUserLeaveHint() {
-        mActivityFlags &= ~ACTIVITY_STATE_USER_ACTIVE;
+        removeActivityFlags(ACTIVITY_STATE_USER_ACTIVE);
         super.onUserLeaveHint();
     }
 
@@ -172,7 +192,7 @@
 
     @Override
     protected void onStop() {
-        mActivityFlags &= ~ACTIVITY_STATE_STARTED & ~ACTIVITY_STATE_USER_ACTIVE;
+        removeActivityFlags(ACTIVITY_STATE_STARTED | ACTIVITY_STATE_USER_ACTIVE);
         mForceInvisible = 0;
         super.onStop();
 
@@ -183,7 +203,7 @@
 
     @Override
     protected void onPause() {
-        mActivityFlags &= ~ACTIVITY_STATE_RESUMED;
+        removeActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_DEFERRED_RESUMED);
         super.onPause();
 
         // Reset the overridden sysui flags used for the task-swipe launch animation, we do this
@@ -193,6 +213,17 @@
         getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
     }
 
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        if (hasFocus) {
+            addActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED);
+        } else {
+            removeActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED);
+        }
+
+    }
+
     public boolean isStarted() {
         return (mActivityFlags & ACTIVITY_STATE_STARTED) != 0;
     }
@@ -208,6 +239,22 @@
         return (mActivityFlags & ACTIVITY_STATE_USER_ACTIVE) != 0;
     }
 
+    public int getActivityFlags() {
+        return mActivityFlags;
+    }
+
+    protected void addActivityFlags(int flags) {
+        mActivityFlags |= flags;
+        onActivityFlagsChanged(flags);
+    }
+
+    protected void removeActivityFlags(int flags) {
+        mActivityFlags &= ~flags;
+        onActivityFlagsChanged(flags);
+    }
+
+    protected void onActivityFlagsChanged(int changeBits) { }
+
     public void addOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) {
         mDPChangeListeners.add(listener);
     }
@@ -233,7 +280,7 @@
     /**
      * Used to set the override visibility state, used only to handle the transition home with the
      * recents animation.
-     * @see QuickstepAppTransitionManagerImpl#getWallpaperOpenRunner()
+     * @see QuickstepAppTransitionManagerImpl#getWallpaperOpenRunner
      */
     public void addForceInvisibleFlag(@InvisibilityFlags int flag) {
         mForceInvisible |= flag;
@@ -260,7 +307,7 @@
 
     @Override
     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
-        if (!UiFactory.dumpActivity(this, writer)) {
+        if (!ApiWrapper.dumpActivity(this, writer)) {
             super.dump(prefix, fd, writer, args);
         }
     }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 67c1a04..bb08ba8 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -58,6 +58,7 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Parcelable;
 import android.os.Process;
@@ -82,6 +83,8 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsStore;
@@ -114,8 +117,8 @@
 import com.android.launcher3.qsb.QsbContainerView;
 import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.touch.AllAppsSwipeController;
 import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -135,6 +138,7 @@
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.util.ViewOnDrawExecutor;
@@ -294,12 +298,11 @@
      */
     private PendingRequestArgs mPendingRequestArgs;
     // Request id for any pending activity result
-    private int mPendingActivityRequestCode = -1;
+    protected int mPendingActivityRequestCode = -1;
 
     public ViewGroupFocusHelper mFocusHandler;
 
     private RotationHelper mRotationHelper;
-    private Runnable mCancelTouchController;
 
     final Handler mHandler = new Handler();
     private final Runnable mHandleDeferredResume = this::handleDeferredResume;
@@ -350,7 +353,6 @@
         mDragController = new DragController(this);
         mAllAppsController = new AllAppsTransitionController(this);
         mStateManager = new LauncherStateManager(this);
-        UiFactory.onCreate(this);
 
         mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
         mAppWidgetHost = new LauncherAppWidgetHost(this,
@@ -474,7 +476,6 @@
     @Override
     public void onEnterAnimationComplete() {
         super.onEnterAnimationComplete();
-        UiFactory.onEnterAnimationComplete(this);
         mAllAppsController.highlightWorkTabIfNecessary();
         mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE);
     }
@@ -488,7 +489,6 @@
         }
 
         mOldConfig.setTo(newConfig);
-        UiFactory.onLauncherStateOrResumeChanged(this);
         super.onConfigurationChanged(newConfig);
     }
 
@@ -504,7 +504,7 @@
     public void reapplyUi() {
         if (supportsFakeLandscapeUI()) {
             mRotationMode = mStableDeviceProfile == null
-                    ? RotationMode.NORMAL : UiFactory.getRotationMode(mDeviceProfile);
+                    ? RotationMode.NORMAL : getFakeRotationMode(mDeviceProfile);
         }
         getRootView().dispatchInsets();
         getStateManager().reapplyState(true /* cancelCurrentAnimation */);
@@ -567,7 +567,7 @@
 
         if (supportsFakeLandscapeUI() && mDeviceProfile.isVerticalBarLayout()) {
             mStableDeviceProfile = mDeviceProfile.inv.portraitProfile;
-            mRotationMode = UiFactory.getRotationMode(mDeviceProfile);
+            mRotationMode = getFakeRotationMode(mDeviceProfile);
         } else {
             mStableDeviceProfile = null;
             mRotationMode = RotationMode.NORMAL;
@@ -933,8 +933,6 @@
         NotificationListener.removeNotificationsChangedListener();
         getStateManager().moveToRestState();
 
-        UiFactory.onLauncherStateOrResumeChanged(this);
-
         // Workaround for b/78520668, explicitly trim memory once UI is hidden
         onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
     }
@@ -957,7 +955,6 @@
             logStopAndResume(Action.Command.RESUME);
             getUserEventDispatcher().startSession();
 
-            UiFactory.onLauncherStateOrResumeChanged(this);
             AppLaunchTracker.INSTANCE.get(this).onReturnedToHome();
 
             // Process any items that were added while Launcher was away.
@@ -972,15 +969,17 @@
 
             DiscoveryBounce.showForHomeIfNeeded(this);
 
-            if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) {
-                UiFactory.resetPendingActivityResults(this, mPendingActivityRequestCode);
-            }
+            onDeferredResumed();
+            addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED);
+
             mDeferredResumePending = false;
         } else {
             mDeferredResumePending = true;
         }
     }
 
+    protected void onDeferredResumed() { }
+
     private void logStopAndResume(int command) {
         int containerType = mStateManager.getState().containerType;
         if (containerType == ContainerType.WORKSPACE && mWorkspace != null) {
@@ -1034,12 +1033,14 @@
         if (mDeferOverlayCallbacks) {
             scheduleDeferredCheck();
         }
+        addActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
     }
 
     public void onStateSetEnd(LauncherState state) {
         getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
         getWorkspace().setClipChildren(!state.disablePageClipping);
         finishAutoCancelActionMode();
+        removeActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
     }
 
     @Override
@@ -1084,18 +1085,6 @@
         }
     }
 
-    @Override
-    protected void onUserLeaveHint() {
-        super.onUserLeaveHint();
-        UiFactory.onLauncherStateOrResumeChanged(this);
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        super.onWindowFocusChanged(hasFocus);
-        mStateManager.onWindowFocusChanged();
-    }
-
     class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
 
         public void onScrollChanged(float progress) {
@@ -1159,7 +1148,6 @@
 
         // Setup the drag layer
         mDragLayer.setup(mDragController, mWorkspace);
-        mCancelTouchController = UiFactory.enableLiveUIChanges(this);
 
         mWorkspace.setup(mDragController);
         // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
@@ -1537,11 +1525,6 @@
         mWorkspace.removeFolderListeners();
         PluginManagerWrapper.INSTANCE.get(this).removePluginListener(this);
 
-        if (mCancelTouchController != null) {
-            mCancelTouchController.run();
-            mCancelTouchController = null;
-        }
-
         // Stop callbacks from LauncherModel
         // It's possible to receive onDestroy after a new Launcher activity has
         // been created. In this case, don't interfere with the new Launcher.
@@ -1577,10 +1560,7 @@
         if (requestCode != -1) {
             mPendingActivityRequestCode = requestCode;
         }
-        if (requestCode == -1
-                || !UiFactory.startActivityForResult(this, intent, requestCode, options)) {
-            super.startActivityForResult(intent, requestCode, options);
-        }
+        super.startActivityForResult(intent, requestCode, options);
     }
 
     @Override
@@ -1589,14 +1569,11 @@
         if (requestCode != -1) {
             mPendingActivityRequestCode = requestCode;
         }
-        if (requestCode == -1 || !UiFactory.startIntentSenderForResult(this, intent, requestCode,
-                fillInIntent, flagsMask, flagsValues, extraFlags, options)) {
-            try {
-                super.startIntentSenderForResult(intent, requestCode,
-                        fillInIntent, flagsMask, flagsValues, extraFlags, options);
-            } catch (IntentSender.SendIntentException e) {
-                throw new ActivityNotFoundException();
-            }
+        try {
+            super.startIntentSenderForResult(intent, requestCode,
+                    fillInIntent, flagsMask, flagsValues, extraFlags, options);
+        } catch (IntentSender.SendIntentException e) {
+            throw new ActivityNotFoundException();
         }
     }
 
@@ -1934,7 +1911,6 @@
             // This clears all widget bitmaps from the widget tray
             // TODO(hyunyoungs)
         }
-        UiFactory.onTrimMemory(this, level);
     }
 
     @Override
@@ -2653,16 +2629,36 @@
         return super.onKeyUp(keyCode, event);
     }
 
-    public static Launcher getLauncher(Context context) {
-        return (Launcher) fromContext(context);
+    protected StateHandler[] createStateHandlers() {
+        return new StateHandler[] { getAllAppsController(), getWorkspace() };
     }
 
+    public TouchController[] createTouchControllers() {
+        return new TouchController[] {getDragController(), new AllAppsSwipeController(this)};
+    }
+
+    protected RotationMode getFakeRotationMode(DeviceProfile deviceProfile) {
+        return RotationMode.NORMAL;
+    }
+
+    protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() {
+        return new ScaleAndTranslation(1.1f, 0f, 0f);
+    }
+
+    public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { }
+
+    public void onDragLayerHierarchyChanged() { }
+
     @Override
     public void returnToHomescreen() {
         super.returnToHomescreen();
         getStateManager().goToState(LauncherState.NORMAL);
     }
 
+    public static Launcher getLauncher(Context context) {
+        return (Launcher) fromContext(context);
+    }
+
     /**
      * Just a wrapper around the type cast to allow easier tracking of calls.
      */
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 6e2626b..d2b447b 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -26,9 +26,11 @@
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
+import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
@@ -36,14 +38,11 @@
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.states.SpringLoadedState;
-import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.uioverrides.states.AllAppsState;
 import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -210,7 +209,7 @@
     }
 
     public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
-        return UiFactory.getOverviewScaleAndTranslationForNormalState(launcher);
+        return launcher.getOverviewScaleAndTranslationForNormalState();
     }
 
     public float getOverviewFullscreenProgress() {
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 848e19f..daf270b 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -24,7 +24,8 @@
 import android.animation.AnimatorSet;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.Log;
+
+import androidx.annotation.IntDef;
 
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -32,16 +33,12 @@
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.uioverrides.UiFactory;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
-import androidx.annotation.IntDef;
-
 /**
  * TODO: figure out what kind of tests we can write for this
  *
@@ -146,7 +143,7 @@
 
     public StateHandler[] getStateHandlers() {
         if (mStateHandlers == null) {
-            mStateHandlers = UiFactory.getStateHandler(mLauncher);
+            mStateHandlers = mLauncher.createStateHandlers();
         }
         return mStateHandlers;
     }
@@ -414,7 +411,6 @@
             // Only disable clipping if needed, otherwise leave it as previous value.
             mLauncher.getWorkspace().setClipChildren(false);
         }
-        UiFactory.onLauncherStateOrResumeChanged(mLauncher);
 
         for (int i = mListeners.size() - 1; i >= 0; i--) {
             mListeners.get(i).onStateTransitionStart(state);
@@ -435,8 +431,6 @@
             setRestState(null);
         }
 
-        UiFactory.onLauncherStateOrResumeChanged(mLauncher);
-
         for (int i = mListeners.size() - 1; i >= 0; i--) {
             mListeners.get(i).onStateTransitionComplete(state);
         }
@@ -444,10 +438,6 @@
         AccessibilityManagerCompat.sendStateEventToTest(mLauncher, state.ordinal);
     }
 
-    public void onWindowFocusChanged() {
-        UiFactory.onLauncherStateOrFocusChanged(mLauncher);
-    }
-
     public LauncherState getLastState() {
         return mLastStableState;
     }
diff --git a/src/com/android/launcher3/WorkspaceItemInfo.java b/src/com/android/launcher3/WorkspaceItemInfo.java
index 23795c5..71bd92f 100644
--- a/src/com/android/launcher3/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/WorkspaceItemInfo.java
@@ -28,7 +28,7 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.ContentWriter;
 
 import java.util.Arrays;
@@ -192,7 +192,7 @@
         }
         disabledMessage = shortcutInfo.getDisabledMessage();
 
-        Person[] persons = UiFactory.getPersons(shortcutInfo);
+        Person[] persons = ApiWrapper.getPersons(shortcutInfo);
         personKeys = persons.length == 0 ? Utilities.EMPTY_STRING_ARRAY
             : Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);
     }
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index cdc7061..8823bde 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -57,7 +57,6 @@
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
-import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.launcher3.views.Transposable;
@@ -121,7 +120,7 @@
     }
 
     public void recreateControllers() {
-        mControllers = UiFactory.createTouchControllers(mActivity);
+        mControllers = mActivity.createTouchControllers();
     }
 
     public ViewGroupFocusHelper getFocusIndicatorHelper() {
@@ -477,14 +476,14 @@
     public void onViewAdded(View child) {
         super.onViewAdded(child);
         updateChildIndices();
-        UiFactory.onLauncherStateOrFocusChanged(mActivity);
+        mActivity.onDragLayerHierarchyChanged();
     }
 
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
         updateChildIndices();
-        UiFactory.onLauncherStateOrFocusChanged(mActivity);
+        mActivity.onDragLayerHierarchyChanged();
     }
 
     @Override
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index 07eb0d6..869dd94 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -32,7 +32,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -68,7 +67,7 @@
     public boolean init(Launcher launcher, boolean alreadyOnHome) {
         super.init(launcher, alreadyOnHome);
         if (!alreadyOnHome) {
-            UiFactory.useFadeOutAnimationForLauncherStart(launcher, mCancelSignal);
+            launcher.useFadeOutAnimationForLauncherStart(mCancelSignal);
         }
         return false;
     }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 4833c26..8e5d852 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -382,8 +382,7 @@
     @Override
     public void onWidgetsBound() {
         ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
-        SystemShortcut widgetInfo = new SystemShortcut.Widgets();
-        View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
+        SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mLauncher, itemInfo);
         View widgetsView = null;
         int count = mSystemShortcutContainer.getChildCount();
         for (int i = 0; i < count; i++) {
@@ -394,7 +393,7 @@
             }
         }
 
-        if (onClickListener != null && widgetsView == null) {
+        if (widgetInfo != null && widgetsView == null) {
             // We didn't have any widgets cached but now there are some, so enable the shortcut.
             if (mSystemShortcutContainer != this) {
                 initializeSystemShortcut(
@@ -407,7 +406,7 @@
                 close(false);
                 PopupContainerWithArrow.showForIcon(mOriginalIcon);
             }
-        } else if (onClickListener == null && widgetsView != null) {
+        } else if (widgetInfo == null && widgetsView != null) {
             // No widgets exist, but we previously added the shortcut so remove it.
             if (mSystemShortcutContainer != this) {
                 mSystemShortcutContainer.removeView(widgetsView);
@@ -430,8 +429,7 @@
             info.setIconAndContentDescriptionFor((ImageView) view);
         }
         view.setTag(info);
-        view.setOnClickListener(info.getOnClickListener(mLauncher,
-                (ItemInfo) mOriginalIcon.getTag()));
+        view.setOnClickListener(info);
     }
 
     /**
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
index 5a5fbab..8751202 100644
--- a/src/com/android/launcher3/popup/RemoteActionShortcut.java
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -16,13 +16,19 @@
 
 package com.android.launcher3.popup;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.annotation.TargetApi;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
+import android.content.Context;
 import android.content.Intent;
-import android.os.Handler;
-import android.os.Looper;
+import android.os.Build;
 import android.util.Log;
 import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.ImageView;
+import android.widget.TextView;
 import android.widget.Toast;
 
 import com.android.launcher3.AbstractFloatingView;
@@ -32,55 +38,75 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 
+@TargetApi(Build.VERSION_CODES.Q)
 public class RemoteActionShortcut extends SystemShortcut<BaseDraggingActivity> {
     private static final String TAG = "RemoteActionShortcut";
     private static final boolean DEBUG = Utilities.IS_DEBUG_DEVICE;
 
     private final RemoteAction mAction;
 
-    public RemoteActionShortcut(RemoteAction action) {
-        super(action.getIcon(), action.getTitle(), action.getContentDescription(),
-                R.id.action_remote_action_shortcut);
+    public RemoteActionShortcut(RemoteAction action,
+            BaseDraggingActivity activity, ItemInfo itemInfo) {
+        super(0, R.id.action_remote_action_shortcut, activity, itemInfo);
         mAction = action;
     }
 
     @Override
-    public View.OnClickListener getOnClickListener(
-            final BaseDraggingActivity activity, final ItemInfo itemInfo) {
-        return view -> {
-            AbstractFloatingView.closeAllOpenViews(activity);
+    public void setIconAndLabelFor(View iconView, TextView labelView) {
+        mAction.getIcon().loadDrawableAsync(iconView.getContext(),
+                iconView::setBackground,
+                MAIN_EXECUTOR.getHandler());
+        labelView.setText(mAction.getTitle());
+    }
 
-            final String actionIdentity = mAction.getTitle() + ", " +
-                    itemInfo.getTargetComponent().getPackageName();
-            try {
-                if (DEBUG) Log.d(TAG, "Sending action: " + actionIdentity);
-                mAction.getActionIntent().send(
-                        activity,
-                        0,
-                        new Intent().putExtra(
-                                Intent.EXTRA_PACKAGE_NAME,
-                                itemInfo.getTargetComponent().getPackageName()),
-                        (pendingIntent, intent, resultCode, resultData, resultExtras) -> {
-                            if (DEBUG) Log.d(TAG, "Action is complete: " + actionIdentity);
-                            if (resultData != null && !resultData.isEmpty()) {
-                                Log.e(TAG, "Remote action returned result: " + actionIdentity
-                                        + " : " + resultData);
-                                Toast.makeText(activity, resultData, Toast.LENGTH_SHORT).show();
-                            }
-                        },
-                        new Handler(Looper.getMainLooper()));
-            } catch (PendingIntent.CanceledException e) {
-                Log.e(TAG, "Remote action canceled: " + actionIdentity, e);
-                Toast.makeText(activity, activity.getString(
-                        R.string.remote_action_failed,
-                        mAction.getTitle()),
-                        Toast.LENGTH_SHORT)
-                        .show();
-            }
+    @Override
+    public void setIconAndContentDescriptionFor(ImageView view) {
+        mAction.getIcon().loadDrawableAsync(view.getContext(),
+                view::setImageDrawable,
+                MAIN_EXECUTOR.getHandler());
+        view.setContentDescription(mAction.getContentDescription());
+    }
 
-            activity.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
-                    LauncherLogProto.ControlType.REMOTE_ACTION_SHORTCUT, view);
-        };
+    @Override
+    public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(Context context) {
+        return new AccessibilityNodeInfo.AccessibilityAction(
+                R.id.action_remote_action_shortcut, mAction.getContentDescription());
+    }
+
+    @Override
+    public void onClick(View view) {
+        AbstractFloatingView.closeAllOpenViews(mTarget);
+
+        final String actionIdentity = mAction.getTitle() + ", "
+                + mItemInfo.getTargetComponent().getPackageName();
+        try {
+            if (DEBUG) Log.d(TAG, "Sending action: " + actionIdentity);
+            mAction.getActionIntent().send(
+                    mTarget,
+                    0,
+                    new Intent().putExtra(
+                            Intent.EXTRA_PACKAGE_NAME,
+                            mItemInfo.getTargetComponent().getPackageName()),
+                    (pendingIntent, intent, resultCode, resultData, resultExtras) -> {
+                        if (DEBUG) Log.d(TAG, "Action is complete: " + actionIdentity);
+                        if (resultData != null && !resultData.isEmpty()) {
+                            Log.e(TAG, "Remote action returned result: " + actionIdentity
+                                    + " : " + resultData);
+                            Toast.makeText(mTarget, resultData, Toast.LENGTH_SHORT).show();
+                        }
+                    },
+                    MAIN_EXECUTOR.getHandler());
+        } catch (PendingIntent.CanceledException e) {
+            Log.e(TAG, "Remote action canceled: " + actionIdentity, e);
+            Toast.makeText(mTarget, mTarget.getString(
+                    R.string.remote_action_failed,
+                    mAction.getTitle()),
+                    Toast.LENGTH_SHORT)
+                    .show();
+        }
+
+        mTarget.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
+                LauncherLogProto.ControlType.REMOTE_ACTION_SHORTCUT, view);
     }
 
     @Override
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index a87b7b8..222c6c9 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -5,14 +5,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Rect;
-import android.graphics.drawable.Icon;
-import android.os.Handler;
-import android.os.Looper;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ItemInfo;
@@ -39,41 +38,30 @@
  * Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
  * @param <T>
  */
-public abstract class SystemShortcut<T extends BaseDraggingActivity>
-        extends ItemInfo {
+public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo
+        implements View.OnClickListener {
+
     private final int mIconResId;
     private final int mLabelResId;
-    private final Icon mIcon;
-    private final CharSequence mLabel;
-    private final CharSequence mContentDescription;
     private final int mAccessibilityActionId;
 
-    public SystemShortcut(int iconResId, int labelResId) {
+    protected final T mTarget;
+    protected final ItemInfo mItemInfo;
+
+    public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo) {
         mIconResId = iconResId;
         mLabelResId = labelResId;
         mAccessibilityActionId = labelResId;
-        mIcon = null;
-        mLabel = null;
-        mContentDescription = null;
+        mTarget = target;
+        mItemInfo = itemInfo;
     }
 
-    public SystemShortcut(Icon icon, CharSequence label, CharSequence contentDescription,
-            int accessibilityActionId) {
-        mIcon = icon;
-        mLabel = label;
-        mContentDescription = contentDescription;
-        mAccessibilityActionId = accessibilityActionId;
-        mIconResId = 0;
-        mLabelResId = 0;
-    }
-
-    public SystemShortcut(SystemShortcut other) {
+    public SystemShortcut(SystemShortcut<T> other) {
         mIconResId = other.mIconResId;
         mLabelResId = other.mLabelResId;
-        mIcon = other.mIcon;
-        mLabel = other.mLabel;
-        mContentDescription = other.mContentDescription;
         mAccessibilityActionId = other.mAccessibilityActionId;
+        mTarget = other.mTarget;
+        mItemInfo = other.mItemInfo;
     }
 
     /**
@@ -84,150 +72,135 @@
     }
 
     public void setIconAndLabelFor(View iconView, TextView labelView) {
-        if (mIcon != null) {
-            mIcon.loadDrawableAsync(iconView.getContext(),
-                    iconView::setBackground,
-                    new Handler(Looper.getMainLooper()));
-        } else {
-            iconView.setBackgroundResource(mIconResId);
-        }
-
-        if (mLabel != null) {
-            labelView.setText(mLabel);
-        } else {
-            labelView.setText(mLabelResId);
-        }
+        iconView.setBackgroundResource(mIconResId);
+        labelView.setText(mLabelResId);
     }
 
     public void setIconAndContentDescriptionFor(ImageView view) {
-        if (mIcon != null) {
-            mIcon.loadDrawableAsync(view.getContext(),
-                    view::setImageDrawable,
-                    new Handler(Looper.getMainLooper()));
-        } else {
-            view.setImageResource(mIconResId);
-        }
-
-        view.setContentDescription(getContentDescription(view.getContext()));
-    }
-
-    private CharSequence getContentDescription(Context context) {
-        return mContentDescription != null ? mContentDescription : context.getText(mLabelResId);
+        view.setImageResource(mIconResId);
+        view.setContentDescription(view.getContext().getText(mLabelResId));
     }
 
     public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(Context context) {
-        return new AccessibilityNodeInfo.AccessibilityAction(mAccessibilityActionId,
-                getContentDescription(context));
+        return new AccessibilityNodeInfo.AccessibilityAction(
+                mAccessibilityActionId, context.getText(mLabelResId));
     }
 
     public boolean hasHandlerForAction(int action) {
         return mAccessibilityActionId == action;
     }
 
-    public abstract View.OnClickListener getOnClickListener(T activity, ItemInfo itemInfo);
+    public interface Factory<T extends BaseDraggingActivity> {
+
+        @Nullable SystemShortcut<T> getShortcut(T activity, ItemInfo itemInfo);
+    }
+
+    public static final Factory<Launcher> WIDGETS = (launcher, itemInfo) -> {
+        if (itemInfo.getTargetComponent() == null) return null;
+        final List<WidgetItem> widgets =
+                launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
+                        itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
+        if (widgets == null) {
+            return null;
+        }
+        return new Widgets(launcher, itemInfo);
+    };
 
     public static class Widgets extends SystemShortcut<Launcher> {
 
-        public Widgets() {
-            super(R.drawable.ic_widget, R.string.widget_button_text);
+        public Widgets(Launcher target, ItemInfo itemInfo) {
+            super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo);
         }
 
         @Override
-        public View.OnClickListener getOnClickListener(final Launcher launcher,
-                final ItemInfo itemInfo) {
-            if (itemInfo.getTargetComponent() == null) return null;
-            final List<WidgetItem> widgets =
-                    launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
-                            itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
-            if (widgets == null) {
-                return null;
-            }
-            return (view) -> {
-                AbstractFloatingView.closeAllOpenViews(launcher);
-                WidgetsBottomSheet widgetsBottomSheet =
-                        (WidgetsBottomSheet) launcher.getLayoutInflater().inflate(
-                                R.layout.widgets_bottom_sheet, launcher.getDragLayer(), false);
-                widgetsBottomSheet.populateAndShow(itemInfo);
-                launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
-                        ControlType.WIDGETS_BUTTON, view);
-            };
+        public void onClick(View view) {
+            AbstractFloatingView.closeAllOpenViews(mTarget);
+            WidgetsBottomSheet widgetsBottomSheet =
+                    (WidgetsBottomSheet) mTarget.getLayoutInflater().inflate(
+                            R.layout.widgets_bottom_sheet, mTarget.getDragLayer(), false);
+            widgetsBottomSheet.populateAndShow(mItemInfo);
+            mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
+                    ControlType.WIDGETS_BUTTON, view);
         }
     }
 
+    public static final Factory<BaseDraggingActivity> APP_INFO = AppInfo::new;
+
     public static class AppInfo extends SystemShortcut {
-        public AppInfo() {
-            super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label);
+
+        public AppInfo(BaseDraggingActivity target, ItemInfo itemInfo) {
+            super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target,
+                    itemInfo);
         }
 
         @Override
-        public View.OnClickListener getOnClickListener(
-                BaseDraggingActivity activity, ItemInfo itemInfo) {
-            return (view) -> {
-                dismissTaskMenuView(activity);
-                Rect sourceBounds = activity.getViewBounds(view);
-                new PackageManagerHelper(activity).startDetailsActivityForInfo(
-                        itemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
-                activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
-                        ControlType.APPINFO_TARGET, view);
-            };
+        public void onClick(View view) {
+            dismissTaskMenuView(mTarget);
+            Rect sourceBounds = mTarget.getViewBounds(view);
+            new PackageManagerHelper(mTarget).startDetailsActivityForInfo(
+                    mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
+            mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
+                    ControlType.APPINFO_TARGET, view);
         }
     }
 
+    public static Factory<BaseDraggingActivity> INSTALL = (activity, itemInfo) -> {
+        boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo)
+                && ((WorkspaceItemInfo) itemInfo).hasStatusFlag(
+                        WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI);
+        boolean isInstantApp = false;
+        if (itemInfo instanceof com.android.launcher3.AppInfo) {
+            com.android.launcher3.AppInfo appInfo = (com.android.launcher3.AppInfo) itemInfo;
+            isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo);
+        }
+        boolean enabled = supportsWebUI || isInstantApp;
+        if (!enabled) {
+            return null;
+        }
+        return new Install(activity, itemInfo);
+    };
+
     public static class Install extends SystemShortcut {
-        public Install() {
-            super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label);
+
+        public Install(BaseDraggingActivity target, ItemInfo itemInfo) {
+            super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label,
+                    target, itemInfo);
         }
 
         @Override
-        public View.OnClickListener getOnClickListener(
-                BaseDraggingActivity activity, ItemInfo itemInfo) {
-            boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo) &&
-                    ((WorkspaceItemInfo) itemInfo).hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI);
-            boolean isInstantApp = false;
-            if (itemInfo instanceof com.android.launcher3.AppInfo) {
-                com.android.launcher3.AppInfo appInfo = (com.android.launcher3.AppInfo) itemInfo;
-                isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo);
-            }
-            boolean enabled = supportsWebUI || isInstantApp;
-            if (!enabled) {
-                return null;
-            }
-            return createOnClickListener(activity, itemInfo);
-        }
-
-        public View.OnClickListener createOnClickListener(
-                BaseDraggingActivity activity, ItemInfo itemInfo) {
-            return view -> {
-                Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
-                        itemInfo.getTargetComponent().getPackageName());
-                activity.startActivitySafely(view, intent, itemInfo, null);
-                AbstractFloatingView.closeAllOpenViews(activity);
-            };
+        public void onClick(View view) {
+            Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
+                    mItemInfo.getTargetComponent().getPackageName());
+            mTarget.startActivitySafely(view, intent, mItemInfo, null);
+            AbstractFloatingView.closeAllOpenViews(mTarget);
         }
     }
 
+    public static Factory<Launcher> DISMISS_PREDICTION = (launcher, itemInfo) -> {
+        if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null;
+        if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) return null;
+        return new DismissPrediction(launcher, itemInfo);
+    };
+
     public static class DismissPrediction extends SystemShortcut<Launcher> {
-        public DismissPrediction() {
-            super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label);
+        public DismissPrediction(Launcher launcher, ItemInfo itemInfo) {
+            super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label, launcher,
+                    itemInfo);
         }
 
         @Override
-        public View.OnClickListener getOnClickListener(Launcher activity, ItemInfo itemInfo) {
-            if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null;
-            if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) return null;
-            return (view) -> {
-                PopupContainerWithArrow.closeAllOpenViews(activity);
-                activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
-                        ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS);
-                AppLaunchTracker.INSTANCE.get(view.getContext())
-                        .onDismissApp(itemInfo.getTargetComponent(),
-                                itemInfo.user,
-                                AppLaunchTracker.CONTAINER_PREDICTIONS);
-            };
+        public void onClick(View view) {
+            PopupContainerWithArrow.closeAllOpenViews(mTarget);
+            mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
+                    ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS);
+            AppLaunchTracker.INSTANCE.get(view.getContext()).onDismissApp(
+                    mItemInfo.getTargetComponent(),
+                    mItemInfo.user,
+                    AppLaunchTracker.CONTAINER_PREDICTIONS);
         }
     }
 
-    protected static void dismissTaskMenuView(BaseDraggingActivity activity) {
+    public static void dismissTaskMenuView(BaseDraggingActivity activity) {
         AbstractFloatingView.closeOpenViews(activity, true,
             AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
     }
diff --git a/src/com/android/launcher3/popup/SystemShortcutFactory.java b/src/com/android/launcher3/popup/SystemShortcutFactory.java
index dfcc2f8..8b8a4d0 100644
--- a/src/com/android/launcher3/popup/SystemShortcutFactory.java
+++ b/src/com/android/launcher3/popup/SystemShortcutFactory.java
@@ -34,25 +34,24 @@
             forOverride(SystemShortcutFactory.class, R.string.system_shortcut_factory_class);
 
     /** Note that these are in order of priority. */
-    private final SystemShortcut[] mAllShortcuts;
+    private final SystemShortcut.Factory[] mAllFactories;
 
     @SuppressWarnings("unused")
     public SystemShortcutFactory() {
-        this(new SystemShortcut.AppInfo(),
-                new SystemShortcut.Widgets(),
-                new SystemShortcut.Install(),
-                new SystemShortcut.DismissPrediction());
+        this(SystemShortcut.APP_INFO, SystemShortcut.WIDGETS, SystemShortcut.INSTALL,
+                SystemShortcut.DISMISS_PREDICTION);
     }
 
-    protected SystemShortcutFactory(SystemShortcut... shortcuts) {
-        mAllShortcuts = shortcuts;
+    protected SystemShortcutFactory(SystemShortcut.Factory... factories) {
+        mAllFactories = factories;
     }
 
     public @NonNull List<SystemShortcut> getEnabledShortcuts(Launcher launcher, ItemInfo info) {
         List<SystemShortcut> systemShortcuts = new ArrayList<>();
-        for (SystemShortcut systemShortcut : mAllShortcuts) {
-            if (systemShortcut.getOnClickListener(launcher, info) != null) {
-                systemShortcuts.add(systemShortcut);
+        for (SystemShortcut.Factory factory : mAllFactories) {
+            SystemShortcut shortcut = factory.getShortcut(launcher, info);
+            if (shortcut != null) {
+                systemShortcuts.add(shortcut);
             }
         }
 
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
similarity index 77%
rename from src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
rename to src/com/android/launcher3/touch/AllAppsSwipeController.java
index bd6ea50..195c7f0 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -1,4 +1,19 @@
-package com.android.launcher3.uioverrides;
+/**
+ * Copyright (C) 2019 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.touch;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
@@ -9,8 +24,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationComponents;
-import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
@@ -58,8 +71,8 @@
 
     @Override
     protected int getLogContainerTypeForNormalState(MotionEvent ev) {
-        return mLauncher.getDragLayer().isEventOverView(mLauncher.getHotseat(), mTouchDownEvent) ?
-                ContainerType.HOTSEAT : ContainerType.WORKSPACE;
+        return mLauncher.getDragLayer().isEventOverView(mLauncher.getHotseat(), mTouchDownEvent)
+                ? ContainerType.HOTSEAT : ContainerType.WORKSPACE;
     }
 
     @Override
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
index a133f01..ec87e79 100644
--- a/src/com/android/launcher3/util/UiThreadHelper.java
+++ b/src/com/android/launcher3/util/UiThreadHelper.java
@@ -23,7 +23,6 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.view.inputmethod.InputMethodManager;
-import com.android.launcher3.uioverrides.UiFactory;
 
 /**
  * Utility class for offloading some class from UI thread
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
new file mode 100644
index 0000000..5407ea3
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.app.Activity;
+import android.app.Person;
+import android.content.pm.ShortcutInfo;
+
+import com.android.launcher3.Utilities;
+
+import java.io.PrintWriter;
+
+public class ApiWrapper {
+
+    public static boolean dumpActivity(Activity activity, PrintWriter writer) {
+        return false;
+    }
+
+    public static Person[] getPersons(ShortcutInfo si) {
+        return Utilities.EMPTY_PERSON_ARRAY;
+    }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
deleted file mode 100644
index 606c990..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import android.app.Activity;
-import android.app.Person;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ShortcutInfo;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.graphics.RotationMode;
-import com.android.launcher3.util.TouchController;
-
-import java.io.PrintWriter;
-
-public class UiFactory {
-
-    public static TouchController[] createTouchControllers(Launcher launcher) {
-        return new TouchController[] {
-                launcher.getDragController(), new AllAppsSwipeController(launcher)};
-    }
-
-    public static Runnable enableLiveUIChanges(Launcher l) {
-        return null;
-    }
-
-    public static StateHandler[] getStateHandler(Launcher launcher) {
-        return new StateHandler[] {
-                launcher.getAllAppsController(), launcher.getWorkspace() };
-    }
-
-    public static void resetOverview(Launcher launcher) { }
-
-    public static void onLauncherStateOrFocusChanged(Launcher launcher) { }
-
-    public static void onCreate(Launcher launcher) { }
-
-    public static void onStart(Launcher launcher) { }
-
-    public static void onEnterAnimationComplete(Context context) {}
-
-    public static void onLauncherStateOrResumeChanged(Launcher launcher) { }
-
-    public static void onTrimMemory(Launcher launcher, int level) { }
-
-    public static void useFadeOutAnimationForLauncherStart(Launcher launcher,
-            CancellationSignal cancellationSignal) { }
-
-    public static boolean dumpActivity(Activity activity, PrintWriter writer) {
-        return false;
-    }
-
-    public static void setBackButtonAlpha(Launcher launcher, float alpha, boolean animate) { }
-
-
-    public static ScaleAndTranslation getOverviewScaleAndTranslationForNormalState(Launcher l) {
-        return new ScaleAndTranslation(1.1f, 0f, 0f);
-    }
-
-    public static RotationMode getRotationMode(DeviceProfile dp) {
-        return RotationMode.NORMAL;
-    }
-
-    public static boolean startIntentSenderForResult(Activity activity, IntentSender intent,
-            int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
-            Bundle options) {
-        return false;
-    }
-
-    public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode,
-            Bundle options) {
-        return false;
-    }
-
-    public static void resetPendingActivityResults(Launcher launcher, int requestCode) { }
-
-    public static Person[] getPersons(ShortcutInfo si) {
-        return Utilities.EMPTY_PERSON_ARRAY;
-    }
-}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 465cee2..29da0fa 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -18,6 +18,10 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_PRESUBMIT;
+import static com.android.launcher3.util.rule.TestStabilityRule.RUN_FLAFOR;
+import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_PRESUBMIT;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -174,7 +178,8 @@
     @Test
     public void testWorkspace() throws Exception {
         // b/142828227
-        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess()) {
+        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() &&
+                (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) {
             return;
         }
         final Workspace workspace = mLauncher.getWorkspace();
diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
index 69bf01d..08fa098 100644
--- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
@@ -57,7 +57,7 @@
     public static final int PLATFORM_PRESUBMIT = 0x8;
     public static final int PLATFORM_POSTSUBMIT = 0x10;
 
-    private static final int RUN_FLAFOR = getRunFlavor();
+    public static final int RUN_FLAFOR = getRunFlavor();
 
     @Retention(RetentionPolicy.RUNTIME)
     @Target(ElementType.METHOD)