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

Commit 168d4204 authored by Shamali P's avatar Shamali P
Browse files

Maintain the recommendations that were initially displayed

- The component names of the widgets shown on first opening the picker
are saved across orientation change - and only those shown initially are
displayed.

* http://screencast/cast/NDkyMDQ0OTg3OTI0NDgwMHw4ZGU1ZjRmNy0zNQ
* https://screencast/cast/NTc1NjQ1NDI5MTExMTkzNnxiZGJlMjQ2Yi01Ng

Bug: 331776686
Flag: ACONFIG com.android.launcher3.enable_categorized_widget_suggestions TEAMFOOD
Test: Manual - see videos.
Change-Id: I6af7421c2757fde321d06406514ab2576a48fb47
parent 962b3d43
Loading
Loading
Loading
Loading
+81 −15
Original line number Diff line number Diff line
@@ -18,7 +18,9 @@ package com.android.launcher3.widget.picker;

import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering;

import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,14 +33,19 @@ import androidx.annotation.Px;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pageindicators.PageIndicatorDots;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * A {@link PagedView} that displays widget recommendations in categories with dots as paged
@@ -46,6 +53,8 @@ import java.util.function.Consumer;
 */
public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots> {
    private @Px float mAvailableHeight = Float.MAX_VALUE;
    private static final String INITIALLY_DISPLAYED_WIDGETS_STATE_KEY =
            "widgetRecommendationsView:mDisplayedWidgets";
    private static final int MAX_CATEGORIES = 3;
    private TextView mRecommendationPageTitle;
    private final List<String> mCategoryTitles = new ArrayList<>();
@@ -57,6 +66,7 @@ public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots
    private OnLongClickListener mWidgetCellOnLongClickListener;
    @Nullable
    private OnClickListener mWidgetCellOnClickListener;
    private Set<ComponentName> mDisplayedWidgets = Collections.emptySet();

    public WidgetRecommendationsView(Context context) {
        this(context, /* attrs= */ null);
@@ -76,6 +86,38 @@ public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots
        mRecommendationPageTitle = parent.findViewById(R.id.recommendations_page_title);
    }

    /**
     * Saves the necessary state in the provided bundle. To be called in case of orientation /
     * other config changes.
     */
    public void saveState(Bundle bundle) {
        // Save the widgets that were displayed, so that, on rotation / fold / unfold, we can
        // maintain the "initial" set of widgets that user first saw (if they fit).
        bundle.putParcelableArrayList(INITIALLY_DISPLAYED_WIDGETS_STATE_KEY,
                new ArrayList<>(mDisplayedWidgets));
    }

    /**
     * Restores the state that was saved by the saveState method during orientation / other config
     * changes.
     */
    public void restoreState(Bundle bundle) {
        ArrayList<ComponentName> componentList;
        if (Utilities.ATLEAST_T) {
            componentList = bundle.getParcelableArrayList(
                    INITIALLY_DISPLAYED_WIDGETS_STATE_KEY, ComponentName.class);
        } else {
            componentList = bundle.getParcelableArrayList(
                    INITIALLY_DISPLAYED_WIDGETS_STATE_KEY);
        }

        // Restore the "initial" set of widgets that were displayed, so that, on rotation / fold /
        // unfold, we can maintain the set of widgets that user first saw (if they fit).
        if (componentList != null) {
            mDisplayedWidgets = new HashSet<>(componentList);
        }
    }

    /** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */
    public void setWidgetCellLongClickListener(OnLongClickListener onLongClickListener) {
        mWidgetCellOnLongClickListener = onLongClickListener;
@@ -112,10 +154,18 @@ public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots
        this.mAvailableHeight = availableHeight;
        clear();

        int displayedWidgets = maybeDisplayInTable(recommendedWidgets, deviceProfile,
        Set<ComponentName> displayedWidgets = maybeDisplayInTable(recommendedWidgets,
                deviceProfile,
                availableWidth, cellPadding);

        if (mDisplayedWidgets.isEmpty()) {
            // Save the widgets shown for the first time user opened the picker; so that, they can
            // be maintained across orientation changes.
            mDisplayedWidgets = displayedWidgets;
        }

        updateTitleAndIndicator(/* requestedPage= */ 0);
        return displayedWidgets;
        return displayedWidgets.size();
    }

    /**
@@ -144,20 +194,21 @@ public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots
        clear();

        int displayedCategories = 0;
        int totalDisplayedWidgets = 0;
        Set<ComponentName> allDisplayedWidgets = new HashSet<>();

        // Render top MAX_CATEGORIES in separate tables. Each table becomes a page.
        for (Map.Entry<WidgetRecommendationCategory, List<WidgetItem>> entry :
                new TreeMap<>(recommendations).entrySet()) {
            // If none of the recommendations for the category could fit in the mAvailableHeight, we
            // don't want to add that category; and we look for the next one.
            int displayedCount = maybeDisplayInTable(entry.getValue(), deviceProfile,
            Set<ComponentName> displayedWidgetsForCategory = maybeDisplayInTable(entry.getValue(),
                    deviceProfile,
                    availableWidth, cellPadding);
            if (displayedCount > 0) {
            if (!displayedWidgetsForCategory.isEmpty()) {
                mCategoryTitles.add(
                        context.getResources().getString(entry.getKey().categoryTitleRes));
                displayedCategories++;
                totalDisplayedWidgets += displayedCount;
                allDisplayedWidgets.addAll(displayedWidgetsForCategory);
            }

            if (displayedCategories == MAX_CATEGORIES) {
@@ -165,11 +216,17 @@ public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots
            }
        }

        if (mDisplayedWidgets.isEmpty()) {
            // Save the widgets shown for the first time user opened the picker; so that, they can
            // be maintained across orientation changes.
            mDisplayedWidgets = allDisplayedWidgets;
        }

        updateTitleAndIndicator(requestedPage);
        // For purpose of recommendations section, we don't want paging dots to be halved in two
        // pane display, so, we always provide isTwoPanels = "false".
        mPageIndicator.setPauseScroll(/*pause=*/false, /*isTwoPanels=*/false);
        return totalDisplayedWidgets;
        return allDisplayedWidgets.size();
    }

    private void clear() {
@@ -241,20 +298,25 @@ public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots
    }

    /**
     * Groups the provided recommendations into rows and displays them in a table if at least one
     * fits.
     * <p>Returns false if none of the recommendations could fit.</p>
     * Groups the provided recommendations into rows and displays ones that fit in a table.
     * <p>Returns the set of widgets that could fit.</p>
     */
    private int maybeDisplayInTable(List<WidgetItem> recommendedWidgets,
    private Set<ComponentName> maybeDisplayInTable(List<WidgetItem> recommendedWidgets,
            DeviceProfile deviceProfile,
            final @Px int availableWidth, final @Px int cellPadding) {
        List<WidgetItem> filteredRecommendedWidgets = recommendedWidgets;
        // Show only those widgets that were displayed when user first opened the picker.
        if (!mDisplayedWidgets.isEmpty()) {
            filteredRecommendedWidgets = recommendedWidgets.stream().filter(
                    w -> mDisplayedWidgets.contains(w.componentName)).toList();
        }
        Context context = getContext();
        LayoutInflater inflater = LayoutInflater.from(context);

        // Since we are limited by space, we don't sort recommendations - to show most relevant
        // (if possible).
        List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
                recommendedWidgets,
                filteredRecommendedWidgets,
                context,
                deviceProfile,
                availableWidth,
@@ -268,13 +330,17 @@ public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots
        recommendationsTable.setWidgetCellOnClickListener(mWidgetCellOnClickListener);
        recommendationsTable.setWidgetCellLongClickListener(mWidgetCellOnLongClickListener);

        int displayedCount = recommendationsTable.setRecommendedWidgets(rows,
        List<ArrayList<WidgetItem>> displayedItems = recommendationsTable.setRecommendedWidgets(
                rows,
                deviceProfile, mAvailableHeight);
        if (displayedCount > 0) {

        if (!displayedItems.isEmpty()) {
            addView(recommendationsTable);
        }

        return displayedCount;
        return displayedItems.stream().flatMap(
                        items -> items.stream().map(w -> w.componentName))
                .collect(Collectors.toSet());
    }

    /** Returns location of a widget cell for displaying the "touch and hold" education tip. */
+5 −11
Original line number Diff line number Diff line
@@ -633,15 +633,15 @@ public class WidgetsFullSheet extends BaseWidgetSheet
    }

    @Px
    private float getMaxAvailableHeightForRecommendations() {
    protected float getMaxAvailableHeightForRecommendations() {
        // There isn't enough space to show recommendations in landscape orientation on phones with
        // a full sheet design. Tablets use a two pane picker.
        if (!isTwoPane() && mDeviceProfile.isLandscape) {
        if (mDeviceProfile.isLandscape) {
            return 0f;
        }

        return (mDeviceProfile.heightPx - mDeviceProfile.bottomSheetTopPadding)
                * getRecommendationSectionHeightRatio();
                * RECOMMENDATION_TABLE_HEIGHT_RATIO;
    }

    /** b/209579563: "Widgets" header should be focused first. */
@@ -650,14 +650,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet
        return mHeaderTitle;
    }

    /**
     * Ratio of recommendations section with respect to bottom sheet's height on scale of 0 to 1.
     */
    @Px
    protected float getRecommendationSectionHeightRatio() {
        return RECOMMENDATION_TABLE_HEIGHT_RATIO;
    }

    private void open(boolean animate) {
        if (animate) {
            if (getPopupContainer().getInsets().bottom > 0) {
@@ -733,6 +725,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
        // picker and calls save/restore hierarchy state. We save the state of recommendations
        // across those updates.
        bundle.putInt(RECOMMENDATIONS_SAVED_STATE_KEY, mRecommendationsCurrentPage);
        mWidgetRecommendationsView.saveState(bundle);
        SparseArray<Parcelable> superState = new SparseArray<>();
        super.saveHierarchyState(superState);
        bundle.putSparseParcelableArray(SUPER_SAVED_STATE_KEY, superState);
@@ -744,6 +737,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
        Bundle state = (Bundle) sparseArray.get(0);
        mRecommendationsCurrentPage = state.getInt(
                RECOMMENDATIONS_SAVED_STATE_KEY, /*defaultValue=*/0);
        mWidgetRecommendationsView.restoreState(state);
        super.restoreHierarchyState(state.getSparseParcelableArray(SUPER_SAVED_STATE_KEY));
    }

+4 −3
Original line number Diff line number Diff line
@@ -83,14 +83,15 @@ public final class WidgetsRecommendationTableLayout extends TableLayout {
     * <p>If the content can't fit {@code recommendationTableMaxHeight}, this view will remove a
     * last row from the {@code recommendedWidgets} until it fits or only one row left.
     *
     * <p>Returns {@code false} if none of the widgets could fit</p>
     * <p>Returns the list of widgets that could fit</p>
     */
    public int setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
    public List<ArrayList<WidgetItem>> setRecommendedWidgets(
            List<ArrayList<WidgetItem>> recommendedWidgets,
            DeviceProfile deviceProfile, float recommendationTableMaxHeight) {
        List<ArrayList<WidgetItem>> rows = selectRowsThatFitInAvailableHeight(recommendedWidgets,
                recommendationTableMaxHeight, deviceProfile);
        bindData(rows);
        return rows.stream().mapToInt(ArrayList::size).sum();
        return rows;
    }

    private void bindData(List<ArrayList<WidgetItem>> recommendationTable) {
+9 −2
Original line number Diff line number Diff line
@@ -276,8 +276,15 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {

    @Override
    @Px
    protected float getRecommendationSectionHeightRatio() {
        return RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE;
    protected float getMaxAvailableHeightForRecommendations() {
        if (mRecommendedWidgetsCount > 0) {
            // If widgets were already selected for display, we show them all on orientation change
            // in a two pane picker
            return Float.MAX_VALUE;
        }

        return (mDeviceProfile.heightPx - mDeviceProfile.bottomSheetTopPadding)
                * RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE;
    }

    @Override