Separating SystemShortcut and Factory

> This makes easier to extend the factory and callbacks separately
> Availability checks are only performed once when getting the shortcut

Change-Id: I413541eabfb2b9e987c852d5171c6696b1853958
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/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/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);
             }
         }