Merge "Update prediction task to provide categorized suggestions." into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
efebbb21fe
@@ -15,9 +15,11 @@
|
||||
*/
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
|
||||
|
||||
import android.app.prediction.AppTarget;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -29,6 +31,7 @@ import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.widget.PendingAddWidgetInfo;
|
||||
import com.android.launcher3.widget.picker.WidgetRecommendationCategoryProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -93,9 +96,21 @@ public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask {
|
||||
servicePredictedItems.addAll(localFilteredWidgets);
|
||||
}
|
||||
|
||||
List<ItemInfo> items = servicePredictedItems.stream()
|
||||
.map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION))
|
||||
.collect(Collectors.toList());
|
||||
List<ItemInfo> items;
|
||||
if (enableCategorizedWidgetSuggestions()) {
|
||||
Context context = appState.getContext();
|
||||
WidgetRecommendationCategoryProvider categoryProvider =
|
||||
WidgetRecommendationCategoryProvider.newInstance(context);
|
||||
items = servicePredictedItems.stream()
|
||||
.map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION,
|
||||
categoryProvider.getWidgetRecommendationCategory(context, it)))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
items = servicePredictedItems.stream()
|
||||
.map(it -> new PendingAddWidgetInfo(it.widgetInfo,
|
||||
CONTAINER_WIDGETS_PREDICTION)).collect(
|
||||
Collectors.toList());
|
||||
}
|
||||
FixedContainerItems fixedContainerItems =
|
||||
new FixedContainerItems(mPredictorState.containerId, items);
|
||||
|
||||
|
||||
+25
-2
@@ -26,6 +26,8 @@ import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
@@ -36,11 +38,14 @@ import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.os.UserHandle;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.test.core.content.pm.ApplicationInfoBuilder;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
|
||||
import com.android.launcher3.util.LauncherLayoutBuilder;
|
||||
@@ -50,6 +55,7 @@ import com.android.launcher3.widget.PendingAddWidgetInfo;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -61,6 +67,9 @@ import java.util.stream.Collectors;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class WidgetsPredicationUpdateTaskTest {
|
||||
|
||||
@Rule
|
||||
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
|
||||
private AppWidgetProviderInfo mApp1Provider1;
|
||||
private AppWidgetProviderInfo mApp1Provider2;
|
||||
private AppWidgetProviderInfo mApp2Provider1;
|
||||
@@ -75,6 +84,7 @@ public final class WidgetsPredicationUpdateTaskTest {
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS);
|
||||
mModelHelper = new LauncherModelHelper();
|
||||
|
||||
mUserHandle = myUserHandle();
|
||||
@@ -93,6 +103,12 @@ public final class WidgetsPredicationUpdateTaskTest {
|
||||
allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1,
|
||||
mApp4Provider1, mApp4Provider2, mApp5Provider1);
|
||||
|
||||
doAnswer(i -> {
|
||||
String pkg = i.getArgument(0);
|
||||
return ApplicationInfoBuilder.newBuilder().setPackageName(pkg).setName(
|
||||
"App " + pkg).build();
|
||||
}).when(mModelHelper.sandboxContext.getPackageManager())
|
||||
.getApplicationInfo(anyString(), anyInt());
|
||||
AppWidgetManager manager = mModelHelper.sandboxContext.spyService(AppWidgetManager.class);
|
||||
doReturn(allWidgets).when(manager).getInstalledProviders();
|
||||
doReturn(allWidgets).when(manager).getInstalledProvidersForProfile(eq(myUserHandle()));
|
||||
@@ -140,12 +156,16 @@ public final class WidgetsPredicationUpdateTaskTest {
|
||||
// 1. app5/provider1 & app4/provider1 have already been added to workspace. They are
|
||||
// excluded from the result.
|
||||
// 2. app3 doesn't have a widget.
|
||||
// 3. only 1 widget is picked from app1 because we only want to promote one widget per app.
|
||||
// 3. only 1 widget is picked from app1 because we only want to promote one widget
|
||||
// per app.
|
||||
List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
|
||||
.stream()
|
||||
.map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(recommendedWidgets).hasSize(2);
|
||||
recommendedWidgets.forEach(pendingAddWidgetInfo ->
|
||||
assertThat(pendingAddWidgetInfo.recommendationCategory).isNotNull()
|
||||
);
|
||||
assertWidgetInfo(recommendedWidgets.get(0).info, mApp2Provider1);
|
||||
assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1);
|
||||
});
|
||||
@@ -179,6 +199,9 @@ public final class WidgetsPredicationUpdateTaskTest {
|
||||
.map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(recommendedWidgets).hasSize(2);
|
||||
recommendedWidgets.forEach(pendingAddWidgetInfo ->
|
||||
assertThat(pendingAddWidgetInfo.recommendationCategory).isNotNull()
|
||||
);
|
||||
// Another widget from the same package
|
||||
assertWidgetInfo(recommendedWidgets.get(0).info, mApp4Provider2);
|
||||
assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1);
|
||||
@@ -192,7 +215,7 @@ public final class WidgetsPredicationUpdateTaskTest {
|
||||
}
|
||||
|
||||
private WidgetsPredictionUpdateTask newWidgetsPredicationTask(List<AppTarget> appTargets) {
|
||||
return new WidgetsPredictionUpdateTask(
|
||||
return new WidgetsPredictionUpdateTask(
|
||||
new PredictorState(CONTAINER_WIDGETS_PREDICTION, "test_widgets_prediction"),
|
||||
appTargets);
|
||||
}
|
||||
|
||||
@@ -31,8 +31,10 @@ import com.android.launcher3.notification.NotificationListener;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.util.ShortcutUtil;
|
||||
import com.android.launcher3.widget.PendingAddWidgetInfo;
|
||||
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
|
||||
import com.android.launcher3.widget.model.WidgetsListContentEntry;
|
||||
import com.android.launcher3.widget.picker.WidgetRecommendationCategory;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
@@ -41,6 +43,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -218,6 +221,32 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/** Returns the recommended widgets mapped by their category. */
|
||||
public Map<WidgetRecommendationCategory, List<WidgetItem>> getCategorizedRecommendedWidgets() {
|
||||
Map<ComponentKey, WidgetItem> allWidgetItems = mAllWidgets.stream()
|
||||
.filter(entry -> entry instanceof WidgetsListContentEntry)
|
||||
.flatMap(entry -> entry.mWidgets.stream())
|
||||
.distinct()
|
||||
.collect(Collectors.toMap(
|
||||
widget -> new ComponentKey(widget.componentName, widget.user),
|
||||
Function.identity()
|
||||
));
|
||||
return mRecommendedWidgets.stream()
|
||||
.filter(itemInfo -> itemInfo instanceof PendingAddWidgetInfo)
|
||||
.collect(Collectors.groupingBy(
|
||||
it -> ((PendingAddWidgetInfo) it).recommendationCategory,
|
||||
Collectors.collectingAndThen(
|
||||
Collectors.toList(),
|
||||
list -> list.stream()
|
||||
.map(it -> allWidgetItems.get(
|
||||
new ComponentKey(it.getTargetComponent(),
|
||||
it.user)))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList())
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
|
||||
return mAllWidgets.stream()
|
||||
.filter(row -> row instanceof WidgetsListContentEntry
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.android.launcher3.PendingAddItemInfo;
|
||||
import com.android.launcher3.logger.LauncherAtom;
|
||||
import com.android.launcher3.model.data.FolderInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.widget.picker.WidgetRecommendationCategory;
|
||||
import com.android.launcher3.widget.util.WidgetSizes;
|
||||
|
||||
/**
|
||||
@@ -42,6 +43,16 @@ public class PendingAddWidgetInfo extends PendingAddItemInfo {
|
||||
public Bundle bindOptions = null;
|
||||
public int sourceContainer;
|
||||
|
||||
public WidgetRecommendationCategory recommendationCategory = null;
|
||||
|
||||
public PendingAddWidgetInfo(
|
||||
LauncherAppWidgetProviderInfo i,
|
||||
int container,
|
||||
WidgetRecommendationCategory recommendationCategory) {
|
||||
this(i, container);
|
||||
this.recommendationCategory = recommendationCategory;
|
||||
}
|
||||
|
||||
public PendingAddWidgetInfo(LauncherAppWidgetProviderInfo i, int container) {
|
||||
if (i.isCustomWidget()) {
|
||||
itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
|
||||
|
||||
@@ -80,6 +80,28 @@ public class WidgetRecommendationCategoryProvider implements ResourceBasedOverri
|
||||
/** Maps application category to an appropriate displayable category. */
|
||||
private static WidgetRecommendationCategory getCategoryFromApplicationCategory(
|
||||
Context context, int applicationCategory, String componentName) {
|
||||
// Weather categories don't map to a specific application category, so, we maintain an
|
||||
// allowlist.
|
||||
String[] weatherRecommendationAllowlist =
|
||||
context.getResources().getStringArray(R.array.weather_recommendations);
|
||||
for (String allowedWeatherComponentName : weatherRecommendationAllowlist) {
|
||||
if (componentName.equalsIgnoreCase(allowedWeatherComponentName)) {
|
||||
return new WidgetRecommendationCategory(
|
||||
R.string.weather_widget_recommendation_category_label, /*order=*/3);
|
||||
}
|
||||
}
|
||||
|
||||
// Fitness categories don't map to a specific application category, so, we maintain an
|
||||
// allowlist.
|
||||
String[] fitnessRecommendationAllowlist =
|
||||
context.getResources().getStringArray(R.array.fitness_recommendations);
|
||||
for (String allowedFitnessComponentName : fitnessRecommendationAllowlist) {
|
||||
if (componentName.equalsIgnoreCase(allowedFitnessComponentName)) {
|
||||
return new WidgetRecommendationCategory(
|
||||
R.string.fitness_widget_recommendation_category_label, /*order=*/2);
|
||||
}
|
||||
}
|
||||
|
||||
if (applicationCategory == ApplicationInfo.CATEGORY_PRODUCTIVITY) {
|
||||
return new WidgetRecommendationCategory(
|
||||
R.string.productivity_widget_recommendation_category_label, /*order=*/0);
|
||||
@@ -99,26 +121,6 @@ public class WidgetRecommendationCategoryProvider implements ResourceBasedOverri
|
||||
/*order=*/4);
|
||||
}
|
||||
|
||||
// Fitness & weather categories don't map to a specific application category, so, we
|
||||
// maintain an allowlist.
|
||||
String[] weatherRecommendationAllowlist =
|
||||
context.getResources().getStringArray(R.array.weather_recommendations);
|
||||
for (String allowedWeatherComponentName : weatherRecommendationAllowlist) {
|
||||
if (componentName.equalsIgnoreCase(allowedWeatherComponentName)) {
|
||||
return new WidgetRecommendationCategory(
|
||||
R.string.weather_widget_recommendation_category_label, /*order=*/2);
|
||||
}
|
||||
}
|
||||
|
||||
String[] fitnessRecommendationAllowlist =
|
||||
context.getResources().getStringArray(R.array.fitness_recommendations);
|
||||
for (String allowedFitnessComponentName : fitnessRecommendationAllowlist) {
|
||||
if (componentName.equalsIgnoreCase(allowedFitnessComponentName)) {
|
||||
return new WidgetRecommendationCategory(
|
||||
R.string.fitness_widget_recommendation_category_label, /*order=*/3);
|
||||
}
|
||||
}
|
||||
|
||||
return new WidgetRecommendationCategory(
|
||||
R.string.others_widget_recommendation_category_label, /*order=*/5);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user