Update the widget previews to use the container sizes

Additionally, adjusts the margins on sides, to make space for displaying
multiple previews side by side.

As a result of this change, previews aren't cropped anymore (indirectly
also fixing b/317366201).

There is one more follow up pending - to limit the height of
recommendations in two pane sheet to only visible area to keep the
suggestions quick to look and concise. It will also help with better
scroll experience when size is same across pages.

* http://screencast/cast/NTQ0Njc4ODgyNjc5MTkzNnxjYzZiYTU2Ny02Yw
* http://screencast/cast/NTk4ODMyMDkxOTE1ODc4NHw3NzdlNTgxZS1kMw
* http://screencast/cast/NTYxOTc5NjIxNjM4MTQ0MHxiMjQ2Njc3OC0zOQ
* http://screencast/cast/NDk4MjIxNjI0MDEzNjE5MnxjY2IwYmIxNi00Mw

Bug: 319152349
Flag: N/A
Test: Image test and table utils test
Change-Id: I07465bd4d84597b560a2b998ff1ccbf9867c0192
This commit is contained in:
Shamali P
2024-03-11 13:18:22 +00:00
parent c93f687f12
commit 927dd27ece
9 changed files with 218 additions and 168 deletions

View File

@@ -17,7 +17,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/widget_cell_horizontal_padding"
android:layout_marginStart="@dimen/widget_cell_horizontal_padding"
android:layout_marginEnd="@dimen/widget_cell_horizontal_padding"
android:paddingVertical="@dimen/widget_cell_vertical_padding"
android:layout_weight="1"
android:orientation="vertical"

View File

@@ -17,5 +17,4 @@
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" />

View File

@@ -37,6 +37,7 @@
<!-- Widget picker-->
<dimen name="widget_list_horizontal_margin">30dp</dimen>
<dimen name="widget_cell_horizontal_padding">16dp</dimen>
<!-- Folder spaces -->
<dimen name="folder_footer_horiz_padding">24dp</dimen>

View File

@@ -176,7 +176,7 @@
<!-- Widget tray -->
<dimen name="widget_cell_vertical_padding">8dp</dimen>
<dimen name="widget_cell_horizontal_padding">16dp</dimen>
<dimen name="widget_cell_horizontal_padding">8dp</dimen>
<dimen name="widget_cell_font_size">14sp</dimen>
<dimen name="widget_cell_app_icon_size">24dp</dimen>
<dimen name="widget_cell_app_icon_padding">8dp</dimen>
@@ -187,7 +187,6 @@
<dimen name="widget_picker_landscape_tablet_left_right_margin">117dp</dimen>
<dimen name="widget_picker_two_panels_left_right_margin">0dp</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 -->
@@ -198,7 +197,8 @@
<dimen name="widget_list_header_view_vertical_padding">20dp</dimen>
<dimen name="widget_list_entry_spacing">2dp</dimen>
<dimen name="widget_list_horizontal_margin">16dp</dimen>
<!-- Less margin on sides to let widgets table width be close to the workspace width. -->
<dimen name="widget_list_horizontal_margin">11dp</dimen>
<!-- Margin applied to the recycler view with search bar & the list of widget apps below it. -->
<dimen name="widget_list_left_pane_horizontal_margin">0dp</dimen>
<dimen name="widget_list_horizontal_margin_two_pane">24dp</dimen>

View File

