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 298b0ffb93..b5441dfd14 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 @@ public class TaskOverlayFactory implements ResourceBasedOverride { TaskShortcutFactory.SPLIT_SCREEN, TaskShortcutFactory.PIN, TaskShortcutFactory.INSTALL, - TaskShortcutFactory.FREE_FORM + TaskShortcutFactory.FREE_FORM, + TaskShortcutFactory.WELLBEING }; - public static final MainThreadInitializedObject INSTANCE = - forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class); - - public List getEnabledShortcuts(TaskView taskView) { + public static List getEnabledShortcuts(TaskView taskView) { final ArrayList shortcuts = new ArrayList<>(); final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext()); for (TaskShortcutFactory menuOption : MENU_OPTIONS) { @@ -63,6 +61,9 @@ public class TaskOverlayFactory implements ResourceBasedOverride { return shortcuts; } + public static final MainThreadInitializedObject 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 a3a1d6d483..9ba2e5a684 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.BaseDraggingActivity; 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 @@ public interface TaskShortcutFactory { 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 b810c4a4e4..80022b4fe1 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 @@ public class TaskMenuView extends AbstractFloatingView { 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 3af0f705d3..0dfc39bbed 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 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { 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 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { 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 98aaceb0bf..5d9a0092c5 100644 --- a/quickstep/res/values/config.xml +++ b/quickstep/res/values/config.xml @@ -33,4 +33,6 @@ 200 20 + + diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index f55ef94012..fc9cfcd7fa 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -35,6 +35,8 @@ import android.os.CancellationSignal; 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.SysUINavigationMode.NavigationModeChangeListener; 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 @@ public abstract class BaseQuickstepLauncher extends Launcher 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 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 0000000000..852a08ec80 --- /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 mActionIdMap = new ArrayMap<>(); + private final Map 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 038718473b..10671c5f3a 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -69,7 +69,6 @@ - diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index bb08ba86a7..3dce9fc955 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -31,6 +31,10 @@ import static com.android.launcher3.Utilities.postAsyncCallback; 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.notification.NotificationListener; 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.HashSet; 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 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, getStateManager().goToState(LauncherState.NORMAL); } + public Stream 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 8e5d852a31..203492677d 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -77,6 +77,7 @@ import java.util.ArrayList; 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 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, 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 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, } } - 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 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, 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 8b8a4d0190..0000000000 --- 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 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 getEnabledShortcuts(Launcher launcher, ItemInfo info) { - List systemShortcuts = new ArrayList<>(); - for (SystemShortcut.Factory factory : mAllFactories) { - SystemShortcut shortcut = factory.getShortcut(launcher, info); - if (shortcut != null) { - systemShortcuts.add(shortcut); - } - } - - return systemShortcuts; - } -}