Merge "Apply API council feedback" into androidx-main
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataTransformNode.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataTransformNode.java
index f52e536..160d4bc 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataTransformNode.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataTransformNode.java
@@ -17,8 +17,10 @@
package androidx.wear.protolayout.expression.pipeline;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import java.util.function.Function;
+import java.util.function.Predicate;
/**
* Dynamic data node that can perform a transformation from an upstream node. This should be created
@@ -34,8 +36,14 @@
final Function<I, O> mTransformer;
DynamicDataTransformNode(
+ DynamicTypeValueReceiverWithPreUpdate<O> downstream, Function<I, O> transformer) {
+ this(downstream, transformer, /* validator= */ null);
+ }
+
+ DynamicDataTransformNode(
DynamicTypeValueReceiverWithPreUpdate<O> downstream,
- Function<I, O> transformer) {
+ Function<I, O> transformer,
+ @Nullable Predicate<I> validator) {
this.mDownstream = downstream;
this.mTransformer = transformer;
@@ -49,6 +57,10 @@
@Override
public void onData(@NonNull I newData) {
+ if (validator != null && !validator.test(newData)) {
+ mDownstream.onInvalidated();
+ return;
+ }
O result = mTransformer.apply(newData);
mDownstream.onData(result);
}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java
index 3366db4..71ddbd09 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java
@@ -38,8 +38,7 @@
private final DynamicTypeValueReceiverWithPreUpdate<Float> mDownstream;
FixedFloatNode(
- FixedFloat protoNode,
- DynamicTypeValueReceiverWithPreUpdate<Float> downstream) {
+ FixedFloat protoNode, DynamicTypeValueReceiverWithPreUpdate<Float> downstream) {
this.mValue = protoNode.getValue();
this.mDownstream = downstream;
}
@@ -53,7 +52,11 @@
@Override
@UiThread
public void init() {
- mDownstream.onData(mValue);
+ if (Float.isNaN(mValue)) {
+ mDownstream.onInvalidated();
+ } else {
+ mDownstream.onData(mValue);
+ }
}
@Override
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
index 9d3f3dd..d542928 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
@@ -47,8 +47,7 @@
private final DynamicTypeValueReceiverWithPreUpdate<Integer> mDownstream;
FixedInt32Node(
- FixedInt32 protoNode,
- DynamicTypeValueReceiverWithPreUpdate<Integer> downstream) {
+ FixedInt32 protoNode, DynamicTypeValueReceiverWithPreUpdate<Integer> downstream) {
this.mValue = protoNode.getValue();
this.mDownstream = downstream;
}
@@ -202,7 +201,8 @@
default:
throw new IllegalArgumentException("Unknown rounding mode");
}
- });
+ },
+ x -> x - 1 < Integer.MAX_VALUE && x >= Integer.MIN_VALUE);
}
}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluatorTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluatorTest.java
index 5ec9a41..458ab19 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluatorTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluatorTest.java
@@ -20,9 +20,12 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.Shadows.shadowOf;
+
import static java.lang.Integer.MAX_VALUE;
import android.icu.util.ULocale;
+import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool;
@@ -50,6 +53,7 @@
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
@RunWith(ParameterizedRobolectricTestRunner.class)
@@ -83,13 +87,16 @@
test(DynamicInt32.fromState("state_int_15").div(DynamicFloat.constant(2.0f)), 7.5f),
test(DynamicInt32.fromState("state_int_15").rem(DynamicFloat.constant(4.5f)), 1.5f),
test(DynamicFloat.constant(5.0f), 5.0f),
+ testForInvalidValue(DynamicFloat.constant(Float.NaN)),
+ testForInvalidValue(DynamicFloat.constant(Float.NaN).plus(5.0f)),
test(DynamicFloat.fromState("state_float_1.5"), 1.5f),
test(DynamicFloat.constant(1234.567f).asInt(), 1234),
test(DynamicFloat.constant(0.967f).asInt(), 0),
test(DynamicFloat.constant(-1234.967f).asInt(), -1235),
test(DynamicFloat.constant(-0.967f).asInt(), -1),
test(DynamicFloat.constant(Float.MIN_VALUE).asInt(), 0),
- test(DynamicFloat.constant(Float.MAX_VALUE).asInt(), (int) Float.MAX_VALUE),
+ testForInvalidValue(DynamicFloat.constant(Float.MAX_VALUE).asInt()),
+ testForInvalidValue(DynamicFloat.constant(-Float.MAX_VALUE).asInt()),
test(DynamicInt32.constant(100).asFloat(), 100.0f),
test(
DynamicInt32.constant(Integer.MIN_VALUE).asFloat(),
@@ -122,10 +129,8 @@
test(DynamicFloat.constant(0.6f).gte(0.4f), true),
test(DynamicFloat.constant(0.1234568f).gte(0.1234562f), true),
test(DynamicBool.constant(true), true),
- test(DynamicBool.constant(true).isTrue(), true),
- test(DynamicBool.constant(false).isTrue(), false),
- test(DynamicBool.constant(true).isFalse(), false),
- test(DynamicBool.constant(false).isFalse(), true),
+ test(DynamicBool.constant(true).negate(), false),
+ test(DynamicBool.constant(false).negate(), true),
test(DynamicBool.constant(true).and(DynamicBool.constant(true)), true),
test(DynamicBool.constant(true).and(DynamicBool.constant(false)), false),
test(DynamicBool.constant(false).and(DynamicBool.constant(true)), false),
@@ -308,9 +313,10 @@
DynamicInstant bindUnderTest, Instant instant) {
return new DynamicTypeEvaluatorTest.TestCase<>(
bindUnderTest.toDynamicInstantProto().toString(),
- (evaluator, cb) -> {
- evaluator.bind(bindUnderTest, new MainThreadExecutor(), cb).startEvaluation();
- },
+ (evaluator, cb) ->
+ evaluator
+ .bind(bindUnderTest, new MainThreadExecutor(), cb)
+ .startEvaluation(),
instant);
}
@@ -336,6 +342,26 @@
expectedValue);
}
+ private static DynamicTypeEvaluatorTest.TestCase<Integer> testForInvalidValue(
+ DynamicInt32 bindUnderTest) {
+ return new DynamicTypeEvaluatorTest.TestCase<>(
+ bindUnderTest.toDynamicInt32Proto().toString(),
+ (evaluator, cb) ->
+ evaluator
+ .bind(bindUnderTest, new MainThreadExecutor(), cb)
+ .startEvaluation());
+ }
+
+ private static DynamicTypeEvaluatorTest.TestCase<Float> testForInvalidValue(
+ DynamicFloat bindUnderTest) {
+ return new DynamicTypeEvaluatorTest.TestCase<>(
+ bindUnderTest.toDynamicFloatProto().toString(),
+ (evaluator, cb) ->
+ evaluator
+ .bind(bindUnderTest, new MainThreadExecutor(), cb)
+ .startEvaluation());
+ }
+
private static class TestCase<T> {
private final String mName;
private final BiConsumer<DynamicTypeEvaluator, DynamicTypeValueReceiver<T>>
@@ -351,8 +377,18 @@
this.mExpectedValue = expectedValue;
}
+ /** Creates a test case for an expression which expects to result in invalid value. */
+ TestCase(
+ String name,
+ BiConsumer<DynamicTypeEvaluator, DynamicTypeValueReceiver<T>> expressionEvaluator) {
+ this.mName = name;
+ this.mExpressionEvaluator = expressionEvaluator;
+ this.mExpectedValue = null;
+ }
+
public void runTest(DynamicTypeEvaluator evaluator) {
List<T> results = new ArrayList<>();
+ AtomicInteger invalidatedCalls = new AtomicInteger(0);
DynamicTypeValueReceiver<T> callback =
new DynamicTypeValueReceiver<T>() {
@@ -362,16 +398,25 @@
}
@Override
- public void onInvalidated() {}
+ public void onInvalidated() {
+ invalidatedCalls.incrementAndGet();
+ }
};
this.mExpressionEvaluator.accept(evaluator, callback);
+ shadowOf(Looper.getMainLooper()).idle();
- assertThat(results).hasSize(1);
- assertThat(results).containsExactly(mExpectedValue);
+ if (mExpectedValue != null) {
+ // Test expects an actual value.
+ assertThat(results).hasSize(1);
+ assertThat(results).containsExactly(mExpectedValue);
+ } else {
+ // Test expects an invalid value.
+ assertThat(results).isEmpty();
+ assertThat(invalidatedCalls.get()).isEqualTo(1);
+ }
}
- @NonNull
@Override
public String toString() {
return mName + " = " + mExpectedValue;
diff --git a/wear/protolayout/protolayout-expression/api/current.txt b/wear/protolayout/protolayout-expression/api/current.txt
index f66fa06..b526915 100644
--- a/wear/protolayout/protolayout-expression/api/current.txt
+++ b/wear/protolayout/protolayout-expression/api/current.txt
@@ -99,8 +99,7 @@
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool constant(boolean);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool fromByteArray(byte[]);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool fromState(String);
- method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool isFalse();
- method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool isTrue();
+ method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool negate();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool or(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicBoolByteArray();
}
diff --git a/wear/protolayout/protolayout-expression/api/public_plus_experimental_current.txt b/wear/protolayout/protolayout-expression/api/public_plus_experimental_current.txt
index bee2cf9..6bcfb7b 100644
--- a/wear/protolayout/protolayout-expression/api/public_plus_experimental_current.txt
+++ b/wear/protolayout/protolayout-expression/api/public_plus_experimental_current.txt
@@ -99,8 +99,7 @@
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool constant(boolean);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool fromByteArray(byte[]);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool fromState(String);
- method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool isFalse();
- method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool isTrue();
+ method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool negate();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool or(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicBoolByteArray();
}
diff --git a/wear/protolayout/protolayout-expression/api/restricted_current.txt b/wear/protolayout/protolayout-expression/api/restricted_current.txt
index f66fa06..b526915 100644
--- a/wear/protolayout/protolayout-expression/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-expression/api/restricted_current.txt
@@ -99,8 +99,7 @@
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool constant(boolean);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool fromByteArray(byte[]);
method public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool fromState(String);
- method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool isFalse();
- method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool isTrue();
+ method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool negate();
method public default androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool or(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool);
method public default byte[] toDynamicBoolByteArray();
}
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
index 299d7b9..f726c3f 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
@@ -150,7 +150,10 @@
static final int ARITHMETIC_OP_TYPE_MODULO = 5;
/**
- * Rounding mode to use when converting a float to an int32.
+ * Rounding mode to use when converting a float to an int32. If the value is larger than {@link
+ * Integer#MAX_VALUE} or smaller than {@link Integer#MIN_VALUE}, the result of this operation
+ * will be invalid and will have an invalid value delivered via
+ * {@link DynamicTypeValueReceiver<T>#onInvalidate()}.
*
* @since 1.2
*/
@@ -3051,7 +3054,7 @@
/**
* Interface defining a dynamic string type.
*
- * <p> {@link DynamicString} string value is subject to being truncated if it's too long.
+ * <p>{@link DynamicString} string value is subject to being truncated if it's too long.
*
* @since 1.2
*/
@@ -3802,7 +3805,13 @@
return toDynamicFloatProto().toByteArray();
}
- /** Creates a constant-valued {@link DynamicFloat}. */
+ /**
+ * Creates a constant-valued {@link DynamicFloat}.
+ *
+ * <p>If {@code Float.isNan(constant)} is true, the value will be invalid. And any
+ * expression that uses this {@link DynamicFloat} will have an invalid result (which will be
+ * delivered through {@link DynamicTypeValueReceiver<T>#onInvalidate()}.
+ */
@NonNull
static DynamicFloat constant(float constant) {
return new FixedFloat.Builder().setValue(constant).build();
@@ -3907,6 +3916,11 @@
/**
* Returns a {@link DynamicInt32} which holds the largest integer value that is smaller than
* or equal to this {@link DynamicFloat}, i.e. {@code int result = (int) Math.floor(this)}
+ *
+ * <p>If the float value is larger than {@link Integer#MAX_VALUE} or smaller than {@link
+ * Integer#MIN_VALUE}, the result of this operation will be invalid and any expression that
+ * uses the {@link DynamicInt32} will have an invalid result (which will be delivered
+ * through {@link DynamicTypeValueReceiver<T>#onInvalidate()}.
*/
@NonNull
default DynamicInt32 asInt() {
@@ -5245,18 +5259,12 @@
return new StateBoolSource.Builder().setSourceKey(stateKey).build();
}
- /** Returns a {@link DynamicBool} that has the same value as this {@link DynamicBool}. */
- @NonNull
- default DynamicBool isTrue() {
- return this;
- }
-
/**
* Returns a {@link DynamicBool} that has the opposite value of this {@link DynamicBool}.
* i.e. {code result = !this}
*/
@NonNull
- default DynamicBool isFalse() {
+ default DynamicBool negate() {
return new NotBoolOp.Builder().setInput(this).build();
}
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/FixedValueBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/FixedValueBuilders.java
index 8809e61..f9829cd 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/FixedValueBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/FixedValueBuilders.java
@@ -230,7 +230,8 @@
}
/**
- * Gets the value.
+ * Gets the value. Note that a NaN value is considered invalid and any expression with this node
+ * will have an invalid value delivered via {@link DynamicTypeValueReceiver<T>#onInvalidate()}.
*
* @since 1.2
*/
@@ -284,8 +285,11 @@
public Builder() {}
+
/**
- * Sets the value.
+ * Sets the value. Note that a NaN value is considered invalid and any expression with this
+ * node will have an invalid value delivered via
+ * {@link DynamicTypeValueReceiver<T>#onInvalidate()}.
*
* @since 1.2
*/
diff --git a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicBoolTest.java b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicBoolTest.java
index 7c51d3e..b9bf7e3 100644
--- a/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicBoolTest.java
+++ b/wear/protolayout/protolayout-expression/src/test/java/androidx/wear/protolayout/expression/DynamicBoolTest.java
@@ -98,14 +98,13 @@
public void negateOpBool() {
DynamicBool firstBool = DynamicBool.constant(true);
- assertThat(firstBool.isTrue().toDynamicBoolProto()).isEqualTo(firstBool.toDynamicBoolProto());
- assertThat(firstBool.isFalse().toDynamicBoolProto().getNotOp().getInput())
+ assertThat(firstBool.negate().toDynamicBoolProto().getNotOp().getInput())
.isEqualTo(firstBool.toDynamicBoolProto());
}
@Test
public void logicalToString() {
- assertThat(DynamicBool.constant(true).isFalse().toString())
+ assertThat(DynamicBool.constant(true).negate().toString())
.isEqualTo("NotBoolOp{input=FixedBool{value=true}}");
}