diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java index 2ac1e9481b..7066fb5450 100644 --- a/src/com/android/launcher3/widget/BaseWidgetSheet.java +++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java @@ -33,6 +33,7 @@ import android.widget.Toast; import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; +import androidx.annotation.Px; import androidx.core.view.ViewCompat; import com.android.launcher3.DeviceProfile; @@ -61,7 +62,7 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView implements OnClickListener, OnLongClickListener, DragSource, PopupDataProvider.PopupDataChangeListener, Insettable { /** The default number of cells that can fit horizontally in a widget sheet. */ - protected static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4; + public static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4; protected static final String KEY_WIDGETS_EDUCATION_TIP_SEEN = "launcher.widgets_education_tip_seen"; @@ -70,15 +71,18 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView /* Touch handling related member variables. */ private Toast mWidgetInstructionToast; - private int mContentHorizontalMarginInPx; + @Px protected int mContentHorizontalMargin; + @Px protected int mWidgetCellHorizontalPadding; protected int mNavBarScrimHeight; private final Paint mNavBarScrimPaint; public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - mContentHorizontalMarginInPx = getResources().getDimensionPixelSize( + mContentHorizontalMargin = getResources().getDimensionPixelSize( R.dimen.widget_list_horizontal_margin); + mWidgetCellHorizontalPadding = getResources().getDimensionPixelSize( + R.dimen.widget_cell_horizontal_padding); mNavBarScrimPaint = new Paint(); mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor)); } @@ -138,11 +142,11 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView @Override public void setInsets(Rect insets) { mInsets.set(insets); - int contentHorizontalMarginInPx = getResources().getDimensionPixelSize( + @Px int contentHorizontalMargin = getResources().getDimensionPixelSize( R.dimen.widget_list_horizontal_margin); - if (contentHorizontalMarginInPx != mContentHorizontalMarginInPx) { - onContentHorizontalMarginChanged(contentHorizontalMarginInPx); - mContentHorizontalMarginInPx = contentHorizontalMarginInPx; + if (contentHorizontalMargin != mContentHorizontalMargin) { + onContentHorizontalMarginChanged(contentHorizontalMargin); + mContentHorizontalMargin = contentHorizontalMargin; } } @@ -198,19 +202,6 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView MeasureSpec.getSize(heightMeasureSpec)); } - /** Returns the number of cells that can fit horizontally in a given {@code content}. */ - protected int computeMaxHorizontalSpans(View content, int contentHorizontalPaddingPx) { - DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); - int availableWidth = content.getMeasuredWidth() - - contentHorizontalPaddingPx - - (2 * mContentHorizontalMarginInPx); - Point cellSize = deviceProfile.getCellSize(); - if (cellSize.x > 0) { - return availableWidth / cellSize.x; - } - return DEFAULT_MAX_HORIZONTAL_SPANS; - } - private boolean beginDraggingWidget(WidgetCell v) { if (TestProtocol.sDebugTracing) { Log.d(TestProtocol.NO_DROP_TARGET, "2"); diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java index 4099302c07..06c622dd00 100644 --- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java +++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java @@ -36,6 +36,8 @@ import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TextView; +import androidx.annotation.Px; + import com.android.launcher3.R; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.model.WidgetItem; @@ -69,8 +71,7 @@ public class WidgetsBottomSheet extends BaseWidgetSheet { private static final long EDUCATION_TIP_DELAY_MS = 300; private ItemInfo mOriginalItemInfo; - private int mMaxHorizontalSpan = DEFAULT_MAX_HORIZONTAL_SPANS; - private final int mWidgetCellHorizontalPadding; + @Px private int mMaxHorizontalSpan; private final OnLayoutChangeListener mLayoutChangeListenerToShowTips = new OnLayoutChangeListener() { @@ -111,8 +112,6 @@ public class WidgetsBottomSheet extends BaseWidgetSheet { if (!hasSeenEducationTip()) { addOnLayoutChangeListener(mLayoutChangeListenerToShowTips); } - mWidgetCellHorizontalPadding = getResources().getDimensionPixelSize( - R.dimen.widget_cell_horizontal_padding); setContentBackground(getContext().getDrawable(R.drawable.bg_rounded_corner_bottom_sheet)); } @@ -134,7 +133,7 @@ public class WidgetsBottomSheet extends BaseWidgetSheet { private boolean updateMaxSpansPerRow() { if (getMeasuredWidth() == 0) return false; - int maxHorizontalSpan = computeMaxHorizontalSpans(mContent, mWidgetCellHorizontalPadding); + @Px int maxHorizontalSpan = mContent.getMeasuredWidth() - (2 * mContentHorizontalMargin); if (mMaxHorizontalSpan != maxHorizontalSpan) { // Ensure the table layout is showing widgets in the right column after measure. mMaxHorizontalSpan = maxHorizontalSpan; @@ -184,7 +183,9 @@ public class WidgetsBottomSheet extends BaseWidgetSheet { TableLayout widgetsTable = findViewById(R.id.widgets_table); widgetsTable.removeAllViews(); - WidgetsTableUtils.groupWidgetItemsIntoTableWithReordering(widgets, mMaxHorizontalSpan) + WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgets, mActivityContext, + mActivityContext.getDeviceProfile(), mMaxHorizontalSpan, + mWidgetCellHorizontalPadding) .forEach(row -> { TableRow tableRow = new TableRow(getContext()); tableRow.setGravity(Gravity.TOP); diff --git a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java index 626e0b91c1..d709196225 100644 --- a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java +++ b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.widget.model; +import androidx.annotation.Px; + import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; @@ -26,7 +28,7 @@ import java.util.List; */ public final class WidgetsListContentEntry extends WidgetsListBaseEntry { - private final int mMaxSpanSizeInCells; + @Px private final int mMaxSpanSize; /** * Constructor for {@link WidgetsListContentEntry}. @@ -37,7 +39,7 @@ public final class WidgetsListContentEntry extends WidgetsListBaseEntry { */ public WidgetsListContentEntry(PackageItemInfo pkgItem, String titleSectionName, List items) { - this(pkgItem, titleSectionName, items, /* maxSpanSizeInCells= */ 0); + this(pkgItem, titleSectionName, items, /* maxSpanSize= */ 0); } /** @@ -46,43 +48,43 @@ public final class WidgetsListContentEntry extends WidgetsListBaseEntry { * @param pkgItem package info associated with the entry * @param titleSectionName title section name associated with the entry. * @param items list of widgets for the package. - * @param maxSpanSizeInCells the max horizontal span in cells that is allowed for grouping more + * @param maxSpanSize the max horizontal span in pixels that is allowed for grouping more * than one widgets in a table row. */ public WidgetsListContentEntry(PackageItemInfo pkgItem, String titleSectionName, - List items, int maxSpanSizeInCells) { + List items, @Px int maxSpanSize) { super(pkgItem, titleSectionName, items); - mMaxSpanSizeInCells = maxSpanSizeInCells; + mMaxSpanSize = maxSpanSize; } @Override public String toString() { - return "Content:" + mPkgItem.packageName + ":" + mWidgets.size() + " maxSpanSizeInCells: " - + mMaxSpanSizeInCells; + return "Content:" + mPkgItem.packageName + ":" + mWidgets.size() + " maxSpanSize: " + + mMaxSpanSize; } /** - * Returns a copy of this {@link WidgetsListContentEntry} with updated - * {@param maxSpanSizeInCells}. + * Returns a copy of this {@link WidgetsListContentEntry} with updated {@code maxSpanSize}. * - * @param maxSpanSizeInCells the maximum horizontal span in cells that is allowed for grouping + * @param maxSpanSize the maximum horizontal span in pixels that is allowed for grouping * more than one widgets in a table row. */ - public WidgetsListContentEntry withMaxSpanSize(int maxSpanSizeInCells) { - if (mMaxSpanSizeInCells == maxSpanSizeInCells) return this; + public WidgetsListContentEntry withMaxSpanSize(@Px int maxSpanSize) { + if (mMaxSpanSize == maxSpanSize) return this; return new WidgetsListContentEntry( mPkgItem, mTitleSectionName, mWidgets, - /* maxSpanSizeInCells= */ maxSpanSizeInCells); + /* maxSpanSize= */ maxSpanSize); } /** - * Returns the max horizontal span size in cells that is allowed for grouping more than one + * Returns the max horizontal span size in pixels that is allowed for grouping more than one * widget in a table row. */ - public int getMaxSpanSizeInCells() { - return mMaxSpanSizeInCells; + @Px + public int getMaxSpanSize() { + return mMaxSpanSize; } @Override @@ -91,6 +93,6 @@ public final class WidgetsListContentEntry extends WidgetsListBaseEntry { WidgetsListContentEntry otherEntry = (WidgetsListContentEntry) obj; return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem) && mTitleSectionName.equals(otherEntry.mTitleSectionName) - && mMaxSpanSizeInCells == otherEntry.mMaxSpanSizeInCells; + && mMaxSpanSize == otherEntry.mMaxSpanSize; } } diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index f8068aac77..5c86a66b40 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -53,6 +53,7 @@ import android.widget.TextView; import androidx.annotation.FloatRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.Px; import androidx.annotation.VisibleForTesting; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.RecyclerView; @@ -181,14 +182,13 @@ public class WidgetsFullSheet extends BaseWidgetSheet } }; - private final int mTabsHeight; - private final int mWidgetSheetContentHorizontalPadding; + @Px private final int mTabsHeight; @Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView; @Nullable private PersonalWorkPagedView mViewPager; private boolean mIsInSearchMode; private boolean mIsNoWidgetsViewNeeded; - private int mMaxSpansPerRow = DEFAULT_MAX_HORIZONTAL_SPANS; + @Px private int mMaxSpanPerRow; private TextView mNoWidgetsView; private StickyHeaderLayout mSearchScrollView; @@ -221,8 +221,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet mTabsHeight = mHasWorkProfile ? resources.getDimensionPixelSize(R.dimen.all_apps_header_pill_height) : 0; - mWidgetSheetContentHorizontalPadding = 2 * resources.getDimensionPixelSize( - R.dimen.widget_cell_horizontal_padding); mUserManagerState.init(UserCache.INSTANCE.get(context), context.getSystemService(UserManager.class)); @@ -334,7 +332,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet : mSearchScrollView.findViewById(R.id.title); mRightPane = mIsTwoPane ? mContent.findViewById(R.id.right_pane) : null; mWidgetsListTableViewHolderBinder = - new WidgetsListTableViewHolderBinder(layoutInflater, this, this); + new WidgetsListTableViewHolderBinder(mActivityContext, layoutInflater, this, this); onRecommendedWidgetsBound(); onWidgetsBound(); @@ -533,22 +531,20 @@ public class WidgetsFullSheet extends BaseWidgetSheet View content = mHasWorkProfile ? mViewPager : mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView; - if (mIsTwoPane && mRightPane != null) { content = mRightPane; } - int maxHorizontalSpans = computeMaxHorizontalSpans(content, - mWidgetSheetContentHorizontalPadding); - if (mMaxSpansPerRow != maxHorizontalSpans) { - mMaxSpansPerRow = maxHorizontalSpans; - mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow( - mMaxSpansPerRow); - mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow( - mMaxSpansPerRow); + @Px int maxHorizontalSpan = content.getMeasuredWidth() - (2 * mContentHorizontalMargin); + if (mMaxSpanPerRow != maxHorizontalSpan) { + mMaxSpanPerRow = maxHorizontalSpan; + mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow( + maxHorizontalSpan); + mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow( + maxHorizontalSpan); if (mHasWorkProfile) { - mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow( - mMaxSpansPerRow); + mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow( + maxHorizontalSpan); } onRecommendedWidgetsBound(); return true; @@ -697,8 +693,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet - noWidgetsViewHeight) * RECOMMENDATION_TABLE_HEIGHT_RATIO; List> recommendedWidgetsInTable = - WidgetsTableUtils.groupWidgetItemsIntoTableWithoutReordering( - recommendedWidgets, mMaxSpansPerRow); + WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering( + recommendedWidgets, + mActivityContext, + mActivityContext.getDeviceProfile(), + mMaxSpanPerRow, + mWidgetCellHorizontalPadding); mRecommendedWidgetsTable.setRecommendedWidgets( recommendedWidgetsInTable, maxTableHeight); } else { @@ -1046,7 +1046,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet if (mAdapterType == PRIMARY || mAdapterType == WORK) { mWidgetsRecyclerView.addOnAttachStateChangeListener(mBindScrollbarInSearchMode); } - mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow); + mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(mMaxSpanPerRow); } } diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java index c28402e403..c89eea8521 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java @@ -19,6 +19,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_DEFAULT; import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_FIRST; import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_LAST; +import static com.android.launcher3.widget.BaseWidgetSheet.DEFAULT_MAX_HORIZONTAL_SPANS; import android.content.Context; import android.os.Process; @@ -32,6 +33,7 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.Px; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.DiffUtil.DiffResult; import androidx.recyclerview.widget.LinearLayoutManager; @@ -49,6 +51,7 @@ import com.android.launcher3.widget.model.WidgetListSpaceEntry; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.model.WidgetsListContentEntry; import com.android.launcher3.widget.model.WidgetsListHeaderEntry; +import com.android.launcher3.widget.util.WidgetSizes; import java.util.ArrayList; import java.util.Arrays; @@ -99,7 +102,7 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC @Nullable private Predicate mFilter = null; @Nullable private RecyclerView mRecyclerView; @Nullable private PackageUserKey mPendingClickHeader; - private int mMaxSpanSize = 4; + @Px private int mMaxHorizontalSpan; public WidgetsListAdapter(Context context, LayoutInflater layoutInflater, IntSupplier emptySpaceHeightProvider, OnClickListener iconClickListener, @@ -107,11 +110,14 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC WidgetsFullSheet.HeaderChangeListener headerChangeListener) { mHeaderChangeListener = headerChangeListener; mContext = context; + mMaxHorizontalSpan = WidgetSizes.getWidgetSizePx( + ActivityContext.lookupContext(context).getDeviceProfile(), + DEFAULT_MAX_HORIZONTAL_SPANS, 1).getWidth(); mViewHolderBinders.put( VIEW_TYPE_WIDGETS_LIST, new WidgetsListTableViewHolderBinder( - layoutInflater, iconClickListener, iconLongClickListener)); + mContext, layoutInflater, iconClickListener, iconLongClickListener)); mViewHolderBinders.put( VIEW_TYPE_WIDGETS_HEADER, new WidgetsListHeaderViewHolderBinder( @@ -199,7 +205,8 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC } else if (entry instanceof WidgetsListContentEntry) { // Adjust the original content entries to accommodate for the current // maxSpanSize. - return ((WidgetsListContentEntry) entry).withMaxSpanSize(mMaxSpanSize); + return ((WidgetsListContentEntry) entry).withMaxSpanSize( + mMaxHorizontalSpan); } return entry; }) @@ -407,11 +414,11 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC } /** - * Sets the max horizontal span in cells that is allowed for grouping more than one widget in a + * Sets the max horizontal span in pixels that is allowed for grouping more than one widget in a * table row. */ - public void setMaxHorizontalSpansPerRow(int maxHorizontalSpans) { - mMaxSpanSize = maxHorizontalSpans; + public void setMaxHorizontalSpansPxPerRow(@Px int maxHorizontalSpan) { + mMaxHorizontalSpan = maxHorizontalSpan; updateVisibleEntries(); } diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java index 2e8f0ab1f8..c7d2aa3fd8 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.widget.picker; +import android.content.Context; import android.graphics.Bitmap; import android.util.Log; import android.util.Pair; @@ -27,9 +28,13 @@ import android.view.ViewGroup; import android.widget.TableLayout; import android.widget.TableRow; +import androidx.annotation.NonNull; +import androidx.annotation.Px; + import com.android.launcher3.R; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.recyclerview.ViewHolderBinder; +import com.android.launcher3.views.ActivityContext; import com.android.launcher3.widget.WidgetCell; import com.android.launcher3.widget.model.WidgetsListContentEntry; import com.android.launcher3.widget.util.WidgetsTableUtils; @@ -47,13 +52,21 @@ public final class WidgetsListTableViewHolderBinder private final LayoutInflater mLayoutInflater; private final OnClickListener mIconClickListener; + private @NonNull final Context mContext; + private @NonNull final ActivityContext mActivityContext; + @Px private final int mCellPadding; private final OnLongClickListener mIconLongClickListener; public WidgetsListTableViewHolderBinder( + @NonNull Context context, LayoutInflater layoutInflater, OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) { mLayoutInflater = layoutInflater; + mContext = context; + mActivityContext = ActivityContext.lookupContext(context); + mCellPadding = context.getResources().getDimensionPixelSize( + R.dimen.widget_cell_horizontal_padding); mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; } @@ -87,8 +100,11 @@ public final class WidgetsListTableViewHolderBinder (position & POSITION_LAST) != 0)); List> widgetItemsTable = - WidgetsTableUtils.groupWidgetItemsIntoTableWithReordering( - entry.mWidgets, entry.getMaxSpanSizeInCells()); + WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(entry.mWidgets, + mContext, + mActivityContext.getDeviceProfile(), + entry.getMaxSpanSize(), + mCellPadding); recycleTableBeforeBinding(table, widgetItemsTable); // Bind the widget items. diff --git a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java index 72e27bf37e..74d306276f 100644 --- a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java +++ b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java @@ -15,6 +15,11 @@ */ package com.android.launcher3.widget.util; +import android.content.Context; + +import androidx.annotation.Px; + +import com.android.launcher3.DeviceProfile; import com.android.launcher3.model.WidgetItem; import java.util.ArrayList; @@ -49,34 +54,41 @@ public final class WidgetsTableUtils { * Groups {@code widgetItems} items into a 2D array which matches their appearance in a UI * table. This takes liberty to rearrange widgets to make the table visually appealing. */ - public static List> groupWidgetItemsIntoTableWithReordering( - List widgetItems, final int maxSpansPerRow) { + public static List> groupWidgetItemsUsingRowPxWithReordering( + List widgetItems, Context context, final DeviceProfile dp, + final @Px int rowPx, final @Px int cellPadding) { List sortedWidgetItems = widgetItems.stream().sorted(WIDGET_SHORTCUT_COMPARATOR) .collect(Collectors.toList()); - return groupWidgetItemsIntoTableWithoutReordering(sortedWidgetItems, maxSpansPerRow); + return groupWidgetItemsUsingRowPxWithoutReordering(sortedWidgetItems, context, dp, rowPx, + cellPadding); } /** * Groups {@code widgetItems} into a 2D array which matches their appearance in a UI table while - * maintaining their order. + * maintaining their order. This function is a variant of + * {@code groupWidgetItemsIntoTableWithoutReordering} in that this uses widget pixels for + * calculation. * *

Grouping: * 1. Widgets and shortcuts never group together in the same row. - * 2. The ordered widgets are grouped together in the same row until their total horizontal - * spans exceed the {@code maxSpansPerRow} - 1. - * 3. The order shortcuts are grouped together in the same row until their total horizontal - * spans exceed the {@code maxSpansPerRow} - 1. - * 4. If there is only one widget in a row, its width may exceed the {@code maxSpansPerRow}. + * 2. The ordered widgets are grouped together in the same row until their individual occupying + * pixels exceed the total allowed pixels for the cell. + * 3. The ordered shortcuts are grouped together in the same row until their individual + * occupying pixels exceed the total allowed pixels for the cell. + * 4. If there is only one widget in a row, its width may exceed the {@code rowPx}. * - *

Let's say the {@code maxSpansPerRow} is set to 6. Widgets can be grouped in the same row - * if their total horizontal spans added don't exceed 5. - * Example 1: Row 1: 2x2, 2x3, 1x1. Total horizontal spans is 5. This is okay. - * Example 2: Row 1: 2x2, 4x3, 1x1. the total horizontal spans is 7. This is wrong. 4x3 and 1x1 - * should be moved to a new row. - * Example 3: Row 1: 6x4. This is okay because this is the only item in the row. + *

Let's say the {@code rowPx} is set to 600 and we have 5 widgets. Widgets can be grouped + * in the same row if each of their individual occupying pixels does not exceed + * {@code rowPx} / 5 - 2 * {@code cellPadding}. + * Example 1: Row 1: 200x200, 200x300, 100x100. Average horizontal pixels is 200 and no widgets + * exceed that width. This is okay. + * Example 2: Row 1: 200x200, 400x300, 100x100. Average horizontal pixels is 200 and one widget + * exceed that width. This is not allowed. + * Example 3: Row 1: 700x400. This is okay because this is the only item in the row. */ - public static List> groupWidgetItemsIntoTableWithoutReordering( - List widgetItems, final int maxSpansPerRow) { + public static List> groupWidgetItemsUsingRowPxWithoutReordering( + List widgetItems, Context context, final DeviceProfile dp, + final @Px int rowPx, final @Px int cellPadding) { List> widgetItemsTable = new ArrayList<>(); ArrayList widgetItemsAtRow = null; @@ -86,23 +98,28 @@ public final class WidgetsTableUtils { widgetItemsTable.add(widgetItemsAtRow); } int numOfWidgetItems = widgetItemsAtRow.size(); - int totalHorizontalSpan = widgetItemsAtRow.stream().map(item -> item.spanX) - .reduce(/* default= */ 0, Integer::sum); - int totalHorizontalSpanAfterAddingWidget = widgetItem.spanX + totalHorizontalSpan; + @Px int individualSpan = (rowPx / (numOfWidgetItems + 1)) - (2 * cellPadding); if (numOfWidgetItems == 0) { widgetItemsAtRow.add(widgetItem); } else if ( - // The max spans per row is reduced by 1 to ensure we don't pack too many - // 1xn widgets on the same row, which may reduce the space for rendering a - // widget's description. - totalHorizontalSpanAfterAddingWidget <= maxSpansPerRow - 1 - && widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1))) { + // Since the size of the widget cell is determined by dividing the maximum span + // pixels evenly, making sure that each widget would have enough span pixels to + // show their contents. + widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1)) + && widgetItemsAtRow.stream().allMatch( + item -> WidgetSizes.getWidgetItemSizePx(context, dp, item) + .getWidth() <= individualSpan) + && WidgetSizes.getWidgetItemSizePx(context, dp, widgetItem) + .getWidth() <= individualSpan) { // Group items in the same row if // 1. they are with the same type, i.e. a row can only have widgets or shortcuts but // never a mix of both. - // 2. the total number of horizontal spans are smaller than or equal to - // MAX_SPAN_PER_ROW. If an item has a horizontal span > MAX_SPAN_PER_ROW, we just - // place it in its own row regardless of the horizontal span limit. + // 2. Each widget will have horizontal cell span pixels that is at least as large as + // it is required to fit in the horizontal content, unless the widget horizontal + // span pixels is larger than the maximum allowed. + // If an item has horizontal span pixels larger than the maximum allowed pixels + // per row, we just place it in its own row regardless of the horizontal span + // limit. widgetItemsAtRow.add(widgetItem); } else { widgetItemsAtRow = new ArrayList<>(); diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java index 9dc46f17fe..e0101f5844 100644 --- a/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java +++ b/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java @@ -94,6 +94,7 @@ public final class WidgetsListTableViewHolderBinderTest { }).when(mIconCache).getTitleNoCache(any()); mViewHolderBinder = new WidgetsListTableViewHolderBinder( + mContext, LayoutInflater.from(mContext), mOnIconClickListener, mOnLongClickListener); diff --git a/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java b/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java index 715dcca588..d2c2fd7217 100644 --- a/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java +++ b/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; @@ -35,11 +36,13 @@ import android.os.UserHandle; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.pm.ShortcutConfigActivityInfo; +import com.android.launcher3.util.ActivityContextWrapper; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.util.WidgetsTableUtils; @@ -57,11 +60,19 @@ import java.util.List; public final class WidgetsTableUtilsTest { private static final String TEST_PACKAGE = "com.google.test"; + private static final int SPACE_SIZE = 10; + private static final int CELL_SIZE = 50; + private static final int NUM_OF_COLS = 5; + private static final int NUM_OF_ROWS = 5; + @Mock private IconCache mIconCache; + @Mock + private DeviceProfile mTestDeviceProfile; + private Context mContext; - private InvariantDeviceProfile mTestProfile; + private InvariantDeviceProfile mTestInvariantProfile; private WidgetItem mWidget1x1; private WidgetItem mWidget2x2; private WidgetItem mWidget2x3; @@ -76,12 +87,13 @@ public final class WidgetsTableUtilsTest { public void setUp() { MockitoAnnotations.initMocks(this); - mContext = getApplicationContext(); + mContext = new ActivityContextWrapper(getApplicationContext()); - mTestProfile = new InvariantDeviceProfile(); - mTestProfile.numRows = 5; - mTestProfile.numColumns = 5; + mTestInvariantProfile = new InvariantDeviceProfile(); + mTestInvariantProfile.numColumns = NUM_OF_COLS; + mTestInvariantProfile.numRows = NUM_OF_ROWS; + initDP(); initTestWidgets(); initTestShortcuts(); @@ -92,17 +104,17 @@ public final class WidgetsTableUtilsTest { @Test - public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpansPerRow5_shouldGroupWidgetsInTable() { + public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding0_shouldGroupWidgetsInTable() { List widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4, mWidget2x2); List> widgetItemInTable = - WidgetsTableUtils.groupWidgetItemsIntoTableWithReordering( - widgetItems, /* maxSpansPerRow= */ 5); + WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext, + mTestDeviceProfile, 220, 0); - // Row 0: 1x1, 2x2 - // Row 1: 2x3, 2x4 - // Row 2: 4x4 + // Row 0: 1x1(50px), 2x2(110px) + // Row 1: 2x3(110px), 2x4(110px) + // Row 2: 4x4(230px) assertThat(widgetItemInTable).hasSize(3); assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2); assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3, mWidget2x4); @@ -110,65 +122,91 @@ public final class WidgetsTableUtilsTest { } @Test - public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpansPerRow4_shouldGroupWidgetsInTable() { + public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding10_shouldGroupWidgetsInTable() { List widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4, mWidget2x2); List> widgetItemInTable = - WidgetsTableUtils.groupWidgetItemsIntoTableWithReordering( - widgetItems, /* maxSpansPerRow= */ 4); + WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext, + mTestDeviceProfile, 220, 10); - // Row 0: 1x1, 2x2 - // Row 1: 2x3, - // Row 2: 2x4, - // Row 3: 4x4 - assertThat(widgetItemInTable).hasSize(4); - assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2); - assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3); - assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x4); - assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4); + // Row 0: 1x1(50px), 2x2(110px) + // Row 1: 2x3(110px), 2x4(110px) + // Row 2: 4x4(230px) + assertThat(widgetItemInTable).hasSize(5); + assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1); + assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2); + assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3); + assertThat(widgetItemInTable.get(3)).containsExactly(mWidget2x4); + assertThat(widgetItemInTable.get(4)).containsExactly(mWidget4x4); } @Test - public void groupWidgetItemsIntoTableWithReordering_mixItems_maxSpansPerRow4_shouldGroupWidgetsInTable() { + public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow350_cellPadding0_shouldGroupWidgetsInTable() { + List widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4, + mWidget2x2); + + List> widgetItemInTable = + WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext, + mTestDeviceProfile, 350, 0); + + // Row 0: 1x1(50px), 2x2(110px), 2x3(110px) + // Row 1: 2x4(110px) + // Row 2: 4x4(230px) + assertThat(widgetItemInTable).hasSize(3); + assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2, mWidget2x3); + assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x4); + assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4); + } + + @Test + public void groupWidgetItemsIntoTableWithReordering_mixItems_maxSpanPxPerRow350_cellPadding0_shouldGroupWidgetsInTable() { List widgetItems = List.of(mWidget4x4, mShortcut3, mWidget2x3, mShortcut1, mWidget1x1, mShortcut2, mWidget2x4, mWidget2x2); List> widgetItemInTable = - WidgetsTableUtils.groupWidgetItemsIntoTableWithReordering( - widgetItems, /* maxSpansPerRow= */ 4); + WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext, + mTestDeviceProfile, 350, 0); - // Row 0: 1x1, 2x2 - // Row 1: 2x3, - // Row 2: 2x4, - // Row 3: 4x4 - // Row 4: shortcut3, shortcut1, shortcut2 - assertThat(widgetItemInTable).hasSize(5); - assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2); - assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3); - assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x4); - assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4); - assertThat(widgetItemInTable.get(4)).containsExactly(mShortcut3, mShortcut2, mShortcut1); + // Row 0: 1x1(50px), 2x2(110px), 2x3(110px) + // Row 1: 2x4(110px), + // Row 2: 4x4(230px) + // Row 3: shortcut3(50px), shortcut1(50px), shortcut2(50px) + assertThat(widgetItemInTable).hasSize(4); + assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2, mWidget2x3); + assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x4); + assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4); + assertThat(widgetItemInTable.get(3)).containsExactly(mShortcut3, mShortcut2, mShortcut1); } @Test - public void groupWidgetItemsIntoTableWithoutReordering_shouldMaintainTheOrder() { + public void groupWidgetItemsIntoTableWithoutReordering_maxSpanPxPerRow220_cellPadding0_shouldMaintainTheOrder() { List widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4, mWidget2x2); List> widgetItemInTable = - WidgetsTableUtils.groupWidgetItemsIntoTableWithoutReordering( - widgetItems, /* maxSpansPerRow= */ 5); + WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering(widgetItems, mContext, + mTestDeviceProfile, 220, 0); - // Row 0: 4x4 - // Row 1: 2x3, 1x1 - // Row 2: 2x4, 2x2 + // Row 0: 4x4(230px) + // Row 1: 2x3(110px), 1x1(50px) + // Row 2: 2x4(110px), 2x2(110px) assertThat(widgetItemInTable).hasSize(3); assertThat(widgetItemInTable.get(0)).containsExactly(mWidget4x4); assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3, mWidget1x1); assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x4, mWidget2x2); } + private void initDP() { + doAnswer(i -> { + ((Point) i.getArgument(0)).set(CELL_SIZE, CELL_SIZE); + return null; + }).when(mTestDeviceProfile).getCellSize(any(Point.class)); + when(mTestDeviceProfile.getCellSize()).thenReturn(new Point(CELL_SIZE, CELL_SIZE)); + mTestDeviceProfile.cellLayoutBorderSpacePx = new Point(SPACE_SIZE, SPACE_SIZE); + when(mTestDeviceProfile.shouldInsetWidgets()).thenReturn(false); + } + private void initTestWidgets() { List widgetSizes = List.of(new Point(1, 1), new Point(2, 2), new Point(2, 3), new Point(2, 4), new Point(4, 4)); @@ -184,7 +222,7 @@ public final class WidgetsTableUtilsTest { LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info); widgetInfo.spanX = widgetSize.x; widgetInfo.spanY = widgetSize.y; - widgetItems.add(new WidgetItem(widgetInfo, mTestProfile, mIconCache)); + widgetItems.add(new WidgetItem(widgetInfo, mTestInvariantProfile, mIconCache)); } ); mWidget1x1 = widgetItems.get(0);