Adding support for remote app action provider.

> Connect to a predefined content provider to get package specific action
> Add that action to shortcut menu and task menu

Change-Id: Ide5c09d04112e86c8e19c2f9e66c88c15b3fd04e
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 298b0ff..b5441df 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -45,13 +45,11 @@
             TaskShortcutFactory.SPLIT_SCREEN,
             TaskShortcutFactory.PIN,
             TaskShortcutFactory.INSTALL,
-            TaskShortcutFactory.FREE_FORM
+            TaskShortcutFactory.FREE_FORM,
+            TaskShortcutFactory.WELLBEING
     };
 
-    public static final MainThreadInitializedObject<TaskOverlayFactory> INSTANCE =
-            forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class);
-
-    public List<SystemShortcut> getEnabledShortcuts(TaskView taskView) {
+    public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView) {
         final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
         final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
         for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
@@ -63,6 +61,9 @@
         return shortcuts;
     }
 
+    public static final MainThreadInitializedObject<TaskOverlayFactory> INSTANCE =
+            forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class);
+
     public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
         return new TaskOverlay();
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
index a3a1d6d..9ba2e5a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.popup.SystemShortcut.AppInfo;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -309,4 +310,6 @@
                  view.getTask().getTopComponent().getPackageName())
                     ? new SystemShortcut.Install(activity, dummyInfo(view)) : null;
 
+    TaskShortcutFactory WELLBEING = (activity, view) ->
+            WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, dummyInfo(view));
 }
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 b810c4a..80022b4 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
@@ -192,8 +192,7 @@
         params.topMargin = (int) -mThumbnailTopMargin;
         mTaskIcon.setLayoutParams(params);
 
-        TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(taskView)
-                .forEach(this::addMenuOption);
+        TaskOverlayFactory.getEnabledShortcuts(taskView).forEach(this::addMenuOption);
     }
 
     private void addMenuOption(SystemShortcut menuOption) {
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 3af0f70..0dfc39b 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
@@ -713,8 +713,7 @@
                         getContext().getText(R.string.accessibility_close_task)));
 
         final Context context = getContext();
-        for (SystemShortcut s : TaskOverlayFactory.INSTANCE.get(getContext())
-                .getEnabledShortcuts(this)) {
+        for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
             info.addAction(s.createAccessibilityAction(context));
         }
 
@@ -746,8 +745,7 @@
             return true;
         }
 
-        for (SystemShortcut s : TaskOverlayFactory.INSTANCE.get(getContext())
-                .getEnabledShortcuts(this)) {
+        for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
             if (s.hasHandlerForAction(action)) {
                 s.onClick(this);
                 return true;
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 98aaceb..5d9a009 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -33,4 +33,6 @@
     <!-- Assistant Gesture -->
     <integer name="assistant_gesture_min_time_threshold">200</integer>
     <integer name="assistant_gesture_corner_deg_threshold">20</integer>
+
+    <string name="wellbeing_provider_pkg" translatable="false"></string>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index f55ef94..fc9cfcd 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -35,6 +35,8 @@
 
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.model.WellbeingModel;
+import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.proxy.ProxyActivityStarter;
 import com.android.launcher3.proxy.StartActivityParams;
 import com.android.launcher3.uioverrides.BackButtonAlphaHandler;
@@ -47,6 +49,8 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 
+import java.util.stream.Stream;
+
 /**
  * Extension of Launcher activity to provide quickstep specific functionality
  */
@@ -251,4 +255,19 @@
             getRootView().setDisallowBackGesture(shouldBackButtonBeHidden);
         }
     }
