From 6a936e3ec43af77dde93eefe0c06e6ce86a748b7 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Thu, 24 Jun 2021 10:58:56 -0700 Subject: [PATCH 1/5] Update colors for non-v31 devices. Also renamed to popup color to popup shade to better differentiate between the primary/secondary/tertirary popup colors. Bug: 191496301 Test: Removed v31 colors to verify the non-v31 colors, that the shades are as expected Tested on wallpapaper with light text, again with dark mode Tested on wallpaper with dark text, and then again with dark mode Change-Id: I3ec9ee7f85696ee48224740a1ec7f4abe42348ee --- .../{popup_color_first.xml => popup_shade_first.xml} | 0 ...{popup_color_second.xml => popup_shade_second.xml} | 0 .../{popup_color_third.xml => popup_shade_third.xml} | 0 .../{popup_color_first.xml => popup_shade_first.xml} | 0 ...{popup_color_second.xml => popup_shade_second.xml} | 0 .../{popup_color_third.xml => popup_shade_third.xml} | 0 .../{popup_color_first.xml => popup_shade_first.xml} | 2 +- ...{popup_color_second.xml => popup_shade_second.xml} | 2 +- .../{popup_color_third.xml => popup_shade_third.xml} | 2 +- res/values/attrs.xml | 3 +++ res/values/colors.xml | 11 +++++++++-- res/values/styles.xml | 6 ++++++ src/com/android/launcher3/popup/ArrowPopup.java | 8 ++++---- 13 files changed, 25 insertions(+), 9 deletions(-) rename res/color-night-v31/{popup_color_first.xml => popup_shade_first.xml} (100%) rename res/color-night-v31/{popup_color_second.xml => popup_shade_second.xml} (100%) rename res/color-night-v31/{popup_color_third.xml => popup_shade_third.xml} (100%) rename res/color-v31/{popup_color_first.xml => popup_shade_first.xml} (100%) rename res/color-v31/{popup_color_second.xml => popup_shade_second.xml} (100%) rename res/color-v31/{popup_color_third.xml => popup_shade_third.xml} (100%) rename res/color/{popup_color_first.xml => popup_shade_first.xml} (93%) rename res/color/{popup_color_second.xml => popup_shade_second.xml} (93%) rename res/color/{popup_color_third.xml => popup_shade_third.xml} (92%) diff --git a/res/color-night-v31/popup_color_first.xml b/res/color-night-v31/popup_shade_first.xml similarity index 100% rename from res/color-night-v31/popup_color_first.xml rename to res/color-night-v31/popup_shade_first.xml diff --git a/res/color-night-v31/popup_color_second.xml b/res/color-night-v31/popup_shade_second.xml similarity index 100% rename from res/color-night-v31/popup_color_second.xml rename to res/color-night-v31/popup_shade_second.xml diff --git a/res/color-night-v31/popup_color_third.xml b/res/color-night-v31/popup_shade_third.xml similarity index 100% rename from res/color-night-v31/popup_color_third.xml rename to res/color-night-v31/popup_shade_third.xml diff --git a/res/color-v31/popup_color_first.xml b/res/color-v31/popup_shade_first.xml similarity index 100% rename from res/color-v31/popup_color_first.xml rename to res/color-v31/popup_shade_first.xml diff --git a/res/color-v31/popup_color_second.xml b/res/color-v31/popup_shade_second.xml similarity index 100% rename from res/color-v31/popup_color_second.xml rename to res/color-v31/popup_shade_second.xml diff --git a/res/color-v31/popup_color_third.xml b/res/color-v31/popup_shade_third.xml similarity index 100% rename from res/color-v31/popup_color_third.xml rename to res/color-v31/popup_shade_third.xml diff --git a/res/color/popup_color_first.xml b/res/color/popup_shade_first.xml similarity index 93% rename from res/color/popup_color_first.xml rename to res/color/popup_shade_first.xml index d9999a20c8..151190b6d4 100644 --- a/res/color/popup_color_first.xml +++ b/res/color/popup_shade_first.xml @@ -14,5 +14,5 @@ limitations under the License. --> - + diff --git a/res/color/popup_color_second.xml b/res/color/popup_shade_second.xml similarity index 93% rename from res/color/popup_color_second.xml rename to res/color/popup_shade_second.xml index d9999a20c8..86608501f0 100644 --- a/res/color/popup_color_second.xml +++ b/res/color/popup_shade_second.xml @@ -14,5 +14,5 @@ limitations under the License. --> - + diff --git a/res/color/popup_color_third.xml b/res/color/popup_shade_third.xml similarity index 92% rename from res/color/popup_color_third.xml rename to res/color/popup_shade_third.xml index d7e9e794e2..95447288fd 100644 --- a/res/color/popup_color_third.xml +++ b/res/color/popup_shade_third.xml @@ -14,5 +14,5 @@ limitations under the License. --> - + \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 1fadc881c2..52df1d3ab0 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -25,6 +25,9 @@ + + + diff --git a/res/values/colors.xml b/res/values/colors.xml index 01f5364459..e32eea0e4a 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -53,6 +53,13 @@ #202124 #757575 + #F9F9F9 + #F1F1F1 + #E2E2E2 + #303030 + #262626 + #1B1B1B + #FFF #757575 @@ -62,8 +69,8 @@ #FFF #FF000000 - #FFFFFF - #FF3C4043 + #F9F9F9 + #464746 ?attr/colorPrimary diff --git a/res/values/styles.xml b/res/values/styles.xml index 871214fc4f..dbb1b0ea52 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -37,6 +37,9 @@ @color/popup_color_primary_light @color/popup_color_secondary_light @color/popup_color_tertiary_light + @color/popup_shade_first_light + @color/popup_shade_second_light + @color/popup_shade_third_light @color/popup_notification_dot_light false false @@ -97,6 +100,9 @@ @color/popup_color_secondary_dark @color/popup_color_tertiary_dark @color/popup_notification_dot_dark + @color/popup_shade_first_dark + @color/popup_shade_second_dark + @color/popup_shade_third_dark @style/WidgetContainerTheme.Dark @color/folder_dot_color @color/folder_background_dark diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java index a89fb3b618..b7fe3480fa 100644 --- a/src/com/android/launcher3/popup/ArrowPopup.java +++ b/src/com/android/launcher3/popup/ArrowPopup.java @@ -183,12 +183,12 @@ public abstract class ArrowPopup> if (isAboveAnotherSurface) { mColors = new int[] { - getColorStateList(context, R.color.popup_color_first).getDefaultColor()}; + getColorStateList(context, R.color.popup_shade_first).getDefaultColor()}; } else { mColors = new int[] { - getColorStateList(context, R.color.popup_color_first).getDefaultColor(), - getColorStateList(context, R.color.popup_color_second).getDefaultColor(), - getColorStateList(context, R.color.popup_color_third).getDefaultColor()}; + getColorStateList(context, R.color.popup_shade_first).getDefaultColor(), + getColorStateList(context, R.color.popup_shade_second).getDefaultColor(), + getColorStateList(context, R.color.popup_shade_third).getDefaultColor()}; } } From e92c63f45b9b29cf1b2fdd332a160c1f0a9bb73a Mon Sep 17 00:00:00 2001 From: Alina Zaidi Date: Thu, 24 Jun 2021 20:09:32 +0100 Subject: [PATCH 2/5] Remove scrim from navigation bar from full widget picker. Bug: 191926325 Test: Manually tested Change-Id: Idc5fded9e8227a6aa9cf58c9e2cce146dc52a2c6 --- .../launcher3/views/TopRoundedCornerView.java | 20 ------------------- .../widget/picker/WidgetsFullSheet.java | 1 - 2 files changed, 21 deletions(-) diff --git a/src/com/android/launcher3/views/TopRoundedCornerView.java b/src/com/android/launcher3/views/TopRoundedCornerView.java index 5519df1f49..92cce92ae7 100644 --- a/src/com/android/launcher3/views/TopRoundedCornerView.java +++ b/src/com/android/launcher3/views/TopRoundedCornerView.java @@ -17,12 +17,10 @@ package com.android.launcher3.views; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.util.AttributeSet; -import com.android.launcher3.R; import com.android.launcher3.util.Themes; /** @@ -34,41 +32,23 @@ public class TopRoundedCornerView extends SpringRelativeLayout { private final Path mClipPath = new Path(); private float[] mRadii; - private final Paint mNavBarScrimPaint; - private int mNavBarScrimHeight = 0; - public TopRoundedCornerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); float radius = Themes.getDialogCornerRadius(context); mRadii = new float[] {radius, radius, radius, radius, 0, 0, 0, 0}; - - mNavBarScrimPaint = new Paint(); - mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor)); } public TopRoundedCornerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } - public void setNavBarScrimHeight(int height) { - if (mNavBarScrimHeight != height) { - mNavBarScrimHeight = height; - invalidate(); - } - } - @Override public void draw(Canvas canvas) { canvas.save(); canvas.clipPath(mClipPath); super.draw(canvas); canvas.restore(); - - if (mNavBarScrimHeight > 0) { - canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(), - mNavBarScrimPaint); - } } @Override diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index 1a58bb066d..0106ef5d5f 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -323,7 +323,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet clearNavBarColor(); } - ((TopRoundedCornerView) mContent).setNavBarScrimHeight(mInsets.bottom); requestLayout(); } From 0b320545f8460dd1d7517f4a62670843794f9ebf Mon Sep 17 00:00:00 2001 From: Alina Zaidi Date: Mon, 21 Jun 2021 17:56:44 +0100 Subject: [PATCH 3/5] Compare maxSpanSize for WidgetsListContentEntries when updating visible entries in widget picker. This is so that widgets are rearranged according to maxSpanSize in widget table Bug: 191336852 Test: Manually rotated widget picker with one of the headers expanded and checked that widgets in widget table adjust according to screen's horizontal size. Change-Id: I2406ca9eccd18f4bb32b396acaa188c37f0059ee --- .../picker/WidgetsDiffReporterTest.java | 26 ++++- .../model/WidgetsListContentEntryTest.java | 105 ++++++++++++++++-- .../widget/model/WidgetsListContentEntry.java | 54 ++++++++- .../widget/picker/WidgetsDiffReporter.java | 10 +- .../widget/picker/WidgetsListAdapter.java | 23 ++-- .../WidgetsListTableViewHolderBinder.java | 8 +- .../widget/util/WidgetsTableUtils.java | 7 ++ 7 files changed, 197 insertions(+), 36 deletions(-) diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java index c946c7218f..b9f183c713 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java @@ -58,7 +58,7 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) public final class WidgetsDiffReporterTest { - private static final String TEST_PACKAGE_PREFIX = "com.google.test"; + private static final String TEST_PACKAGE_PREFIX = "com.android.test"; private static final WidgetListBaseRowEntryComparator COMPARATOR = new WidgetListBaseRowEntryComparator(); @@ -241,6 +241,30 @@ public final class WidgetsDiffReporterTest { assertThat(currentList).containsExactlyElementsIn(newList); } + @Test + public void headersContentsMix_contentMaxSpanSizeModified_shouldInvokeCorrectCallbacks() { + // GIVEN the current list has app headers [A, B, E content]. + ArrayList currentList = new ArrayList<>( + List.of(mHeaderA, mHeaderB, mContentE)); + // GIVEN the new list has max span size in "E content" modified. + List newList = List.of( + mHeaderA, + mHeaderB, + new WidgetsListContentEntry( + mContentE.mPkgItem, + mContentE.mTitleSectionName, + mContentE.mWidgets, + mContentE.getMaxSpanSizeInCells() + 1)); + + // WHEN computing the list difference. + mWidgetsDiffReporter.process(currentList, newList, COMPARATOR); + + // THEN notify "E content" has been changed. + verify(mAdapter).notifyItemChanged(/* position= */ 2); + // THEN the current list contains all elements from the new list. + assertThat(currentList).containsExactlyElementsIn(newList); + } + private WidgetsListHeaderEntry createWidgetsHeaderEntry(String packageName, String appName, int numOfWidgets) { diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java index 2d22c45e2c..106cac0fdf 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java @@ -49,8 +49,10 @@ import java.util.Map; @RunWith(RobolectricTestRunner.class) public final class WidgetsListContentEntryTest { - private static final String PACKAGE_NAME = "com.google.test"; - private final PackageItemInfo mPackageItemInfo = new PackageItemInfo(PACKAGE_NAME); + private static final String PACKAGE_NAME = "com.android.test"; + private static final String PACKAGE_NAME_2 = "com.android.test2"; + private final PackageItemInfo mPackageItemInfo1 = new PackageItemInfo(PACKAGE_NAME); + private final PackageItemInfo mPackageItemInfo2 = new PackageItemInfo(PACKAGE_NAME_2); private final ComponentName mWidget1 = ComponentName.createRelative(PACKAGE_NAME, ".mWidget1"); private final ComponentName mWidget2 = ComponentName.createRelative(PACKAGE_NAME, ".mWidget2"); private final ComponentName mWidget3 = ComponentName.createRelative(PACKAGE_NAME, ".mWidget3"); @@ -91,7 +93,7 @@ public final class WidgetsListContentEntryTest { WidgetItem widgetItem3 = createWidgetItem(mWidget3, /* spanX= */ 2, /* spanY= */ 3); // WHEN creates a WidgetsListRowEntry with the unsorted widgets. - WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(mPackageItemInfo, + WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(mPackageItemInfo1, /* titleSectionName= */ "T", List.of(widgetItem1, widgetItem2, widgetItem3)); @@ -100,7 +102,7 @@ public final class WidgetsListContentEntryTest { .containsExactly(widgetItem3, widgetItem1, widgetItem2) .inOrder(); assertThat(widgetsListRowEntry.mTitleSectionName).isEqualTo("T"); - assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(mPackageItemInfo); + assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(mPackageItemInfo1); } @Test @@ -114,7 +116,7 @@ public final class WidgetsListContentEntryTest { WidgetItem widgetItem3 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 2); // WHEN creates a WidgetsListRowEntry with the unsorted widgets. - WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(mPackageItemInfo, + WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(mPackageItemInfo1, /* titleSectionName= */ "T", List.of(widgetItem1, widgetItem2, widgetItem3)); @@ -124,7 +126,7 @@ public final class WidgetsListContentEntryTest { .containsExactly(widgetItem2, widgetItem3, widgetItem1) .inOrder(); assertThat(widgetsListRowEntry.mTitleSectionName).isEqualTo("T"); - assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(mPackageItemInfo); + assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(mPackageItemInfo1); } @Test @@ -140,7 +142,7 @@ public final class WidgetsListContentEntryTest { WidgetItem widgetItem4 = createWidgetItem(mWidget3, /* spanX= */ 2, /* spanY= */ 2); // WHEN creates a WidgetsListRowEntry with the unsorted widgets. - WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(mPackageItemInfo, + WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(mPackageItemInfo1, /* titleSectionName= */ "T", List.of(widgetItem1, widgetItem2, widgetItem3, widgetItem4)); @@ -151,9 +153,96 @@ public final class WidgetsListContentEntryTest { .containsExactly(widgetItem4, widgetItem2, widgetItem1, widgetItem3) .inOrder(); assertThat(widgetsListRowEntry.mTitleSectionName).isEqualTo("T"); - assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(mPackageItemInfo); + assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(mPackageItemInfo1); } + @Test + public void equals_entriesWithDifferentPackageItemInfo_returnFalse() { + WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 3); + WidgetsListContentEntry widgetsListRowEntry1 = new WidgetsListContentEntry( + mPackageItemInfo1, + /* titleSectionName= */ "T", + List.of(widgetItem1), + /* maxSpanSizeInCells= */ 3); + WidgetsListContentEntry widgetsListRowEntry2 = new WidgetsListContentEntry( + mPackageItemInfo2, + /* titleSectionName= */ "T", + List.of(widgetItem1), + /* maxSpanSizeInCells= */ 3); + + assertThat(widgetsListRowEntry1.equals(widgetsListRowEntry2)).isFalse(); + } + + @Test + public void equals_entriesWithDifferentTitleSectionName_returnFalse() { + WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 3); + WidgetsListContentEntry widgetsListRowEntry1 = new WidgetsListContentEntry( + mPackageItemInfo1, + /* titleSectionName= */ "T", + List.of(widgetItem1), + /* maxSpanSizeInCells= */ 3); + WidgetsListContentEntry widgetsListRowEntry2 = new WidgetsListContentEntry( + mPackageItemInfo1, + /* titleSectionName= */ "S", + List.of(widgetItem1), + /* maxSpanSizeInCells= */ 3); + + assertThat(widgetsListRowEntry1.equals(widgetsListRowEntry2)).isFalse(); + } + + @Test + public void equals_entriesWithDifferentWidgetsList_returnFalse() { + WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 3); + WidgetItem widgetItem2 = createWidgetItem(mWidget2, /* spanX= */ 2, /* spanY= */ 3); + WidgetsListContentEntry widgetsListRowEntry1 = new WidgetsListContentEntry( + mPackageItemInfo1, + /* titleSectionName= */ "T", + List.of(widgetItem1), + /* maxSpanSizeInCells= */ 3); + WidgetsListContentEntry widgetsListRowEntry2 = new WidgetsListContentEntry( + mPackageItemInfo1, + /* titleSectionName= */ "T", + List.of(widgetItem2), + /* maxSpanSizeInCells= */ 3); + + assertThat(widgetsListRowEntry1.equals(widgetsListRowEntry2)).isFalse(); + } + + @Test + public void equals_entriesWithDifferentMaxSpanSize_returnFalse() { + WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 3); + WidgetsListContentEntry widgetsListRowEntry1 = new WidgetsListContentEntry( + mPackageItemInfo1, + /* titleSectionName= */ "T", + List.of(widgetItem1), + /* maxSpanSizeInCells= */ 3); + WidgetsListContentEntry widgetsListRowEntry2 = new WidgetsListContentEntry( + mPackageItemInfo1, + /* titleSectionName= */ "T", + List.of(widgetItem1), + /* maxSpanSizeInCells= */ 2); + + assertThat(widgetsListRowEntry1.equals(widgetsListRowEntry2)).isFalse(); + } + + @Test + public void equals_entriesWithSameContents_returnTrue() { + WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 3); + WidgetsListContentEntry widgetsListRowEntry1 = new WidgetsListContentEntry( + mPackageItemInfo1, + /* titleSectionName= */ "T", + List.of(widgetItem1), + /* maxSpanSizeInCells= */ 3); + WidgetsListContentEntry widgetsListRowEntry2 = new WidgetsListContentEntry( + mPackageItemInfo1, + /* titleSectionName= */ "T", + List.of(widgetItem1), + /* maxSpanSizeInCells= */ 3); + + assertThat(widgetsListRowEntry1.equals(widgetsListRowEntry2)).isTrue(); + } + + private WidgetItem createWidgetItem(ComponentName componentName, int spanX, int spanY) { String label = mWidgetsToLabels.get(componentName); ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager()); diff --git a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java index 0328cf68ba..73b17f1d4f 100644 --- a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java +++ b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java @@ -26,14 +26,39 @@ import java.util.List; */ public final class WidgetsListContentEntry extends WidgetsListBaseEntry { + private final int mMaxSpanSizeInCells; + + /** + * Constructor for {@link WidgetsListContentEntry}. + * + * @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. + */ public WidgetsListContentEntry(PackageItemInfo pkgItem, String titleSectionName, List items) { + this(pkgItem, titleSectionName, items, /* maxSpanSizeInCells= */ 0); + } + + /** + * Constructor for {@link WidgetsListContentEntry}. + * + * @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 + * than one widgets in a table row. + */ + public WidgetsListContentEntry(PackageItemInfo pkgItem, String titleSectionName, + List items, int maxSpanSizeInCells) { super(pkgItem, titleSectionName, items); + mMaxSpanSizeInCells = maxSpanSizeInCells; } @Override public String toString() { - return "Content:" + mPkgItem.packageName + ":" + mWidgets.size(); + return "Content:" + mPkgItem.packageName + ":" + mWidgets.size() + " maxSpanSizeInCells: " + + mMaxSpanSizeInCells; } @Override @@ -42,11 +67,36 @@ public final class WidgetsListContentEntry extends WidgetsListBaseEntry { return RANK_WIDGETS_LIST_CONTENT; } + /** + * Returns a copy of this {@link WidgetsListContentEntry} with updated + * {@param maxSpanSizeInCells}. + * + * @param maxSpanSizeInCells the maximum horizontal span in cells that is allowed for grouping + * more than one widgets in a table row. + */ + public WidgetsListContentEntry withMaxSpanSize(int maxSpanSizeInCells) { + if (mMaxSpanSizeInCells == maxSpanSizeInCells) return this; + return new WidgetsListContentEntry( + mPkgItem, + mTitleSectionName, + mWidgets, + /* maxSpanSizeInCells= */ maxSpanSizeInCells); + } + + /** + * Returns the max horizontal span size in cells that is allowed for grouping more than one + * widget in a table row. + */ + public int getMaxSpanSizeInCells() { + return mMaxSpanSizeInCells; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof WidgetsListContentEntry)) return false; WidgetsListContentEntry otherEntry = (WidgetsListContentEntry) obj; return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem) - && mTitleSectionName.equals(otherEntry.mTitleSectionName); + && mTitleSectionName.equals(otherEntry.mTitleSectionName) + && mMaxSpanSizeInCells == otherEntry.mMaxSpanSizeInCells; } } diff --git a/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java index dfe447a38e..99374f5eef 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java +++ b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java @@ -115,7 +115,7 @@ public class WidgetsDiffReporter { // or did the widget size and desc, span, etc change? if (!isSamePackageItemInfo(orgRowEntry.mPkgItem, newRowEntry.mPkgItem) || hasHeaderUpdated(orgRowEntry, newRowEntry) - || hasWidgetsListChanged(orgRowEntry, newRowEntry)) { + || hasWidgetsListContentChanged(orgRowEntry, newRowEntry)) { index = currentEntries.indexOf(orgRowEntry); currentEntries.set(index, newRowEntry); mListener.notifyItemChanged(index); @@ -158,17 +158,15 @@ public class WidgetsDiffReporter { /** * Returns {@code true} if both {@code curRow} & {@code newRow} are - * {@link WidgetsListContentEntry}s with a different list of widgets. + * {@link WidgetsListContentEntry}s with a different list or arrangement of widgets. */ - private boolean hasWidgetsListChanged(WidgetsListBaseEntry curRow, + private boolean hasWidgetsListContentChanged(WidgetsListBaseEntry curRow, WidgetsListBaseEntry newRow) { if (!(curRow instanceof WidgetsListContentEntry) || !(newRow instanceof WidgetsListContentEntry)) { return false; } - WidgetsListContentEntry orgRowEntry = (WidgetsListContentEntry) curRow; - WidgetsListContentEntry newRowEntry = (WidgetsListContentEntry) newRow; - return !orgRowEntry.mWidgets.equals(newRowEntry.mWidgets); + return !curRow.equals(newRow); } /** diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java index b668c90031..08a2263eff 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java @@ -111,6 +111,7 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC @Nullable private PackageUserKey mPendingClickHeader; private final int mShortcutPreviewPadding; private final int mSpacingBetweenEntries; + private int mMaxSpanSize = 4; private final WidgetPreviewLoadedCallback mPreviewLoadedCallback = ignored -> updateVisibleEntries(); @@ -249,10 +250,14 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC .filter(entry -> (mFilter == null || mFilter.test(entry)) && mHeaderAndSelectedContentFilter.test(entry)) .map(entry -> { - // Adjust the original entries to expand headers for the selected content. if (entry instanceof WidgetsListBaseEntry.Header && matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) { + // Adjust the original entries to expand headers for the selected content. return ((WidgetsListBaseEntry.Header) entry).withWidgetListShown(); + } else if (entry instanceof WidgetsListContentEntry) { + // Adjust the original content entries to accommodate for the current + // maxSpanSize. + return ((WidgetsListContentEntry) entry).withMaxSpanSize(mMaxSpanSize); } return entry; }) @@ -491,20 +496,12 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC } /** - * Sets the max horizontal spans that are allowed for grouping more than one widgets in a table - * row. - * - *

