Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit efebbb21 authored by Shamali Patwa's avatar Shamali Patwa Committed by Android (Google) Code Review
Browse files

Merge "Update prediction task to provide categorized suggestions." into main

parents b7efb5cb 57c1eedb
Loading
Loading
Loading
Loading
+18 −3
Original line number Diff line number Diff line
@@ -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))
        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
Original line number Diff line number Diff line
@@ -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);
+29 −0
Original line number Diff line number Diff line
@@ -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
+11 −0
Original line number Diff line number Diff line
@@ -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;
+22 −20
Original line number Diff line number Diff line
@@ -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);
    }