Inspect preset sizes for Autosizing
TextView will throw if there are no specified sizes or all off them
are 0 or negative.
Bug: 302531969
Test: Added
Change-Id: Ie700ca4fc6159697d091d91a8497a09c04d5bd4c
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
index cf3b9b5..be6c6ca 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
@@ -1168,16 +1168,32 @@
// No autosizing needed.
textView.setTextSize(COMPLEX_UNIT_SP, sizes.get(0).getValue());
} else if (isAutoSizeAllowed && sizesCnt <= TEXT_AUTOSIZES_LIMIT) {
- // Max size is needed so that TextView leaves enough space for it. Otherwise,
- // the text won't be able to grow.
- int maxSize =
- sizes.stream().mapToInt(sp -> (int) sp.getValue()).max().getAsInt();
- textView.setTextSize(COMPLEX_UNIT_SP, maxSize);
+ // We need to check values so that we are certain that there's at least 1 non zero
+ // value.
+ boolean atLeastOneCorrectSize =
+ sizes.stream()
+ .mapToInt(sp -> (int) sp.getValue())
+ .filter(sp -> sp > 0)
+ .distinct()
+ .count()
+ > 0;
- // No need for sorting, TextView does that.
- textView.setAutoSizeTextTypeUniformWithPresetSizes(
- sizes.stream().mapToInt(spProp -> (int) spProp.getValue()).toArray(),
- COMPLEX_UNIT_SP);
+ if (atLeastOneCorrectSize) {
+ // Max size is needed so that TextView leaves enough space for it. Otherwise,
+ // the text won't be able to grow.
+ int maxSize =
+ sizes.stream().mapToInt(sp -> (int) sp.getValue()).max().getAsInt();
+ textView.setTextSize(COMPLEX_UNIT_SP, maxSize);
+
+ // No need for sorting, TextView does that.
+ textView.setAutoSizeTextTypeUniformWithPresetSizes(
+ sizes.stream().mapToInt(spProp -> (int) spProp.getValue()).toArray(),
+ COMPLEX_UNIT_SP);
+ } else {
+ Log.w(
+ TAG,
+ "Trying to autosize text but no valid font sizes has been specified.");
+ }
} else {
// Fallback where multiple values can't be used and the last value would be used.
// This can happen in two cases.
@@ -1191,8 +1207,8 @@
Log.w(
TAG,
"More than " + TEXT_AUTOSIZES_LIMIT + " sizes has been added for the "
- + "text autosizing. Ignoring values after the "
- + TEXT_AUTOSIZES_LIMIT + " one.");
+ + "text autosizing. Ignoring all other sizes and using the last"
+ + "one.");
}
textView.setTextSize(COMPLEX_UNIT_SP, sizes.get(sizesCnt - 1).getValue());
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
index cb9e6d6..c6a3d9c 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
@@ -2092,6 +2092,35 @@
}
@Test
+ public void inflate_textView_autosize_wrongSizes_noop() {
+ String text = "Test text";
+ List<DimensionProto.SpProp> sizes = buildSizesList(new int[]{0, -2, 0});
+
+ LayoutElement textElement =
+ LayoutElement.newBuilder()
+ .setText(
+ Text.newBuilder()
+ .setText(string(text))
+ .setFontStyle(
+ FontStyle.newBuilder()
+ .addAllSize(sizes)))
+ .build();
+ LayoutElement root =
+ LayoutElement.newBuilder().setBox(
+ Box.newBuilder()
+ .setWidth(expand())
+ .setHeight(expand())
+ .addContents(textElement)).build();
+
+ FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
+ ArrayList<View> textChildren = new ArrayList<>();
+ rootLayout.findViewsWithText(textChildren, text, View.FIND_VIEWS_WITH_TEXT);
+ TextView tv = (TextView) textChildren.get(0);
+ expect.that(tv.getAutoSizeTextType()).isEqualTo(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
+ expect.that(tv.getAutoSizeTextAvailableSizes()).isEmpty();
+ }
+
+ @Test
public void inflate_spannable_marqueeAnimation() {
String text = "Marquee Animation";
LayoutElement root =
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
index 1cf6bce..19707de 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
@@ -700,9 +700,8 @@
}
/**
- * Gets the size of the font, in scaled pixels (sp). If not specified, defaults to the size
- * of the system's "body" font. If more than one size was originally added, it will
- * return the last one.
+ * Gets the size of the font, in scaled pixels (sp). If more than one size was originally
+ * added, it will return the last one.
*
* @since 1.0
*/
@@ -713,8 +712,7 @@
}
/**
- * Gets the available sizes of the font, in scaled pixels (sp). If not specified, defaults
- * to the size of the system's "body" font.
+ * Gets the available sizes of the font, in scaled pixels (sp).
*
* @since 1.3
*/
@@ -787,52 +785,6 @@
public Builder() {}
/**
- * Sets the available sizes of the font, in scaled pixels (sp). If not specified,
- * defaults to the size of the system's "body" font.
- *
- * <p>If more than one size is specified and this {@link FontStyle} is applied to a
- * {@link Text} element with static text, the text size will be automatically picked
- * from the provided sizes to try to perfectly fit within its parent bounds. In other
- * words, the largest size from the specified preset sizes that can fit the most text
- * within the parent bounds will be used.
- *
- * <p>The specified sizes don't have to be sorted. The maximum number of sizes used is
- * limited to 10.
- *
- * <p>Note that, if multiple sizes are set, the parent of the {@link Text} element this
- * corresponds to shouldn't have its width and height set to wrapped, as it can lead to
- * unexpected results.
- *
- * <p>If this {@link FontStyle} is set to any other element besides {@link Text} or
- * that {@link Text} element has dynamic field, only the last added size will be use.
- *
- * <p>Any previously added values with this method or {@link #setSize} will be cleared.
- *
- * <p>While this field is accessible from 1.0 as singular, it only accepts multiple
- * values since version 1.3 and renderers supporting version 1.3 will use the multiple
- * values to automatically scale text. Renderers who don't support this version will
- * use the last size among multiple values.
- *
- * @throws IllegalArgumentException if the number of available sizes is larger than 10.
- * @since 1.3
- */
- @NonNull
- @ProtoLayoutExperimental
- public Builder setSizes(@NonNull SpProp... sizes) {
- if (sizes.length > TEXT_SIZES_LIMIT) {
- throw new IllegalArgumentException(
- "Number of available sizes can't be larger than 10.");
- }
- mImpl.clearSize();
- for (SpProp size: sizes) {
- mImpl.addSize(size.toProto());
- mFingerprint.recordPropertyUpdate(
- 1, checkNotNull(size.getFingerprint()).aggregateValueAsInt());
- }
- return this;
- }
-
- /**
* Sets whether the text should be rendered in a italic typeface. If not specified,
* defaults to "false".
*
@@ -878,23 +830,6 @@
}
/**
- * Sets the size of the font, in scaled pixels (sp). If not specified, defaults to the
- * size of the system's "body" font.
- *
- * <p>Any previously added values with this method or {@link #setSizes} will be cleared.
- *
- * @since 1.0
- */
- @NonNull
- public Builder setSize(@NonNull SpProp size) {
- mImpl.clearSize();
- mImpl.addSize(size.toProto());
- mFingerprint.recordPropertyUpdate(
- 1, checkNotNull(size.getFingerprint()).aggregateValueAsInt());
- return this;
- }
-
- /**
* Sets the text color. If not defined, defaults to white.
*
* <p>While this field is statically accessible from 1.0, it's only bindable since
@@ -982,6 +917,76 @@
return this;
}
+ /**
+ * Sets the available sizes of the font, in scaled pixels (sp). If not specified,
+ * defaults to the size of the system's "body" font.
+ *
+ * <p>If more than one size is specified and this {@link FontStyle} is applied to a
+ * {@link Text} element with static text, the text size will be automatically picked
+ * from the provided sizes to try to perfectly fit within its parent bounds. In other
+ * words, the largest size from the specified preset sizes that can fit the most text
+ * within the parent bounds will be used.
+ *
+ * <p>The specified sizes don't have to be sorted, but they need to contain at least
+ * one positive value. The maximum number of sizes used is limited to 10.
+ *
+ * <p>Note that, if multiple sizes are set, the parent of the {@link Text} element this
+ * corresponds to shouldn't have its width and height set to wrapped, as it can lead to
+ * unexpected results.
+ *
+ * <p>If this {@link FontStyle} is set to any other element besides {@link Text} or
+ * that {@link Text} element has dynamic field, only the last added size will be use.
+ *
+ * <p>Any previously added values with this method or {@link #setSize} will be cleared.
+ *
+ * <p>While this field is accessible from 1.0 as singular, it only accepts multiple
+ * values since version 1.3 and renderers supporting version 1.3 will use the multiple
+ * values to automatically scale text. Renderers who don't support this version will
+ * use the last size among multiple values.
+ *
+ * @throws IllegalArgumentException if the number of available sizes is larger than 10.
+ * @since 1.3
+ */
+ @NonNull
+ @ProtoLayoutExperimental
+ public Builder setSizes(@NonNull SpProp... sizes) {
+ if (sizes.length > TEXT_SIZES_LIMIT) {
+ throw new IllegalArgumentException(
+ "Number of available sizes of the font style can't be larger than 10.");
+ }
+
+ mImpl.clearSize();
+ for (SpProp size: sizes) {
+ if (size.getValue() <= 0) {
+ throw new IllegalArgumentException(
+ "Available sizes of the font style must contain only positive "
+ + "value.");
+ }
+
+ mImpl.addSize(size.toProto());
+ mFingerprint.recordPropertyUpdate(
+ 1, checkNotNull(size.getFingerprint()).aggregateValueAsInt());
+ }
+ return this;
+ }
+
+ /**
+ * Sets the size of the font, in scaled pixels (sp). If not specified, defaults to the
+ * size of the system's "body" font.
+ *
+ * <p>Any previously added values with this method or {@link #setSizes} will be cleared.
+ *
+ * @since 1.0
+ */
+ @NonNull
+ public Builder setSize(@NonNull SpProp size) {
+ mImpl.clearSize();
+ mImpl.addSize(size.toProto());
+ mFingerprint.recordPropertyUpdate(
+ 1, checkNotNull(size.getFingerprint()).aggregateValueAsInt());
+ return this;
+ }
+
/** Builds an instance from accumulated values. */
@NonNull
public FontStyle build() {
diff --git a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
index 6f10e5a..370a8bd 100644
--- a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
+++ b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
@@ -239,11 +239,11 @@
LayoutElementProto.FontStyle fontStyleProto = fontStyle.toProto();
- assertThat(fontStyleProto.getSizeList().size()).isEqualTo(1);
+ assertThat(fontStyleProto.getSizeList()).hasSize(1);
assertThat(fontStyleProto.getSizeList().get(0).getValue()).isEqualTo(lastSize);
// Make sure that if 1 size is used than it's the last one.
assertThat(fontStyle.getSize().getValue()).isEqualTo(lastSize);
- assertThat(fontStyle.getSizes().size()).isEqualTo(1);
+ assertThat(fontStyleProto.getSizeList()).hasSize(1);
assertThat(fontStyle.getSizes().get(0).getValue()).isEqualTo(lastSize);
}
@@ -283,6 +283,15 @@
}
@Test
+ public void testFontStyleSetSize_allNegativeOrZero_throws() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new LayoutElementBuilders.FontStyle.Builder()
+ .setSizes(sp(-1), sp(0))
+ .build());
+ }
+
+ @Test
public void textSetText_withoutLayoutConstraint_throws() {
assertThrows(
IllegalStateException.class,