+
+    @Override
+    public void finishBindingItems(int pageBoundFirst) {
+        super.finishBindingItems(pageBoundFirst);
+        // Instantiate and initialize WellbeingModel now that its loading won't interfere with
+        // populating workspace.
+        // TODO: Find a better place for this
+        WellbeingModel.get(this);
+    }
+
+    @Override
+    public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
+        return Stream.concat(super.getSupportedShortcuts(),
+                Stream.of(WellbeingModel.SHORTCUT_FACTORY));
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
new file mode 100644
index 0000000..852a08e
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model;
+
+import static android.content.ContentResolver.SCHEME_CONTENT;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
+
+import android.annotation.TargetApi;
+import android.app.RemoteAction;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.LauncherApps;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.popup.RemoteActionShortcut;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Data model for digital wellbeing status of apps.
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+public final class WellbeingModel {
+    private static final String TAG = "WellbeingModel";
+    private static final int[] RETRY_TIMES_MS = {5000, 15000, 30000};
+    private static final boolean DEBUG = false;
+
+    private static final int MSG_PACKAGE_ADDED = 1;
+    private static final int MSG_PACKAGE_REMOVED = 2;
+    private static final int MSG_FULL_REFRESH = 3;
+
+    // Welbeing contract
+    private static final String METHOD_GET_ACTIONS = "get_actions";
+    private static final String EXTRA_ACTIONS = "actions";
+    private static final String EXTRA_ACTION = "action";
+    private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown";
+    private static final String EXTRA_PACKAGES = "packages";
+
+    private static WellbeingModel sInstance;
+
+    private final Context mContext;
+    private final String mWellbeingProviderPkg;
+    private final Handler mWorkerHandler;
+
+    private final ContentObserver mContentObserver;
+
+    private final Object mModelLock = new Object();
+    // Maps the action Id to the corresponding RemoteAction
+    private final Map<String, RemoteAction> mActionIdMap = new ArrayMap<>();
+    private final Map<String, String> mPackageToActionId = new HashMap<>();
+
+    private boolean mIsInTest;
+
+    private WellbeingModel(final Context context) {
+        mContext = context;
+        mWorkerHandler =
+                new Handler(createAndStartNewLooper("WellbeingHandler"), this::handleMessage);
+
+        mWellbeingProviderPkg = mContext.getString(R.string.wellbeing_provider_pkg);
+        mContentObserver = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                // Wellbeing reports that app actions have changed.
+                if (DEBUG || mIsInTest) {
+                    Log.d(TAG, "ContentObserver.onChange() called with: selfChange = [" + selfChange
+                            + "], uri = [" + uri + "]");
+                }
+                Preconditions.assertUIThread();
+                updateWellbeingData();
+            }
+        };
+
+        if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
+            context.registerReceiver(
+                    new SimpleBroadcastReceiver(this::onWellbeingProviderChanged),
+                    PackageManagerHelper.getPackageFilter(mWellbeingProviderPkg,
+                            Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED,
+                            Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_DATA_CLEARED,
+                            Intent.ACTION_PACKAGE_RESTARTED));
+
+            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addDataScheme("package");
+            context.registerReceiver(new SimpleBroadcastReceiver(this::onAppPackageChanged),
+                    filter);
+
+            restartObserver();
+        }
+    }
+
+    public void setInTest(boolean inTest) {
+        mIsInTest = inTest;
+    }
+
+    protected void onWellbeingProviderChanged(Intent intent) {
+        if (DEBUG || mIsInTest) {
+            Log.d(TAG, "Changes to Wellbeing package: intent = [" + intent + "]");
+        }
+        restartObserver();
+    }
+
+    private void restartObserver() {
+        final ContentResolver resolver = mContext.getContentResolver();
+        resolver.unregisterContentObserver(mContentObserver);
+        Uri actionsUri = apiBuilder().path("actions").build();
+        try {
+            resolver.registerContentObserver(
+                    actionsUri, true /* notifyForDescendants */, mContentObserver);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e);
+            if (mIsInTest) throw new RuntimeException(e);
+        }
+        updateWellbeingData();
+    }
+
+    @MainThread
+    public static WellbeingModel get(@NonNull Context context) {
+        Preconditions.assertUIThread();
+        if (sInstance == null) {
+            sInstance = new WellbeingModel(context.getApplicationContext());
+        }
+        return sInstance;
+    }
+
+    @MainThread
+    private SystemShortcut getShortcutForApp(String packageName, int userId,
+            BaseDraggingActivity activity, ItemInfo info) {
+        Preconditions.assertUIThread();
+        // Work profile apps are not recognized by digital wellbeing.
+        if (userId != UserHandle.myUserId()) {
+            if (DEBUG || mIsInTest) {
+                Log.d(TAG, "getShortcutForApp [" + packageName + "]: not current user");
+            }
+            return null;
+        }
+
+        synchronized (mModelLock) {
+            String actionId = mPackageToActionId.get(packageName);
+            final RemoteAction action = actionId != null ? mActionIdMap.get(actionId) : null;
+            if (action == null) {
+                if (DEBUG || mIsInTest) {
+                    Log.d(TAG, "getShortcutForApp [" + packageName + "]: no action");
+                }
+                return null;
+            }
+            if (DEBUG || mIsInTest) {
+                Log.d(TAG,
+                        "getShortcutForApp [" + packageName + "]: action: '" + action.getTitle()
+                                + "'");
+            }
+            return new RemoteActionShortcut(action, activity, info);
+        }
+    }
+
+    private void updateWellbeingData() {
+        mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH);
+    }
+
+    private Uri.Builder apiBuilder() {
+        return new Uri.Builder()
+                .scheme(SCHEME_CONTENT)
+                .authority(mWellbeingProviderPkg + ".api");
+    }
+
+    private boolean updateActions(String... packageNames) {
+        if (packageNames.length == 0) {
+            return true;
+        }
+        if (DEBUG || mIsInTest) {
+            Log.d(TAG, "retrieveActions() called with: packageNames = [" + String.join(", ",
+                    packageNames) + "]");
+        }
+        Preconditions.assertNonUiThread();
+
+        Uri contentUri = apiBuilder().build();
+        final Bundle remoteActionBundle;
+        try (ContentProviderClient client = mContext.getContentResolver()
+                .acquireUnstableContentProviderClient(contentUri)) {
+            if (client == null) {
+                if (DEBUG || mIsInTest) Log.i(TAG, "retrieveActions(): null provider");
+                return false;
+            }
+
+            // Prepare wellbeing call parameters.
+            final Bundle params = new Bundle();
+            params.putStringArray(EXTRA_PACKAGES, packageNames);
+            params.putInt(EXTRA_MAX_NUM_ACTIONS_SHOWN, 1);
+            // Perform wellbeing call .
+            remoteActionBundle = client.call(METHOD_GET_ACTIONS, null, params);
+        } catch (DeadObjectException e) {
+            Log.i(TAG, "retrieveActions(): DeadObjectException");
+            return false;
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to retrieve data from " + contentUri + ": " + e);
+            if (mIsInTest) throw new RuntimeException(e);
+            return true;
+        }
+
+        synchronized (mModelLock) {
+            // Remove the entries for requested packages, and then update the fist with what we
+            // got from service
+            Arrays.stream(packageNames).forEach(mPackageToActionId::remove);
+
+            // The result consists of sub-bundles, each one is per a remote action. Each sub-bundle
+            // has a RemoteAction and a list of packages to which the action applies.
+            for (String actionId :
+                    remoteActionBundle.getStringArray(EXTRA_ACTIONS)) {
+                final Bundle actionBundle = remoteActionBundle.getBundle(actionId);
+                mActionIdMap.put(actionId,
+                        actionBundle.getParcelable(EXTRA_ACTION));
+
+                final String[] packagesForAction =
+                        actionBundle.getStringArray(EXTRA_PACKAGES);
+                if (DEBUG || mIsInTest) {
+                    Log.d(TAG, "....actionId: " + actionId + ", packages: " + String.join(", ",
+                            packagesForAction));
+                }
+                for (String packageName : packagesForAction) {
+                    mPackageToActionId.put(packageName, actionId);
+                }
+            }
+        }
+        if (DEBUG || mIsInTest) Log.i(TAG, "retrieveActions(): finished");
+        return true;
+    }
+
+    private boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_PACKAGE_REMOVED: {
+                String packageName = (String) msg.obj;
+                mWorkerHandler.removeCallbacksAndMessages(packageName);
+                synchronized (mModelLock) {
+                    mPackageToActionId.remove(packageName);
+                }
+                return true;
+            }
+            case MSG_PACKAGE_ADDED: {
+                String packageName = (String) msg.obj;
+                mWorkerHandler.removeCallbacksAndMessages(packageName);
+                if (!updateActions(packageName)) {
+                    scheduleRefreshRetry(msg);
+                }
+                return true;
+            }
+
+            case MSG_FULL_REFRESH: {
+                // Remove all existing messages
+                mWorkerHandler.removeCallbacksAndMessages(null);
+                final String[] packageNames = mContext.getSystemService(LauncherApps.class)
+                            .getActivityList(null, Process.myUserHandle()).stream()
+                            .map(li -> li.getApplicationInfo().packageName).distinct()
+                            .toArray(String[]::new);
+                if (!updateActions(packageNames)) {
+                    scheduleRefreshRetry(msg);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void scheduleRefreshRetry(Message originalMsg) {
+        int retryCount = originalMsg.arg1;
+        if (retryCount >= RETRY_TIMES_MS.length) {
+            // To many retries, skip
+            return;
+        }
+
+        Message msg = Message.obtain(originalMsg);
+        msg.arg1 = retryCount + 1;
+        mWorkerHandler.sendMessageDelayed(msg, RETRY_TIMES_MS[retryCount]);
+    }
+
+    private void onAppPackageChanged(Intent intent) {
+        if (DEBUG || mIsInTest) Log.d(TAG, "Changes in apps: intent = [" + intent + "]");
+        Preconditions.assertUIThread();
+
+        final String packageName = intent.getData().getSchemeSpecificPart();
+        if (packageName == null || packageName.length() == 0) {
+            // they sent us a bad intent
+            return;
+        }
+
+        final String action = intent.getAction();
+        if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+            Message.obtain(mWorkerHandler, MSG_PACKAGE_REMOVED, packageName).sendToTarget();
+        } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+            Message.obtain(mWorkerHandler, MSG_PACKAGE_ADDED, packageName).sendToTarget();
+        }
+    }
+
+    /**
+     * Shortcut factory for generating wellbeing action
+     */
+    public static final SystemShortcut.Factory SHORTCUT_FACTORY = (activity, info) ->
+            WellbeingModel.get(activity).getShortcutForApp(
+                    info.getTargetComponent().getPackageName(),
+                    info.user.getIdentifier(),
+                    activity, info);
+}
diff --git a/res/values/config.xml b/res/values/config.xml
index 0387184..10671c5 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -69,7 +69,6 @@
     <string name="app_transition_manager_class" translatable="false"></string>
     <string name="instant_app_resolver_class" translatable="false"></string>
     <string name="main_process_initializer_class" translatable="false"></string>