If there is only one widget in a row, that widget horizontal span is allowed to exceed - * {@code maxHorizontalSpans}. - *

Let's say the max horizontal spans is set to 5. 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. + * Sets the max horizontal span in cells that is allowed for grouping more than one widget in a + * table row. */ public void setMaxHorizontalSpansPerRow(int maxHorizontalSpans) { - mWidgetsListTableViewHolderBinder.setMaxSpansPerRow(maxHorizontalSpans); + mMaxSpanSize = maxHorizontalSpans; + updateVisibleEntries(); } /** diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java index 7b526639f5..57dec1401e 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java @@ -49,7 +49,6 @@ public final class WidgetsListTableViewHolderBinder private static final boolean DEBUG = false; private static final String TAG = "WidgetsListRowViewHolderBinder"; - private int mMaxSpansPerRow = 4; private final LayoutInflater mLayoutInflater; private final OnClickListener mIconClickListener; private final OnLongClickListener mIconLongClickListener; @@ -82,10 +81,6 @@ public final class WidgetsListTableViewHolderBinder mApplyBitmapDeferred = applyBitmapDeferred; } - public void setMaxSpansPerRow(int maxSpansPerRow) { - mMaxSpansPerRow = maxSpansPerRow; - } - @Override public WidgetsRowViewHolder newViewHolder(ViewGroup parent) { if (DEBUG) { @@ -113,7 +108,8 @@ public final class WidgetsListTableViewHolderBinder position == mWidgetsListAdapter.getItemCount() - 1 ? LAST : MIDDLE); List> widgetItemsTable = - WidgetsTableUtils.groupWidgetItemsIntoTable(entry.mWidgets, mMaxSpansPerRow); + WidgetsTableUtils.groupWidgetItemsIntoTable( + entry.mWidgets, entry.getMaxSpanSizeInCells()); recycleTableBeforeBinding(table, widgetItemsTable); // Bind the widget items. for (int i = 0; i < widgetItemsTable.size(); i++) { diff --git a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java index 7294a3ab31..54aaf93ac0 100644 --- a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java +++ b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java @@ -56,6 +56,13 @@ public final class WidgetsTableUtils { * 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}. + * + *

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. */ public static List> groupWidgetItemsIntoTable( List widgetItems, final int maxSpansPerRow) { From 420ba54dee386cbf563940e81a24aff782d7a124 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 24 Jun 2021 14:02:26 -0700 Subject: [PATCH 4/5] Improving hierarchy dump for test failure Bug: 192003777 Test: Manual Change-Id: I3223a14cd88beddd76ecae0ae37c6fdce0752bef --- .../launcher3/util/rule/FailureWatcher.java | 70 +++++++++++-------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java index 4c4794789e..dc59bddabd 100644 --- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java +++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java @@ -2,6 +2,8 @@ package com.android.launcher3.util.rule; import static androidx.test.InstrumentationRegistry.getInstrumentation; +import android.os.FileUtils; +import android.os.ParcelFileDescriptor.AutoCloseInputStream; import android.util.Log; import androidx.test.uiautomator.UiDevice; @@ -12,9 +14,12 @@ import com.android.launcher3.ui.AbstractLauncherUiTest; import org.junit.rules.TestWatcher; import org.junit.runner.Description; -import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; public class FailureWatcher extends TestWatcher { private static final String TAG = "FailureWatcher"; @@ -26,20 +31,6 @@ public class FailureWatcher extends TestWatcher { mLauncher = launcher; } - private static void dumpViewHierarchy(UiDevice device) { - final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - try { - device.dumpWindowHierarchy(stream); - stream.flush(); - stream.close(); - for (String line : stream.toString().split("\\r?\\n")) { - Log.e(TAG, line.trim()); - } - } catch (IOException e) { - Log.e(TAG, "error dumping XML to logcat", e); - } - } - @Override protected void succeeded(Description description) { super.succeeded(description); @@ -53,22 +44,41 @@ public class FailureWatcher extends TestWatcher { public static void onError(UiDevice device, Description description, Throwable e) { if (device == null) return; - final String pathname = getInstrumentation().getTargetContext(). - getFilesDir().getPath() + "/TestScreenshot-" + description.getMethodName() - + ".png"; - Log.e(TAG, "Failed test " + description.getMethodName() + - ", screenshot will be saved to " + pathname + - ", track trace is below, UI object dump is further below:\n" + - Log.getStackTraceString(e)); - dumpViewHierarchy(device); + final File parentFile = getInstrumentation().getTargetContext().getFilesDir(); + final File sceenshot = new File(parentFile, + "TestScreenshot-" + description.getMethodName() + ".png"); + final File hierarchy = new File(parentFile, + "Hierarchy-" + description.getMethodName() + ".zip"); - try { - final String dumpsysResult = device.executeShellCommand( - "dumpsys activity service TouchInteractionService"); - Log.d(TAG, "TouchInteractionService: " + dumpsysResult); - } catch (IOException ex) { + // Dump window hierarchy + try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(hierarchy))) { + out.putNextEntry(new ZipEntry("bugreport.txt")); + dumpStringCommand("dumpsys window windows", out); + dumpStringCommand("dumpsys package", out); + dumpStringCommand("dumpsys activity service TouchInteractionService", out); + out.closeEntry(); + + out.putNextEntry(new ZipEntry("visible_windows.zip")); + dumpCommand("cmd window dump-visible-window-views", out); + out.closeEntry(); + } catch (IOException ex) { } + + Log.e(TAG, "Failed test " + description.getMethodName() + + ",\nscreenshot will be saved to " + sceenshot + + ",\nUI dump at: " + hierarchy + + " (use go/web-hv to open the dump file)", e); + device.takeScreenshot(sceenshot); + } + + private static void dumpStringCommand(String cmd, OutputStream out) throws IOException { + out.write(("\n\n" + cmd + "\n").getBytes()); + dumpCommand(cmd, out); + } + + private static void dumpCommand(String cmd, OutputStream out) throws IOException { + try (AutoCloseInputStream in = new AutoCloseInputStream(getInstrumentation() + .getUiAutomation().executeShellCommand(cmd))) { + FileUtils.copy(in, out); } - - device.takeScreenshot(new File(pathname)); } } From 85a84e52cc4eb4f2c07804c9ff4bbe09da92048d Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Fri, 25 Jun 2021 12:36:22 -0700 Subject: [PATCH 5/5] Enabling screen record for some flaky tests Bug: 187080582 Bug: 192005114 Bug: 192018189 Bug: 192010616 Test: Manual Change-Id: I32fc0e4c9848c4def1aa6254139579bc631862cc --- .../launcher3/ui/AbstractLauncherUiTest.java | 4 + .../launcher3/ui/TaplTestsLauncher3.java | 2 + .../ui/widget/RequestPinItemTest.java | 3 + .../launcher3/util/rule/ScreenRecordRule.java | 82 +++++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 tests/src/com/android/launcher3/util/rule/ScreenRecordRule.java diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 2712bc02ce..bf4eba0f64 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -65,6 +65,7 @@ import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.FailureWatcher; import com.android.launcher3.util.rule.LauncherActivityRule; +import com.android.launcher3.util.rule.ScreenRecordRule; import com.android.launcher3.util.rule.ShellCommandRule; import com.android.launcher3.util.rule.TestStabilityRule; @@ -204,6 +205,9 @@ public abstract class AbstractLauncherUiTest { public ShellCommandRule mDisableHeadsUpNotification = ShellCommandRule.disableHeadsUpNotification(); + @Rule + public ScreenRecordRule mScreenRecordRule = new ScreenRecordRule(); + protected void clearPackageData(String pkg) throws IOException, InterruptedException { final CountDownLatch count = new CountDownLatch(2); final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java index 06bc26a2a2..4dd44f4193 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java @@ -36,6 +36,7 @@ import com.android.launcher3.tapl.AppIconMenu; import com.android.launcher3.tapl.AppIconMenuItem; import com.android.launcher3.tapl.Widgets; import com.android.launcher3.tapl.Workspace; +import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; import com.android.launcher3.views.OptionsPopupView; import com.android.launcher3.widget.picker.WidgetsFullSheet; import com.android.launcher3.widget.picker.WidgetsRecyclerView; @@ -92,6 +93,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { } @Test + @ScreenRecord //b/187080582 public void testDevicePressMenu() throws Exception { mDevice.pressMenu(); mDevice.waitForIdle(); diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java index 822fefc208..745dc220ba 100644 --- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java +++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java @@ -42,6 +42,7 @@ import com.android.launcher3.testcomponent.RequestPinItemActivity; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.util.Wait; import com.android.launcher3.util.Wait.Condition; +import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; import com.android.launcher3.util.rule.ShellCommandRule; import org.junit.Before; @@ -77,6 +78,7 @@ public class RequestPinItemTest extends AbstractLauncherUiTest { public void testEmpty() throws Throwable { /* needed while the broken tests are being fixed */ } @Test + @ScreenRecord //b/192010616 public void testPinWidgetNoConfig() throws Throwable { runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo && ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && @@ -85,6 +87,7 @@ public class RequestPinItemTest extends AbstractLauncherUiTest { } @Test + @ScreenRecord //b/192005114 public void testPinWidgetNoConfig_customPreview() throws Throwable { // Command to set custom preview Intent command = RequestPinItemActivity.getCommandIntent( diff --git a/tests/src/com/android/launcher3/util/rule/ScreenRecordRule.java b/tests/src/com/android/launcher3/util/rule/ScreenRecordRule.java new file mode 100644 index 0000000000..00b1cdd35e --- /dev/null +++ b/tests/src/com/android/launcher3/util/rule/ScreenRecordRule.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2021 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.util.rule; + +import static androidx.test.InstrumentationRegistry.getInstrumentation; + +import android.app.Instrumentation; +import android.app.UiAutomation; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import androidx.test.uiautomator.UiDevice; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.io.File; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Rule which captures a screen record for a test. + * After adding this rule to the test class, apply the annotation @ScreenRecord to individual tests + */ +public class ScreenRecordRule implements TestRule { + + private static final String TAG = "ScreenRecordRule"; + + @Override + public Statement apply(Statement base, Description description) { + if (description.getAnnotation(ScreenRecord.class) == null) { + return base; + } + + return new Statement() { + @Override + public void evaluate() throws Throwable { + Instrumentation inst = getInstrumentation(); + UiAutomation automation = inst.getUiAutomation(); + UiDevice device = UiDevice.getInstance(inst); + + File outputFile = new File(inst.getTargetContext().getFilesDir(), + "screenrecord-" + description.getMethodName() + ".mp4"); + device.executeShellCommand("killall screenrecord"); + ParcelFileDescriptor output = + automation.executeShellCommand("screenrecord " + outputFile); + String screenRecordPid = device.executeShellCommand("pidof screenrecord"); + try { + base.evaluate(); + } finally { + device.executeShellCommand("kill -INT " + screenRecordPid); + Log.e(TAG, "Screenrecord captured at: " + outputFile); + output.close(); + } + } + }; + } + + /** + * Interface to indicate that the test should capture screenrecord + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface ScreenRecord { } +}