Merge "Update widgetsModel to return pickable vs all widgets separately." into main
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 1cf7dda..f992913 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -322,13 +322,14 @@
stringCache.loadStrings(this);
bindStringCache(stringCache);
- bindWidgets(mModel.getWidgetsByPackageItem(), mModel.getDefaultWidgetsFilter());
+ bindWidgets(mModel.getWidgetsByPackageItemForPicker(),
+ mModel.getDefaultWidgetsFilter());
// Open sheet once widgets are available, so that it doesn't interrupt the open
// animation.
openWidgetsSheet();
if (mUiSurface != null) {
mWidgetPredictionsRequester = new WidgetPredictionsRequester(app.getContext(),
- mUiSurface, mModel.getWidgetsByComponentKey());
+ mUiSurface, mModel.getWidgetsByComponentKeyForPicker());
mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
}
});
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index 40e1c10..9626a61 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -79,7 +79,7 @@
// Widgets (excluding shortcuts & already added widgets) that belong to apps eligible for
// being in predictions.
Map<ComponentKey, WidgetItem> allEligibleWidgets =
- dataModel.widgetsModel.getWidgetsByComponentKey()
+ dataModel.widgetsModel.getWidgetsByComponentKeyForPicker()
.entrySet()
.stream()
.filter(entry -> entry.getValue().widgetInfo != null
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index fa2eb1e..d52d054 100644
--- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -91,6 +91,7 @@
private AppWidgetProviderInfo mApp4Provider1;
private AppWidgetProviderInfo mApp4Provider2;
private AppWidgetProviderInfo mApp5Provider1;
+ private AppWidgetProviderInfo mApp6PinOnlyProvider1;
private List<AppWidgetProviderInfo> allWidgets;
private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback();
@@ -117,8 +118,14 @@
ComponentName.createRelative("app4", ".provider2"));
mApp5Provider1 = createAppWidgetProviderInfo(
ComponentName.createRelative("app5", "provider1"));
+ mApp6PinOnlyProvider1 = createAppWidgetProviderInfo(
+ ComponentName.createRelative("app6", "provider1"),
+ /*hideFromPicker=*/ true
+ );
+
+
allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1,
- mApp4Provider1, mApp4Provider2, mApp5Provider1);
+ mApp4Provider1, mApp4Provider2, mApp5Provider1, mApp6PinOnlyProvider1);
mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class);
doAnswer(i -> {
@@ -270,6 +277,32 @@
});
}
+ @Test
+ public void widgetsRecommendations_excludesWidgetsHiddenForPicker() {
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
+
+ // Not installed widget - hence eligible
+ AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1",
+ mUserHandle);
+ // Provider marked as hidden from picker - hence not eligible
+ AppTarget widget6 = new AppTarget(new AppTargetId("app6"), "app6", "provider1",
+ mUserHandle);
+
+ mCallback.mRecommendedWidgets = null;
+ mModelHelper.getModel().enqueueModelUpdateTask(
+ newWidgetsPredicationTask(List.of(widget1, widget6)));
+ runOnExecutorSync(MAIN_EXECUTOR, () -> { });
+
+ // Only widget 1 (and no widget 6 as its meant to be hidden from picker).
+ List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
+ .stream()
+ .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
+ .collect(Collectors.toList());
+ assertThat(recommendedWidgets).hasSize(1);
+ assertThat(recommendedWidgets.get(0).componentName.getPackageName()).isEqualTo("app1");
+ });
+ }
+
private void assertWidgetInfo(
LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected) {
assertThat(actual.provider).isEqualTo(expected.provider);
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 003bef3..3ee8b87 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -167,7 +167,7 @@
return;
}
Map<PackageItemInfo, List<WidgetItem>>
- widgetsByPackageItem = mBgDataModel.widgetsModel.getWidgetsByPackageItem();
+ widgetsByPackageItem = mBgDataModel.widgetsModel.getWidgetsByPackageItemForPicker();
List<WidgetsListBaseEntry> widgets = new WidgetsListBaseEntriesBuilder(mApp.getContext())
.build(widgetsByPackageItem);
Predicate<WidgetItem> filter = mBgDataModel.widgetsModel.getDefaultWidgetsFilter();
diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt
index 40ea17d..6e3e35e 100644
--- a/src/com/android/launcher3/model/ModelTaskController.kt
+++ b/src/com/android/launcher3/model/ModelTaskController.kt
@@ -77,7 +77,7 @@
}
fun bindUpdatedWidgets(dataModel: BgDataModel) {
- val widgetsByPackageItem = dataModel.widgetsModel.widgetsByPackageItem
+ val widgetsByPackageItem = dataModel.widgetsModel.widgetsByPackageItemForPicker
val allWidgets = WidgetsListBaseEntriesBuilder(app.context).build(widgetsByPackageItem)
val defaultWidgetsFilter = dataModel.widgetsModel.defaultWidgetsFilter
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index a176465..ab960d8 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -70,6 +70,7 @@
private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsByPackageItem = new HashMap<>();
@Nullable private Predicate<WidgetItem> mDefaultWidgetsFilter = null;
@Nullable private Predicate<WidgetItem> mPredictedWidgetsFilter = null;
+ @Nullable private WidgetValidityCheckForPicker mWidgetValidityCheckForPicker = null;
/**
* Returns all widgets keyed by their component key.
@@ -87,13 +88,44 @@
}
/**
- * Returns widgets grouped by the package item that they should belong to.
+ * Returns widgets (eligible for display in picker) keyed by their component key.
*/
- public synchronized Map<PackageItemInfo, List<WidgetItem>> getWidgetsByPackageItem() {
- if (!WIDGETS_ENABLED) {
+ public synchronized Map<ComponentKey, WidgetItem> getWidgetsByComponentKeyForPicker() {
+ if (!WIDGETS_ENABLED || mWidgetValidityCheckForPicker == null) {
return Collections.emptyMap();
}
- return new HashMap<>(mWidgetsByPackageItem);
+
+ return mWidgetsByPackageItem.values().stream()
+ .flatMap(Collection::stream).distinct()
+ .filter(widgetItem -> mWidgetValidityCheckForPicker.test(widgetItem))
+ .collect(Collectors.toMap(
+ widget -> new ComponentKey(widget.componentName, widget.user),
+ Function.identity()
+ ));
+ }
+
+ /**
+ * Returns widgets (displayable in the widget picker) grouped by the package item that
+ * they should belong to.
+ */
+ public synchronized Map<PackageItemInfo, List<WidgetItem>> getWidgetsByPackageItemForPicker() {
+ if (!WIDGETS_ENABLED || mWidgetValidityCheckForPicker == null) {
+ return Collections.emptyMap();
+ }
+
+ return mWidgetsByPackageItem.entrySet().stream()
+ .collect(
+ Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> entry.getValue().stream()
+ .filter(widgetItem ->
+ mWidgetValidityCheckForPicker.test(widgetItem))
+ .collect(Collectors.toList())
+ )
+ )
+ .entrySet().stream()
+ .filter(entry -> !entry.getValue().isEmpty())
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
/**
@@ -181,6 +213,9 @@
Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
}
+ // Refresh the validity checker with latest app state.
+ mWidgetValidityCheckForPicker = new WidgetValidityCheckForPicker(app);
+
// Temporary cache for {@link PackageItemInfos} to avoid having to go through
// {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
PackageItemInfoCache packageItemInfoCache = new PackageItemInfoCache();
@@ -195,7 +230,6 @@
// add and update.
mWidgetsByPackageItem.putAll(rawWidgetsShortcuts.stream()
- .filter(new WidgetValidityCheck(app))
.filter(new WidgetFlagCheck())
.flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
.map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem)))
@@ -270,12 +304,15 @@
return packageUserKeys;
}
- private static class WidgetValidityCheck implements Predicate<WidgetItem> {
+ /**
+ * Checks if widgets are eligible for displaying in widget picker / tray.
+ */
+ private static class WidgetValidityCheckForPicker implements Predicate<WidgetItem> {
private final InvariantDeviceProfile mIdp;
private final AppFilter mAppFilter;
- WidgetValidityCheck(LauncherAppState app) {
+ WidgetValidityCheckForPicker(LauncherAppState app) {
mIdp = app.getInvariantDeviceProfile();
mAppFilter = new AppFilter(app.getContext());
}
@@ -310,6 +347,10 @@
}
}
+ /**
+ * Checks if certain widgets that are available behind flag can be used across all surfaces in
+ * launcher.
+ */
private static class WidgetFlagCheck implements Predicate<WidgetItem> {
private static final String BUBBLES_SHORTCUT_WIDGET =
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
index ae4ff04..d704195 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
@@ -119,6 +119,11 @@
// A widget in different package (none of that app's widgets are in widget
// sections xml)
createAppWidgetProviderInfo(AppBTestWidgetComponent),
+ // A widget in different app that is meant to be hidden from picker
+ createAppWidgetProviderInfo(
+ AppCPinOnlyTestWidgetComponent,
+ /*hideFromPicker=*/ true,
+ ),
)
)
@@ -129,12 +134,13 @@
}
@Test
- fun widgetsByPackage_treatsWidgetSectionsAsSeparatePackageItems() {
+ fun widgetsByPackageForPicker_treatsWidgetSectionsAsSeparatePackageItems() {
loadWidgets()
- val packages: Map<PackageItemInfo, List<WidgetItem>> = underTest.widgetsByPackageItem
+ val packages: Map<PackageItemInfo, List<WidgetItem>> =
+ underTest.widgetsByPackageItemForPicker
- // expect 3 package items
+ // expect 3 package items (no app C as its widget is hidden from picker)
// one for the custom section with widget from appA
// one for package section for second widget from appA (that wasn't listed in xml)
// and one for package section for appB
@@ -167,6 +173,13 @@
assertThat(appBPackageSection).hasSize(1)
val widgetsInAppBSection = appBPackageSection.entries.first().value
assertThat(widgetsInAppBSection).hasSize(1)
+
+ // No App C's package section - as the only widget hosted by it is hidden in picker
+ val appCPackageSection =
+ packageSections.filter {
+ it.key.packageName == AppCPinOnlyTestWidgetComponent.packageName
+ }
+ assertThat(appCPackageSection).isEmpty()
}
@Test
@@ -175,7 +188,29 @@
val widgetsByComponentKey: Map<ComponentKey, WidgetItem> = underTest.widgetsByComponentKey
+ // Has all widgets including ones not visible in picker
+ assertThat(widgetsByComponentKey).hasSize(4)
+ widgetsByComponentKey.forEach { entry ->
+ assertThat(entry.key).isEqualTo(entry.value as ComponentKey)
+ }
+ }
+
+ @Test
+ fun widgetComponentMapForPicker_excludesWidgetsHiddenInPicker() {
+ loadWidgets()
+
+ val widgetsByComponentKey: Map<ComponentKey, WidgetItem> =
+ underTest.widgetsByComponentKeyForPicker
+
+ // Has all widgets excluding the appC's widget.
assertThat(widgetsByComponentKey).hasSize(3)
+ assertThat(
+ widgetsByComponentKey.filter {
+ it.key.componentName == AppCPinOnlyTestWidgetComponent
+ }
+ )
+ .isEmpty()
+ // widgets mapped correctly
widgetsByComponentKey.forEach { entry ->
assertThat(entry.key).isEqualTo(entry.value as ComponentKey)
}
@@ -189,7 +224,7 @@
}
@Test
- fun getWidgetsByPackageItem_returnsACopyOfMap() {
+ fun getWidgetsByPackageItemForPicker_returnsACopyOfMap() {
loadWidgets()
val latch = CountDownLatch(1)
@@ -198,8 +233,8 @@
// each "widgetsByPackageItem" read returns a different copy of the map held internally.
// Modifying one shouldn't impact another.
- for ((_, _) in underTest.widgetsByPackageItem.entries) {
- underTest.widgetsByPackageItem.clear()
+ for ((_, _) in underTest.widgetsByPackageItemForPicker.entries) {
+ underTest.widgetsByPackageItemForPicker.clear()
if (update) { // trigger update
update = false
// Similarly, model could update its code independently while a client is
@@ -256,6 +291,9 @@
private val AppBTestWidgetComponent: ComponentName =
ComponentName.createRelative("com.test.package", "TestProvider")
+ private val AppCPinOnlyTestWidgetComponent: ComponentName =
+ ComponentName.createRelative("com.testC.package", "PinOnlyTestProvider")
+
private const val LOAD_WIDGETS_TIMEOUT_SECONDS = 2L
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
index a87a208..9fbd7ff 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.util;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER;
+
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -83,14 +85,30 @@
/**
* Creates a {@link AppWidgetProviderInfo} for the provided component name
+ *
+ * @param cn component name of the appwidget provider
+ * @param hideFromPicker indicates if the widget should appear in widget picker
*/
- public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
+ public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn,
+ boolean hideFromPicker) {
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.applicationInfo = new ApplicationInfo();
activityInfo.applicationInfo.uid = Process.myUid();
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
+ if (hideFromPicker) {
+ info.widgetFeatures = WIDGET_FEATURE_HIDE_FROM_PICKER;
+ }
info.providerInfo = activityInfo;
info.provider = cn;
return info;
}
+
+ /**
+ * Creates a {@link AppWidgetProviderInfo} for the provided component name
+ *
+ * @param cn component name of the appwidget provider
+ */
+ public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
+ return createAppWidgetProviderInfo(cn, /*hideFromPicker=*/ false);
+ }
}