-    <string name="system_shortcut_factory_class" translatable="false"></string>
     <string name="app_launch_tracker_class" translatable="false"></string>
     <string name="test_information_handler_class" translatable="false"></string>
     <string name="launcher_activity_logic_class" translatable="false"></string>
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index bb08ba8..3dce9fc 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -31,6 +31,10 @@
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.logging.LoggerUtils.newTarget;
+import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
+import static com.android.launcher3.popup.SystemShortcut.DISMISS_PREDICTION;
+import static com.android.launcher3.popup.SystemShortcut.INSTALL;
+import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 import static com.android.launcher3.testing.TestProtocol.CRASH_ADD_CUSTOM_SHORTCUT;
 
@@ -114,6 +118,7 @@
 import com.android.launcher3.pm.PinRequestHelper;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.qsb.QsbContainerView;
 import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.testing.TestProtocol;
@@ -170,6 +175,7 @@
 import java.util.List;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
+import java.util.stream.Stream;
 
 /**
  * Default launcher application.
@@ -2655,6 +2661,10 @@
         getStateManager().goToState(LauncherState.NORMAL);
     }
 
+    public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
+        return Stream.of(APP_INFO, WIDGETS, INSTALL, DISMISS_PREDICTION);
+    }
+
     public static Launcher getLauncher(Context context) {
         return (Launcher) fromContext(context);
     }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 8e5d852..2034926 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -77,6 +77,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 /**
  * A container for shortcuts to deep links and notifications associated with an app.
@@ -213,7 +214,7 @@
         final PopupContainerWithArrow container =
                 (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
                         R.layout.popup_container, launcher.getDragLayer(), false);
-        container.populateAndShow(icon, itemInfo, SystemShortcutFactory.INSTANCE.get(launcher));
+        container.populateAndShow(icon, itemInfo);
         return container;
     }
 
@@ -238,8 +239,7 @@
         }
     }
 
-    protected void populateAndShow(
-            BubbleTextView icon, ItemInfo item, SystemShortcutFactory factory) {
+    protected void populateAndShow(BubbleTextView icon, ItemInfo item) {
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.NO_CONTEXT_MENU, "populateAndShow");
         }
@@ -247,7 +247,10 @@
         populateAndShow(icon,
                 popupDataProvider.getShortcutCountForItem(item),
                 popupDataProvider.getNotificationKeysForItem(item),
-                factory.getEnabledShortcuts(mLauncher, item));
+                mLauncher.getSupportedShortcuts()
+                        .map(s -> s.getShortcut(mLauncher, item))
+                        .filter(s -> s != null)
+                        .collect(Collectors.toList()));
     }
 
     public ViewGroup getSystemShortcutContainerForTesting() {
diff --git a/src/com/android/launcher3/popup/SystemShortcutFactory.java b/src/com/android/launcher3/popup/SystemShortcutFactory.java
deleted file mode 100644
index 8b8a4d0..0000000
--- a/src/com/android/launcher3/popup/SystemShortcutFactory.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.popup;
-
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SystemShortcutFactory implements ResourceBasedOverride {
-
-    public static final MainThreadInitializedObject<SystemShortcutFactory> INSTANCE =
-            forOverride(SystemShortcutFactory.class, R.string.system_shortcut_factory_class);
-
-    /** Note that these are in order of priority. */
-    private final SystemShortcut.Factory[] mAllFactories;
-
-    @SuppressWarnings("unused")
-    public SystemShortcutFactory() {
-        this(SystemShortcut.APP_INFO, SystemShortcut.WIDGETS, SystemShortcut.INSTALL,
-                SystemShortcut.DISMISS_PREDICTION);
-    }
-
-    protected SystemShortcutFactory(SystemShortcut.Factory... factories) {
-        mAllFactories = factories;
-    }
-
-    public @NonNull List<SystemShortcut> getEnabledShortcuts(Launcher launcher, ItemInfo info) {
-        List<SystemShortcut> systemShortcuts = new ArrayList<>();
-        for (SystemShortcut.Factory factory : mAllFactories) {
-            SystemShortcut shortcut = factory.getShortcut(launcher, info);
-            if (shortcut != null) {
-                systemShortcuts.add(shortcut);
-            }
-        }
-
-        return systemShortcuts;
-    }
-}