@@ -57,6 +57,8 @@ import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize;
import com.android.launcher3.widget.util.WidgetSizes;
import java.util.function.Consumer;
@@ -80,7 +82,7 @@ public class WidgetCell extends LinearLayout {
* The requested scale of the preview container. It can be lower than this as well.
*/
private float mPreviewContainerScale = 1f;
private Size mPreviewContainerSize = new Size(0, 0);
private FrameLayout mWidgetImageContainer;
private WidgetImageView mWidgetImage;
private ImageView mWidgetBadge;
@@ -176,6 +178,8 @@ public class WidgetCell extends LinearLayout {
mWidgetDims.setText(null);
mWidgetDescription.setText(null);
mWidgetDescription.setVisibility(GONE);
showDescription(true);
showDimensions(true);
if (mActiveRequest != null) {
mActiveRequest.cancel();
@@ -186,6 +190,7 @@ public class WidgetCell extends LinearLayout {
mWidgetImageContainer.removeView(mAppWidgetHostViewPreview);
}
mAppWidgetHostViewPreview = null;
mPreviewContainerSize = new Size(0, 0);
mAppWidgetHostViewScale = 1f;
mPreviewContainerScale = 1f;
mItem = null;
@@ -201,30 +206,21 @@ public class WidgetCell extends LinearLayout {
* Applies the item to this view
*/
public void applyFromCellItem(WidgetItem item) {
applyFromCellItem(item, 1f);
}
/**
* Applies the item to this view
*/
public void applyFromCellItem(WidgetItem item, float previewScale) {
applyFromCellItem(item, previewScale, this::applyPreview, null);
applyFromCellItem(item, this::applyPreview, /*cachedPreview=*/null);
}
/**
* Applies the item to this view
* @param item item to apply
* @param previewScale factor to scale the preview
* @param callback callback when preview is loaded in case the preview is being loaded or cached
* @param cachedPreview previously cached preview bitmap is present
*/
public void applyFromCellItem(WidgetItem item, float previewScale,
@NonNull Consumer<Bitmap> callback, @Nullable Bitmap cachedPreview) {
mPreviewContainerScale = previewScale;
public void applyFromCellItem(WidgetItem item, @NonNull Consumer<Bitmap> callback,
@Nullable Bitmap cachedPreview) {
Context context = getContext();
mItem = item;
mWidgetSize = getWidgetItemSizePx(getContext(), mActivity.getDeviceProfile(), mItem);
initPreviewContainerSizeAndScale();
mWidgetName.setText(mItem.label);
mWidgetName.setContentDescription(
@@ -278,6 +274,17 @@ public class WidgetCell extends LinearLayout {
}
}
private void initPreviewContainerSizeAndScale() {
WidgetPreviewContainerSize previewSize = WidgetPreviewContainerSize.Companion.forItem(mItem,
mActivity.getDeviceProfile());
mPreviewContainerSize = WidgetSizes.getWidgetSizePx(mActivity.getDeviceProfile(),
previewSize.spanX, previewSize.spanY);
float scaleX = (float) mPreviewContainerSize.getWidth() / mWidgetSize.getWidth();
float scaleY = (float) mPreviewContainerSize.getHeight() / mWidgetSize.getHeight();
mPreviewContainerScale = Math.min(scaleX, scaleY);
}
private void setAppWidgetHostViewPreview(
NavigableAppWidgetHostView appWidgetHostViewPreview,
LauncherAppWidgetProviderInfo providerInfo,
@@ -383,6 +390,16 @@ public class WidgetCell extends LinearLayout {
mWidgetDescription.setVisibility(show ? VISIBLE : GONE);
}
/**
* Shows or hides the dimensions displayed below each widget.
*
* @param show a flag that shows the dimensions of the widget if {@code true}, hides it if
* {@code false}.
*/
public void showDimensions(boolean show) {
mWidgetDims.setVisibility(show ? VISIBLE : GONE);
}
/**
* Set whether the app icon, for the app that provides the widget, should be shown next to the
* title text of the widget.
@@ -448,17 +465,22 @@ public class WidgetCell extends LinearLayout {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ViewGroup.LayoutParams containerLp = mWidgetImageContainer.getLayoutParams();
mAppWidgetHostViewScale = mPreviewContainerScale;
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
containerLp.width = Math.round(mWidgetSize.getWidth() * mAppWidgetHostViewScale);
// mPreviewContainerScale ensures the needed scaling with respect to original widget size.
mAppWidgetHostViewScale = mPreviewContainerScale;
containerLp.width = mPreviewContainerSize.getWidth();
containerLp.height = mPreviewContainerSize.getHeight();
// If we don't have enough available width, scale the preview container to fit.
if (containerLp.width > maxWidth) {
containerLp.width = maxWidth;
mAppWidgetHostViewScale = (float) containerLp.width / mWidgetSize.getWidth();
mAppWidgetHostViewScale = (float) containerLp.width / mPreviewContainerSize.getWidth();
containerLp.height = Math.round(
mPreviewContainerSize.getHeight() * mAppWidgetHostViewScale);
}
containerLp.height = Math.round(mWidgetSize.getHeight() * mAppWidgetHostViewScale);
// No need to call mWidgetImageContainer.setLayoutParams as we are in measure pass
// No need to call mWidgetImageContainer.setLayoutParams as we are in measure pass
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

View File

@@ -119,7 +119,7 @@ public final class WidgetsListTableViewHolderBinder
widget.setVisibility(View.VISIBLE);
// When preview loads, notify adapter to rebind the item and possibly animate
widget.applyFromCellItem(widgetItem, 1f,
widget.applyFromCellItem(widgetItem,
bitmap -> holder.onPreviewLoaded(Pair.create(widgetItem, bitmap)),
holder.previewCache.get(widgetItem));
widget.requestLayout();

View File

@@ -17,11 +17,13 @@ package com.android.launcher3.widget.picker;
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx;
import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_SIZE_COMPARATOR;
import static java.lang.Math.max;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Size;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,26 +32,23 @@ import android.widget.TableLayout;
import android.widget.TableRow;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.util.WidgetSizes;
import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize;
import java.util.ArrayList;
import java.util.List;
/** A {@link TableLayout} for showing recommended widgets. */
public final class WidgetsRecommendationTableLayout extends TableLayout {
private static final String TAG = "WidgetsRecommendationTableLayout";
private static final float DOWN_SCALE_RATIO = 0.9f;
private static final float MAX_DOWN_SCALE_RATIO = 0.5f;
private final float mWidgetsRecommendationTableVerticalPadding;
private final float mWidgetCellVerticalPadding;
private final float mWidgetCellTextViewsHeight;
private float mRecommendationTableMaxHeight = Float.MAX_VALUE;
@Nullable private OnLongClickListener mWidgetCellOnLongClickListener;
@Nullable private OnClickListener mWidgetCellOnClickListener;
@@ -82,43 +81,40 @@ public final class WidgetsRecommendationTableLayout extends TableLayout {
* desired {@code recommendationTableMaxHeight}.
*
* <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.
* 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>
*/
public boolean setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
DeviceProfile deviceProfile,
float recommendationTableMaxHeight) {
mRecommendationTableMaxHeight = recommendationTableMaxHeight;
RecommendationTableData data = fitRecommendedWidgetsToTableSpace(/* previewScale= */ 1f,
deviceProfile,
recommendedWidgets);
bindData(data);
return !data.mRecommendationTable.isEmpty();
public int 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();
}
private void bindData(RecommendationTableData data) {
if (data.mRecommendationTable.isEmpty()) {
private void bindData(List<ArrayList<WidgetItem>> recommendationTable) {
if (recommendationTable.isEmpty()) {
setVisibility(GONE);
return;
}
removeAllViews();
for (int i = 0; i < data.mRecommendationTable.size(); i++) {
List<WidgetItem> widgetItems = data.mRecommendationTable.get(i);
for (int i = 0; i < recommendationTable.size(); i++) {
List<WidgetItem> widgetItems = recommendationTable.get(i);
TableRow tableRow = new TableRow(getContext());
// Vertically center align items, so that even if they don't fill bounds, they can
// look organized when placed together in a row.
tableRow.setGravity(Gravity.CENTER_VERTICAL);
for (WidgetItem widgetItem : widgetItems) {
WidgetCell widgetCell = addItemCell(tableRow);
widgetCell.applyFromCellItem(widgetItem, data.mPreviewScale);
widgetCell.applyFromCellItem(widgetItem);
widgetCell.showAppIconInWidgetTitle(true);
widgetCell.showBadge();
if (enableCategorizedWidgetSuggestions()) {
widgetCell.showDescription(false);
widgetCell.showDimensions(false);
}
}
addView(tableRow);
@@ -140,58 +136,32 @@ public final class WidgetsRecommendationTableLayout extends TableLayout {
return widget;
}
private RecommendationTableData fitRecommendedWidgetsToTableSpace(
float previewScale,
DeviceProfile deviceProfile,
List<ArrayList<WidgetItem>> recommendedWidgetsInTable) {
if (previewScale < MAX_DOWN_SCALE_RATIO) {
Log.w(TAG, "Hide recommended widgets. Can't down scale previews to " + previewScale);
return new RecommendationTableData(List.of(), previewScale);
}
private List<ArrayList<WidgetItem>> selectRowsThatFitInAvailableHeight(
List<ArrayList<WidgetItem>> recommendedWidgets, @Px float recommendationTableMaxHeight,
DeviceProfile deviceProfile) {
List<ArrayList<WidgetItem>> filteredRows = new ArrayList<>();
// A naive estimation of the widgets recommendation table height without inflation.
float totalHeight = mWidgetsRecommendationTableVerticalPadding;
for (int i = 0; i < recommendedWidgetsInTable.size(); i++) {
List<WidgetItem> widgetItems = recommendedWidgetsInTable.get(i);
for (int i = 0; i < recommendedWidgets.size(); i++) {
List<WidgetItem> widgetItems = recommendedWidgets.get(i);
float rowHeight = 0;
for (int j = 0; j < widgetItems.size(); j++) {
WidgetItem widgetItem = widgetItems.get(j);
Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile,
widgetItem);
float previewHeight = widgetSize.getHeight() * previewScale;
rowHeight = Math.max(rowHeight,
previewHeight + mWidgetCellTextViewsHeight + mWidgetCellVerticalPadding);
WidgetPreviewContainerSize previewContainerSize =
WidgetPreviewContainerSize.Companion.forItem(widgetItem, deviceProfile);
float widgetItemHeight = getWidgetSizePx(deviceProfile, previewContainerSize.spanX,
previewContainerSize.spanY).getHeight();
rowHeight = max(rowHeight,
widgetItemHeight + mWidgetCellTextViewsHeight + mWidgetCellVerticalPadding);
}
if (totalHeight + rowHeight <= recommendationTableMaxHeight) {
totalHeight += rowHeight;
filteredRows.add(new ArrayList<>(widgetItems));
}
totalHeight += rowHeight;
}
if (totalHeight < mRecommendationTableMaxHeight) {
return new RecommendationTableData(recommendedWidgetsInTable, previewScale);
}
if (recommendedWidgetsInTable.size() > 1) {
// We don't want to scale down widgets preview unless we really need to. Reduce the
// num of row by 1 to see if it fits.
return fitRecommendedWidgetsToTableSpace(
previewScale,
deviceProfile,
recommendedWidgetsInTable.subList(/* fromIndex= */0,
/* toIndex= */recommendedWidgetsInTable.size() - 1));
}
float nextPreviewScale = previewScale * DOWN_SCALE_RATIO;
return fitRecommendedWidgetsToTableSpace(nextPreviewScale, deviceProfile,
recommendedWidgetsInTable);
}
/** Data class for the widgets recommendation table and widgets preview scaling. */
private class RecommendationTableData {
private final List<ArrayList<WidgetItem>> mRecommendationTable;
private final float mPreviewScale;
RecommendationTableData(List<ArrayList<WidgetItem>> recommendationTable,
float previewScale) {
mRecommendationTable = recommendationTable;
mPreviewScale = previewScale;
}
// Perform re-ordering once we have filtered out recommendations that fit.
return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList();
}
}

View File

@@ -16,11 +16,13 @@
package com.android.launcher3.widget.util;
import android.content.Context;
import android.util.Size;
import androidx.annotation.Px;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize;
import java.util.ArrayList;
import java.util.Comparator;
@@ -33,8 +35,8 @@ public final class WidgetsTableUtils {
/**
* Groups widgets in the following order:
* 1. Widgets always go before shortcuts.
* 2. Widgets with smaller horizontal spans will be shown first.
* 3. If widgets have the same horizontal spans, then widgets with a smaller vertical spans will
* 2. Widgets with smaller vertical spans will be shown first.
* 3. If widgets have the same vertical spans, then widgets with a smaller horizontal spans will
* go first.
* 4. If both widgets have the same horizontal and vertical spans, they will use the same order
* from the given {@code widgetItems}.
@@ -43,13 +45,28 @@ public final class WidgetsTableUtils {
if (item.widgetInfo != null && otherItem.widgetInfo == null) return -1;
if (item.widgetInfo == null && otherItem.widgetInfo != null) return 1;
if (item.spanX == otherItem.spanX) {
if (item.spanY == otherItem.spanY) return 0;
return item.spanY > otherItem.spanY ? 1 : -1;
if (item.spanY == otherItem.spanY) {
if (item.spanX == otherItem.spanX) return 0;
return item.spanX > otherItem.spanX ? 1 : -1;
}
return item.spanX > otherItem.spanX ? 1 : -1;
return item.spanY > otherItem.spanY ? 1 : -1;
};
/**
* Comparator that enables displaying rows in increasing order of their size (totalW * H);
* except for shortcuts which always show at the bottom.
*/
public static final Comparator<ArrayList<WidgetItem>> WIDGETS_TABLE_ROW_SIZE_COMPARATOR =
Comparator.comparingInt(row -> {
if (row.stream().anyMatch(WidgetItem::isShortcut)) {
return Integer.MAX_VALUE;
} else {
int rowWidth = row.stream().mapToInt(w -> w.spanX).sum();
int rowHeight = row.get(0).spanY;
return (rowWidth * rowHeight);
}
});
/**
* 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.
@@ -59,72 +76,70 @@ public final class WidgetsTableUtils {
final @Px int rowPx, final @Px int cellPadding) {
List<WidgetItem> sortedWidgetItems = widgetItems.stream().sorted(WIDGET_SHORTCUT_COMPARATOR)
.collect(Collectors.toList());
return groupWidgetItemsUsingRowPxWithoutReordering(sortedWidgetItems, context, dp, rowPx,
List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
sortedWidgetItems, context, dp, rowPx,
cellPadding);
return rows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList();
}
/**
* Groups {@code widgetItems} into a 2D array which matches their appearance in a UI table while
* maintaining their order. This function is a variant of
* {@code groupWidgetItemsIntoTableWithoutReordering} in that this uses widget pixels for
* calculation.
* {@code groupWidgetItemsIntoTableWithoutReordering} in that this uses widget container's
* pixels for calculation.
*
* <p>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 individual occupying
* pixels exceed the total allowed pixels for the cell.
* 2. Widgets are grouped together only if they have same preview container size.
* 3. Widgets are grouped together in the same row until the total of individual container sizes
* exceed the total allowed pixels for the row.
* 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}.
*
* <p>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.
* <p>See WidgetTableUtilsTest
*/
public static List<ArrayList<WidgetItem>> groupWidgetItemsUsingRowPxWithoutReordering(
List<WidgetItem> widgetItems, Context context, final DeviceProfile dp,
final @Px int rowPx, final @Px int cellPadding) {
List<ArrayList<WidgetItem>> widgetItemsTable = new ArrayList<>();
ArrayList<WidgetItem> widgetItemsAtRow = null;
// A row displays only items of same container size.
WidgetPreviewContainerSize containerSizeForRow = null;
@Px int currentRowWidth = 0;
for (WidgetItem widgetItem : widgetItems) {
if (widgetItemsAtRow == null) {
widgetItemsAtRow = new ArrayList<>();
widgetItemsTable.add(widgetItemsAtRow);
}
int numOfWidgetItems = widgetItemsAtRow.size();
@Px int individualSpan = (rowPx / (numOfWidgetItems + 1)) - (2 * cellPadding);
WidgetPreviewContainerSize containerSize =
WidgetPreviewContainerSize.Companion.forItem(widgetItem, dp);
Size containerSizePx = WidgetSizes.getWidgetSizePx(dp, containerSize.spanX,
containerSize.spanY);
@Px int containerWidth = containerSizePx.getWidth() + (2 * cellPadding);
if (numOfWidgetItems == 0) {
widgetItemsAtRow.add(widgetItem);
} else if (
// 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) {
containerSizeForRow = containerSize;
currentRowWidth = containerWidth;
} else if ((currentRowWidth + containerWidth) <= rowPx
&& widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1))
&& containerSize.equals(containerSizeForRow)) {
// 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. 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.
// 2. Each widget in the given row has same preview container size.
widgetItemsAtRow.add(widgetItem);
currentRowWidth += containerWidth;
} else {
widgetItemsAtRow = new ArrayList<>();
widgetItemsTable.add(widgetItemsAtRow);
widgetItemsAtRow.add(widgetItem);
containerSizeForRow = containerSize;
currentRowWidth = containerWidth;
}
}
return widgetItemsTable;

View File

@@ -63,6 +63,7 @@ public final class WidgetsTableUtilsTest {
private static final String TEST_PACKAGE = "com.google.test";
private static final int SPACE_SIZE = 10;
// Note - actual widget size includes SPACE_SIZE (border) + cell padding.
private static final int CELL_SIZE = 50;
private static final int NUM_OF_COLS = 5;
private static final int NUM_OF_ROWS = 5;
@@ -105,7 +106,7 @@ public final class WidgetsTableUtilsTest {
@Test
public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding0_shouldGroupWidgetsInTable() {
public void groupWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding0() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
@@ -113,17 +114,20 @@ public final class WidgetsTableUtilsTest {
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 220, 0);
// 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);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4);
// With reordering, rows displayed in order of increasing size.
// Row 0: 1x1(50px)
// Row 1: 2x2(in a 2x2 container - 110px)
// Row 2: 2x3(in a 2x3 container - 110px), 2x4(in a 2x3 container - 110px)
// Row 3: 4x4(in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
assertThat(widgetItemInTable).hasSize(4);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
}
@Test
public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding10_shouldGroupWidgetsInTable() {
public void groupWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding10() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
@@ -131,9 +135,13 @@ public final class WidgetsTableUtilsTest {
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 220, 10);
// Row 0: 1x1(50px), 2x2(110px)
// Row 1: 2x3(110px), 2x4(110px)
// Row 2: 4x4(230px)
// With reordering, but space taken up by cell padding, so, no grouping (even if 2x2 and 2x3
// use same preview container).
// Row 0: 1x1(50px)
// Row 1: 2x2(in a 2x2 container: 130px)
// Row 2: 2x3(in a 2x3 container: 130px)
// Row 3: 2x4(in a 2x3 container: 130px)
// Row 4: 4x4(in a 3x3 container in tablet - 190px; 4x3 on phone - 250px)
assertThat(widgetItemInTable).hasSize(5);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
@@ -143,7 +151,29 @@ public final class WidgetsTableUtilsTest {
}
@Test
public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow350_cellPadding0_shouldGroupWidgetsInTable() {
public void groupWithReordering_widgetsOnly_maxSpanPxPerRow260_cellPadding10() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
List<ArrayList<WidgetItem>> widgetItemInTable =
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 260, 10);
// With reordering, even with cellPadding, enough space to group 2x3 and 2x4 (which also use
// same container)
// Row 0: 1x1(50px)
// Row 1: 2x2(in a 2x2 container: 130px)
// Row 2: 2x3(in a 2x3 container: 130px), 2x4(in a 2x3 container: 130px)
// Row 3: 4x4(in a 3x3 container in tablet - 190px; 4x3 on phone - 250px)
assertThat(widgetItemInTable).hasSize(4);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
}
@Test
public void groupWithReordering_widgetsOnly_maxSpanPxPerRow350_cellPadding0() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
@@ -151,17 +181,20 @@ public final class WidgetsTableUtilsTest {
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);
// With reordering, rows displayed in order of increasing size.
// Row 0: 1x1(50px)
// Row 1: 2x2(in a 2x2 container: 110px)
// Row 2: 2x3(in a 2x3 container: 110px), 2x4(in a 2x3 container: 110px)
// Row 3: 4x4(in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
assertThat(widgetItemInTable).hasSize(4);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
}
@Test
public void groupWidgetItemsIntoTableWithReordering_mixItems_maxSpanPxPerRow350_cellPadding0_shouldGroupWidgetsInTable() {
public void groupWithReordering_mixItems_maxSpanPxPerRow350_cellPadding0() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mShortcut3, mWidget2x3, mShortcut1,
mWidget1x1, mShortcut2, mWidget2x4, mWidget2x2);
@@ -169,19 +202,22 @@ public final class WidgetsTableUtilsTest {
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 350, 0);
// 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);
// With reordering - rows displays in order of increasing size:
// Row 0: 1x1(50px)
// Row 1: 2x2(110px)
// Row 2: 2x3 (in a 2x3 container 110px), 2x4 (in a 2x3 container 110px)
// Row 3: 4x4 (in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
// Row 4: shortcut3, shortcut1, shortcut2 (shortcuts are always displayed at bottom)
assertThat(widgetItemInTable).hasSize(5);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
assertThat(widgetItemInTable.get(4)).containsExactly(mShortcut3, mShortcut2, mShortcut1);
}
@Test
public void groupWidgetItemsIntoTableWithoutReordering_maxSpanPxPerRow220_cellPadding0_shouldMaintainTheOrder() {
public void groupWithoutReordering_maxSpanPxPerRow220_cellPadding0() {
List<WidgetItem> widgetItems =
List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4, mWidget2x2);
@@ -189,13 +225,19 @@ public final class WidgetsTableUtilsTest {
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering(widgetItems, mContext,
mTestDeviceProfile, 220, 0);
// Row 0: 4x4(230px)
// Row 1: 2x3(110px), 1x1(50px)
// Row 2: 2x4(110px), 2x2(110px)
assertThat(widgetItemInTable).hasSize(3);
// Without reordering, widgets are grouped only if the next one fits and uses same preview
// container:
// Row 0: 4x4(in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
// Row 1: 2x3(in a 2x3 container - 110px)
// Row 2: 1x1(50px)
// Row 3: 2x4(in a 2x3 container - 110px)
// Row 4: 2x2(in a 2x2 container - 110px)
assertThat(widgetItemInTable).hasSize(5);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget4x4);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3, mWidget1x1);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x4, mWidget2x2);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(3)).containsExactly(mWidget2x4);
assertThat(widgetItemInTable.get(4)).containsExactly(mWidget2x2);
}
private void initDP() {