Initial UI changes for displaying categorized suggestions.
Ordering changes and some more UI changes in follow-up.
Bug: 318410881
Test: See screenshots
Flag: ACONFIG com.android.launcher3.enable_categorized_widget_recommendations DEVELOPMENT
Change-Id: I77e7f4dcdda32e2921ae56721cddbe261832f0d8
diff --git a/res/layout/widget_recommendations.xml b/res/layout/widget_recommendations.xml
new file mode 100644
index 0000000..89821ac
--- /dev/null
+++ b/res/layout/widget_recommendations.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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.
+ -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto">
+
+ <!--
+ Shown when there are more than one pages
+ Note: on page change, using accessibility live region lets user know that the title has changed.
+ -->
+ <TextView
+ android:id="@+id/recommendations_page_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="12dp"
+ android:layout_marginTop="16dp"
+ android:accessibilityLiveRegion="polite"
+ android:gravity="center_horizontal"
+ android:lineHeight="20sp"
+ android:textColor="?attr/widgetPickerTitleColor"
+ android:textFontWeight="500"
+ android:textSize="16sp"
+ android:visibility="gone" />
+ <!-- Shown when there are more than one pages -->
+ <com.android.launcher3.pageindicators.PageIndicatorDots
+ android:id="@+id/widget_recommendations_page_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:elevation="1dp"
+ android:visibility="gone" />
+ <!--
+ Note: importantForAccessibility = yes on this view ensures that with talkback, when user
+ swipes right on the last item in current page, they are taken to the next page. And, doing
+ the same on the last page, takes them to the next section e.g. apps list in single pane
+ picker.
+ -->
+ <com.android.launcher3.widget.picker.WidgetRecommendationsView
+ android:id="@+id/widget_recommendations_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@drawable/widgets_surface_background"
+ android:importantForAccessibility="yes"
+ launcher:pageIndicator="@+id/widget_recommendations_page_indicator" />
+</merge>
\ No newline at end of file
diff --git a/res/layout/widget_recommendations_table.xml b/res/layout/widget_recommendations_table.xml
new file mode 100644
index 0000000..e3f0562
--- /dev/null
+++ b/res/layout/widget_recommendations_table.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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.
+ -->
+<com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="@dimen/widget_recommendations_table_horizontal_padding"
+ android:paddingVertical="@dimen/widget_recommendations_table_vertical_padding" />
diff --git a/res/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml
index 069d4bc..1d37043 100644
--- a/res/layout/widgets_full_sheet_paged_view.xml
+++ b/res/layout/widgets_full_sheet_paged_view.xml
@@ -73,15 +73,18 @@
<include layout="@layout/widgets_search_bar" />
</FrameLayout>
- <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
- android:id="@+id/recommended_widget_table"
+ <!-- Shown when there are recommendations to display -->
+ <LinearLayout
+ android:id="@+id/widget_recommendations_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
- android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
android:background="@drawable/widgets_surface_background"
- android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
- android:visibility="gone" />
+ android:orientation="vertical"
+ android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
+ android:visibility="gone">
+ <include layout="@layout/widget_recommendations" />
+ </LinearLayout>
<com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
android:id="@+id/tabs"
diff --git a/res/layout/widgets_full_sheet_recyclerview.xml b/res/layout/widgets_full_sheet_recyclerview.xml
index 25bbad4..dca08ff 100644
--- a/res/layout/widgets_full_sheet_recyclerview.xml
+++ b/res/layout/widgets_full_sheet_recyclerview.xml
@@ -56,14 +56,17 @@
<include layout="@layout/widgets_search_bar" />
</FrameLayout>
- <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
- android:id="@+id/recommended_widget_table"
+ <!-- Shown when there are recommendations to display -->
+ <LinearLayout
+ android:id="@+id/widget_recommendations_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/widgets_surface_background"
- android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
- android:visibility="gone" />
+ android:orientation="vertical"
+ android:visibility="gone">
+ <include layout="@layout/widget_recommendations" />
+ </LinearLayout>
</com.android.launcher3.views.StickyHeaderLayout>
</merge>
\ No newline at end of file
diff --git a/res/layout/widgets_two_pane_sheet.xml b/res/layout/widgets_two_pane_sheet.xml
index f692e24..8e45740f 100644
--- a/res/layout/widgets_two_pane_sheet.xml
+++ b/res/layout/widgets_two_pane_sheet.xml
@@ -118,13 +118,16 @@
android:background="@drawable/widgets_surface_background"
android:importantForAccessibility="yes"
android:id="@+id/right_pane">
- <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
- android:id="@+id/recommended_widget_table"
+ <!-- Shown when there are recommendations to display -->
+ <LinearLayout
+ android:id="@+id/widget_recommendations_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingHorizontal=
- "@dimen/widget_list_horizontal_margin_two_pane"
- android:visibility="gone" />
+ android:background="@drawable/widgets_surface_background"
+ android:orientation="vertical"
+ android:visibility="gone">
+ <include layout="@layout/widget_recommendations" />
+ </LinearLayout>
</LinearLayout>
</ScrollView>
</FrameLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 6189961..2f0f130 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -182,9 +182,8 @@
<dimen name="widget_apps_tabs_vertical_padding">6dp</dimen>
<dimen name="widget_picker_landscape_tablet_left_right_margin">117dp</dimen>
<dimen name="widget_picker_two_panels_left_right_margin">0dp</dimen>
-
- <dimen name="recommended_widgets_table_vertical_padding">8dp</dimen>
-
+ <dimen name="widget_recommendations_table_vertical_padding">8dp</dimen>
+ <dimen name="widget_recommendations_table_horizontal_padding">16dp</dimen>
<!-- Bottom margin for the search and recommended widgets container without work profile -->
<dimen name="search_and_recommended_widgets_container_bottom_margin">16dp</dimen>
<!-- Bottom margin for the search and recommended widgets container with work profile -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a4e7ec4..231aec4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -71,7 +71,7 @@
<!-- Widget suggestions header title in the full widgets picker for large screen devices
in landscape mode. [CHAR_LIMIT=50] -->
<string name="suggested_widgets_header_title">Suggestions</string>
- <string name="productivity_widget_recommendation_category_label">Boost your day</string>
+ <string name="productivity_widget_recommendation_category_label">Your Daily Essentials</string>
<string name="news_widget_recommendation_category_label">News For You</string>
<string name="social_and_entertainment_widget_recommendation_category_label">Your Chill Zone</string>
<string name="fitness_widget_recommendation_category_label">Reach Your Fitness Goals</string>
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index df369c6..1b5abaa 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -367,7 +367,7 @@
mNumPages = numMarkers;
// If the last page gets removed we want to go to the previous page.
- if (mNumPages == mActivePage) {
+ if (mNumPages > 0 && mNumPages == mActivePage) {
mActivePage--;
CURRENT_POSITION.set(this, (float) mActivePage);
}
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index f1d837c..fb463f7 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -222,6 +222,7 @@
}
/** Returns the recommended widgets mapped by their category. */
+ @NonNull
public Map<WidgetRecommendationCategory, List<WidgetItem>> getCategorizedRecommendedWidgets() {
Map<ComponentKey, WidgetItem> allWidgetItems = mAllWidgets.stream()
.filter(entry -> entry instanceof WidgetsListContentEntry)
@@ -232,7 +233,8 @@
Function.identity()
));
return mRecommendedWidgets.stream()
- .filter(itemInfo -> itemInfo instanceof PendingAddWidgetInfo)
+ .filter(itemInfo -> itemInfo instanceof PendingAddWidgetInfo
+ && ((PendingAddWidgetInfo) itemInfo).recommendationCategory != null)
.collect(Collectors.groupingBy(
it -> ((PendingAddWidgetInfo) it).recommendationCategory,
Collectors.collectingAndThen(
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index c75f9d1..94c630a 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -367,6 +367,16 @@
}
}
+ /**
+ * Shows or hides the long description displayed below each widget.
+ *
+ * @param show a flag that shows the long description of the widget if {@code true}, hides it if
+ * {@code false}.
+ */
+ public void showDescription(boolean show) {
+ mWidgetDescription.setVisibility(show ? VISIBLE : GONE);
+ }
+
@Override
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
new file mode 100644
index 0000000..738d74e
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2024 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.widget.picker;
+
+import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.Px;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.pageindicators.PageIndicatorDots;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A {@link PagedView} that displays widget recommendations in categories with dots as paged
+ * indicators.
+ */
+public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots> {
+ private @Px float mAvailableHeight = Float.MAX_VALUE;
+
+ private static final int MAX_CATEGORIES = 3;
+ private TextView mRecommendationPageTitle;
+ private final List<String> mCategoryTitles = new ArrayList<>();
+
+ @Nullable
+ private OnLongClickListener mWidgetCellOnLongClickListener;
+ @Nullable
+ private OnClickListener mWidgetCellOnClickListener;
+
+ public WidgetRecommendationsView(Context context) {
+ this(context, /* attrs= */ null);
+ }
+
+ public WidgetRecommendationsView(Context context, AttributeSet attrs) {
+ this(context, attrs, /* defStyleAttr= */ 0);
+ }
+
+ public WidgetRecommendationsView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void initParentViews(View parent) {
+ super.initParentViews(parent);
+ mRecommendationPageTitle = parent.findViewById(R.id.recommendations_page_title);
+ }
+
+ /** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */
+ public void setWidgetCellLongClickListener(OnLongClickListener onLongClickListener) {
+ mWidgetCellOnLongClickListener = onLongClickListener;
+ }
+
+ /** Sets a {@link android.view.View.OnClickListener} for all widget cells in this table. */
+ public void setWidgetCellOnClickListener(OnClickListener widgetCellOnClickListener) {
+ mWidgetCellOnClickListener = widgetCellOnClickListener;
+ }
+
+ /**
+ * Displays all the provided recommendations in a single table if they fit.
+ *
+ * @param recommendedWidgets list of widgets to be displayed in recommendation section.
+ * @param availableHeight height in px that can be used to display the recommendations;
+ * recommendations that don't fit in this height won't be shown
+ * @param availableWidth width in px that the recommendations should display in
+ * @param cellPadding padding in px that should be applied to each widget in the
+ * recommendations
+ * @return {@code false} if no recommendations could fit in the available space.
+ */
+ public boolean setRecommendations(
+ List<WidgetItem> recommendedWidgets, final @Px float availableHeight,
+ final @Px int availableWidth, final @Px int cellPadding) {
+ this.mAvailableHeight = availableHeight;
+ removeAllViews();
+
+ maybeDisplayInTable(recommendedWidgets, availableWidth, cellPadding);
+ updateTitleAndIndicator();
+ return getChildCount() > 0;
+ }
+
+ /**
+ * Displays the recommendations grouped by categories as pages.
+ * <p>In case of a single category, no title is displayed for it.</p>
+ *
+ * @param recommendations a map of widget items per recommendation category
+ * @param availableHeight height in px that can be used to display the recommendations;
+ * recommendations that don't fit in this height won't be shown
+ * @param availableWidth width in px that the recommendations should display in
+ * @param cellPadding padding in px that should be applied to each widget in the
+ * recommendations
+ * @return {@code false} if no recommendations could fit in the available space.
+ */
+ public boolean setRecommendations(
+ Map<WidgetRecommendationCategory, List<WidgetItem>> recommendations,
+ final @Px float availableHeight, final @Px int availableWidth,
+ final @Px int cellPadding) {
+ this.mAvailableHeight = availableHeight;
+ Context context = getContext();
+ removeAllViews();
+
+ int displayedCategories = 0;
+
+ // 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.
+ if (maybeDisplayInTable(entry.getValue(), availableWidth, cellPadding)) {
+ mCategoryTitles.add(
+ context.getResources().getString(entry.getKey().categoryTitleRes));
+ displayedCategories++;
+ }
+
+ if (displayedCategories == MAX_CATEGORIES) {
+ break;
+ }
+ }
+
+ updateTitleAndIndicator();
+ return getChildCount() > 0;
+ }
+
+ /** Displays the page title and paging indicator if there are multiple pages. */
+ private void updateTitleAndIndicator() {
+ int titleAndIndicatorVisibility = getPageCount() > 1 ? View.VISIBLE : View.GONE;
+ mRecommendationPageTitle.setVisibility(titleAndIndicatorVisibility);
+ mPageIndicator.setVisibility(titleAndIndicatorVisibility);
+ }
+
+ @Override
+ protected void notifyPageSwitchListener(int prevPage) {
+ if (getPageCount() > 1) {
+ // Since the title is outside the paging scroll, we update the title on page switch.
+ mRecommendationPageTitle.setText(mCategoryTitles.get(getNextPage()));
+ super.notifyPageSwitchListener(prevPage);
+ requestLayout();
+ }
+ }
+
+ @Override
+ protected boolean canScroll(float absVScroll, float absHScroll) {
+ // Allow only horizontal scroll.
+ return (absHScroll > absVScroll) && super.canScroll(absVScroll, absHScroll);
+ }
+
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+ mPageIndicator.setScroll(l, mMaxScroll);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ boolean hasMultiplePages = getChildCount() > 0;
+
+ if (hasMultiplePages) {
+ int finalWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int desiredHeight = 0;
+
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ measureChild(child, widthMeasureSpec, heightMeasureSpec);
+ if (mAvailableHeight == Float.MAX_VALUE) {
+ // When we are not limited by height, use currentPage's height. This is the case
+ // when the paged layout is placed in a scrollable container. We cannot use
+ // height
+ // of tallest child in such case, as it will display a scrollbar even for
+ // smaller
+ // pages that don't have more content.
+ if (i == mCurrentPage) {
+ int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
+ desiredHeight = Math.max(parentHeight, child.getMeasuredHeight());
+ }
+ } else {
+ // Use height of tallest child when we are limited to a certain height.
+ desiredHeight = Math.max(desiredHeight, child.getMeasuredHeight());
+ }
+ }
+
+ int finalHeight = resolveSizeAndState(desiredHeight, heightMeasureSpec, 0);
+ setMeasuredDimension(finalWidth, finalHeight);
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ /**
+ * 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>
+ */
+ private boolean maybeDisplayInTable(List<WidgetItem> recommendedWidgets,
+ final @Px int availableWidth, final @Px int cellPadding) {
+ Context context = getContext();
+ DeviceProfile deviceProfile = Launcher.getLauncher(context).getDeviceProfile();
+ LayoutInflater inflater = LayoutInflater.from(context);
+
+ List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
+ recommendedWidgets,
+ context,
+ deviceProfile,
+ availableWidth,
+ cellPadding);
+
+ WidgetsRecommendationTableLayout recommendationsTable =
+ (WidgetsRecommendationTableLayout) inflater.inflate(
+ R.layout.widget_recommendations_table,
+ /* root=*/ this,
+ /* attachToRoot=*/ false);
+ recommendationsTable.setWidgetCellOnClickListener(mWidgetCellOnClickListener);
+ recommendationsTable.setWidgetCellLongClickListener(mWidgetCellOnLongClickListener);
+
+ boolean displayedAtLeastOne = recommendationsTable.setRecommendedWidgets(rows,
+ mAvailableHeight);
+ if (displayedAtLeastOne) {
+ addView(recommendationsTable);
+ }
+
+ return displayedAtLeastOne;
+ }
+
+ /** Returns location of a widget cell for displaying the "touch and hold" education tip. */
+ public View getViewForEducationTip() {
+ if (getChildCount() > 0) {
+ // first page (a table layout) -> first item (a widget cell).
+ return ((ViewGroup) getChildAt(0)).getChildAt(0);
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index e321d83..237078e 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -46,6 +46,7 @@
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.Button;
+import android.widget.LinearLayout;
import android.widget.TextView;
import android.window.BackEvent;
@@ -66,7 +67,6 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.model.UserManagerState;
-import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.views.RecyclerViewFastScroller;
@@ -77,7 +77,6 @@
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.picker.search.SearchModeListener;
import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
-import com.android.launcher3.widget.util.WidgetsTableUtils;
import com.android.launcher3.workprofile.PersonalWorkPagedView;
import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
@@ -168,7 +167,8 @@
protected TextView mNoWidgetsView;
protected StickyHeaderLayout mSearchScrollView;
- protected WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
+ protected WidgetRecommendationsView mWidgetRecommendationsView;
+ protected LinearLayout mWidgetRecommendationsContainer;
protected View mTabBar;
protected View mSearchBarContainer;
protected WidgetsSearchBar mSearchBar;
@@ -222,9 +222,14 @@
setupViews();
- mRecommendedWidgetsTable = mSearchScrollView.findViewById(R.id.recommended_widget_table);
- mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
- mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
+ mWidgetRecommendationsContainer = mSearchScrollView.findViewById(
+ R.id.widget_recommendations_container);
+ mWidgetRecommendationsView = mSearchScrollView.findViewById(
+ R.id.widget_recommendations_view);
+ mWidgetRecommendationsView.initParentViews(mWidgetRecommendationsContainer);
+ mWidgetRecommendationsView.setWidgetCellLongClickListener(this);
+ mWidgetRecommendationsView.setWidgetCellOnClickListener(this);
+
mHeaderTitle = mSearchScrollView.findViewById(R.id.title);
onRecommendedWidgetsBound();
@@ -552,7 +557,7 @@
protected void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
mIsInSearchMode = isInSearchMode;
if (isInSearchMode) {
- mRecommendedWidgetsTable.setVisibility(GONE);
+ mWidgetRecommendationsContainer.setVisibility(GONE);
if (mHasWorkProfile) {
mViewPager.setVisibility(GONE);
mTabBar.setVisibility(GONE);
@@ -581,40 +586,44 @@
if (mIsInSearchMode) {
return;
}
- List<WidgetItem> recommendedWidgets =
- mActivityContext.getPopupDataProvider().getRecommendedWidgets();
- mHasRecommendedWidgets = recommendedWidgets.size() > 0;
- if (mHasRecommendedWidgets) {
- float noWidgetsViewHeight = 0;
- if (mIsNoWidgetsViewNeeded) {
- // Make sure recommended section leaves enough space for noWidgetsView.
- Rect noWidgetsViewTextBounds = new Rect();
- mNoWidgetsView.getPaint()
- .getTextBounds(mNoWidgetsView.getText().toString(), /* start= */ 0,
- mNoWidgetsView.getText().length(), noWidgetsViewTextBounds);
- noWidgetsViewHeight = noWidgetsViewTextBounds.height();
- }
- if (!isTwoPane()) {
- doMeasure(
- makeMeasureSpec(mActivityContext.getDeviceProfile().availableWidthPx,
- MeasureSpec.EXACTLY),
- makeMeasureSpec(mActivityContext.getDeviceProfile().availableHeightPx,
- MeasureSpec.EXACTLY));
- }
- float maxTableHeight = getMaxTableHeight(noWidgetsViewHeight);
- List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
- WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering(
- recommendedWidgets,
- mActivityContext,
- mActivityContext.getDeviceProfile(),
- mMaxSpanPerRow,
- mWidgetCellHorizontalPadding);
- mRecommendedWidgetsTable.setRecommendedWidgets(
- recommendedWidgetsInTable, maxTableHeight);
+ if (enableCategorizedWidgetSuggestions()) {
+ mHasRecommendedWidgets = mWidgetRecommendationsView.setRecommendations(
+ mActivityContext.getPopupDataProvider().getCategorizedRecommendedWidgets(),
+ /* availableHeight= */ getMaxAvailableHeightForRecommendations(),
+ /* availableWidth= */ mMaxSpanPerRow,
+ /* cellPadding= */ mWidgetCellHorizontalPadding
+ );
} else {
- mRecommendedWidgetsTable.setVisibility(GONE);
+ mHasRecommendedWidgets = mWidgetRecommendationsView.setRecommendations(
+ mActivityContext.getPopupDataProvider().getRecommendedWidgets(),
+ /* availableHeight= */ getMaxAvailableHeightForRecommendations(),
+ /* availableWidth= */ mMaxSpanPerRow,
+ /* cellPadding= */ mWidgetCellHorizontalPadding
+ );
}
+ mWidgetRecommendationsContainer.setVisibility(mHasRecommendedWidgets ? VISIBLE : GONE);
+ }
+
+ @Px
+ private float getMaxAvailableHeightForRecommendations() {
+ float noWidgetsViewHeight = 0;
+ if (mIsNoWidgetsViewNeeded) {
+ // Make sure recommended section leaves enough space for noWidgetsView.
+ Rect noWidgetsViewTextBounds = new Rect();
+ mNoWidgetsView.getPaint()
+ .getTextBounds(mNoWidgetsView.getText().toString(), /* start= */ 0,
+ mNoWidgetsView.getText().length(), noWidgetsViewTextBounds);
+ noWidgetsViewHeight = noWidgetsViewTextBounds.height();
+ }
+ if (!isTwoPane()) {
+ doMeasure(
+ makeMeasureSpec(mActivityContext.getDeviceProfile().availableWidthPx,
+ MeasureSpec.EXACTLY),
+ makeMeasureSpec(mActivityContext.getDeviceProfile().availableHeightPx,
+ MeasureSpec.EXACTLY));
+ }
+ return getMaxTableHeight(noWidgetsViewHeight);
}
/** b/209579563: "Widgets" header should be focused first. */
@@ -623,7 +632,8 @@
return mHeaderTitle;
}
- protected float getMaxTableHeight(float noWidgetsViewHeight) {
+ @Px
+ protected float getMaxTableHeight(@Px float noWidgetsViewHeight) {
return (mContent.getMeasuredHeight()
- mTabsHeight - getHeaderViewHeight()
- noWidgetsViewHeight)
@@ -870,9 +880,8 @@
}
@Nullable private View getViewToShowEducationTip() {
- if (mRecommendedWidgetsTable.getVisibility() == VISIBLE
- && mRecommendedWidgetsTable.getChildCount() > 0) {
- return ((ViewGroup) mRecommendedWidgetsTable.getChildAt(0)).getChildAt(0);
+ if (mWidgetRecommendationsContainer.getVisibility() == VISIBLE) {
+ return mWidgetRecommendationsView.getViewForEducationTip();
}
AdapterHolder adapterHolder = mAdapters.get(mIsInSearchMode
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 2d17033..ce1f4e0 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -62,7 +62,7 @@
super(context, attrs);
// There are 1 row for title, 1 row for dimension and 2 rows for description.
mWidgetsRecommendationTableVerticalPadding = 2 * getResources()
- .getDimensionPixelSize(R.dimen.recommended_widgets_table_vertical_padding);
+ .getDimensionPixelSize(R.dimen.widget_recommendations_table_vertical_padding);
mWidgetCellVerticalPadding = 2 * getResources()
.getDimensionPixelSize(R.dimen.widget_cell_vertical_padding);
mWidgetCellTextViewsHeight = 4 * getResources().getDimension(R.dimen.widget_cell_font_size);
@@ -85,17 +85,20 @@
* <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. If the only
* row still doesn't fit, we scale down the preview image.
+ *
+ * <p>Returns {@code false} if none of the widgets could fit</p>
*/
- public void setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
+ public boolean setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
float recommendationTableMaxHeight) {
mRecommendationTableMaxHeight = recommendationTableMaxHeight;
RecommendationTableData data = fitRecommendedWidgetsToTableSpace(/* previewScale= */ 1f,
recommendedWidgets);
bindData(data);
+ return !data.mRecommendationTable.isEmpty();
}
private void bindData(RecommendationTableData data) {
- if (data.mRecommendationTable.size() == 0) {
+ if (data.mRecommendationTable.isEmpty()) {
setVisibility(GONE);
return;
}
@@ -116,6 +119,9 @@
WidgetCell widgetCell = addItemCell(tableRow);
widgetCell.applyFromCellItem(widgetItem, data.mPreviewScale);
widgetCell.showBadge();
+ if (enableCategorizedWidgetSuggestions()) {
+ widgetCell.showDescription(false);
+ }
}
addView(tableRow);
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 4326515..165b2fe 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -110,9 +110,15 @@
mWidgetsListTableViewHolderBinder =
new WidgetsListTableViewHolderBinder(mActivityContext, layoutInflater, this, this);
- mRecommendedWidgetsTable = mContent.findViewById(R.id.recommended_widget_table);
- mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
- mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
+
+ mWidgetRecommendationsContainer = mContent.findViewById(
+ R.id.widget_recommendations_container);
+ mWidgetRecommendationsView = mContent.findViewById(
+ R.id.widget_recommendations_view);
+ mWidgetRecommendationsView.initParentViews(mWidgetRecommendationsContainer);
+ mWidgetRecommendationsView.setWidgetCellLongClickListener(this);
+ mWidgetRecommendationsView.setWidgetCellOnClickListener(this);
+
mHeaderTitle = mContent.findViewById(R.id.title);
mRightPane = mContent.findViewById(R.id.right_pane);
mRightPane.setOutlineProvider(mViewOutlineProviderRightPane);
@@ -214,7 +220,7 @@
mSuggestedWidgetsHeader.setExpanded(true);
resetExpandedHeaders();
mRightPane.removeAllViews();
- mRightPane.addView(mRecommendedWidgetsTable);
+ mRightPane.addView(mWidgetRecommendationsContainer);
mRightPaneScrollView.setScrollY(0);
mRightPane.setAccessibilityPaneTitle(suggestionsRightPaneTitle);
mSuggestedWidgetsPackageUserKey = PackageUserKey.fromPackageItemInfo(packageItemInfo);
@@ -225,7 +231,8 @@
}
@Override
- protected float getMaxTableHeight(float noWidgetsViewHeight) {
+ @Px
+ protected float getMaxTableHeight(@Px float noWidgetsViewHeight) {
return Float.MAX_VALUE;
}