blob: 57e7620343c20054ff6ddb4d380eb9dcc6e9b04b [file] [log] [blame]
/*
* Copyright 2022 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 androidx.wear.protolayout.expression.pipeline;
import static java.util.Collections.emptyMap;
import android.icu.util.ULocale;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.annotation.UiThread;
import androidx.wear.protolayout.expression.DynamicBuilders;
import androidx.wear.protolayout.expression.pipeline.BoolNodes.ComparisonFloatNode;
import androidx.wear.protolayout.expression.pipeline.BoolNodes.ComparisonInt32Node;
import androidx.wear.protolayout.expression.pipeline.BoolNodes.FixedBoolNode;
import androidx.wear.protolayout.expression.pipeline.BoolNodes.LogicalBoolOp;
import androidx.wear.protolayout.expression.pipeline.BoolNodes.NotBoolOp;
import androidx.wear.protolayout.expression.pipeline.BoolNodes.StateBoolNode;
import androidx.wear.protolayout.expression.pipeline.ColorNodes.AnimatableFixedColorNode;
import androidx.wear.protolayout.expression.pipeline.ColorNodes.DynamicAnimatedColorNode;
import androidx.wear.protolayout.expression.pipeline.ColorNodes.FixedColorNode;
import androidx.wear.protolayout.expression.pipeline.ColorNodes.StateColorSourceNode;
import androidx.wear.protolayout.expression.pipeline.DurationNodes.BetweenInstancesNode;
import androidx.wear.protolayout.expression.pipeline.FloatNodes.AnimatableFixedFloatNode;
import androidx.wear.protolayout.expression.pipeline.FloatNodes.ArithmeticFloatNode;
import androidx.wear.protolayout.expression.pipeline.FloatNodes.DynamicAnimatedFloatNode;
import androidx.wear.protolayout.expression.pipeline.FloatNodes.FixedFloatNode;
import androidx.wear.protolayout.expression.pipeline.FloatNodes.Int32ToFloatNode;
import androidx.wear.protolayout.expression.pipeline.FloatNodes.StateFloatSourceNode;
import androidx.wear.protolayout.expression.pipeline.InstantNodes.FixedInstantNode;
import androidx.wear.protolayout.expression.pipeline.InstantNodes.PlatformTimeSourceNode;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.AnimatableFixedInt32Node;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.ArithmeticInt32Node;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.DynamicAnimatedInt32Node;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.FixedInt32Node;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.FloatToInt32Node;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.GetDurationPartOpNode;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.PlatformInt32SourceNode;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.StateInt32SourceNode;
import androidx.wear.protolayout.expression.pipeline.StringNodes.FixedStringNode;
import androidx.wear.protolayout.expression.pipeline.StringNodes.FloatFormatNode;
import androidx.wear.protolayout.expression.pipeline.StringNodes.Int32FormatNode;
import androidx.wear.protolayout.expression.pipeline.StringNodes.StateStringNode;
import androidx.wear.protolayout.expression.pipeline.StringNodes.StringConcatOpNode;
import androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway;
import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableDynamicColor;
import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableDynamicFloat;
import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableDynamicInt32;
import androidx.wear.protolayout.expression.proto.DynamicProto.ConditionalColorOp;
import androidx.wear.protolayout.expression.proto.DynamicProto.ConditionalFloatOp;
import androidx.wear.protolayout.expression.proto.DynamicProto.ConditionalInt32Op;
import androidx.wear.protolayout.expression.proto.DynamicProto.ConditionalStringOp;
import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicBool;
import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicColor;
import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicDuration;
import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicFloat;
import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicInstant;
import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicInt32;
import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicString;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Evaluates protolayout dynamic types.
*
* <p>Given a dynamic ProtoLayout data source, this builds up a sequence of {@link DynamicDataNode}
* instances, which can source the required data, and transform it into its final form.
*
* <p>Data source can include animations which will then emit value transitions.
*
* <p>In order to evaluate dynamic types, the caller needs to add any number of pending dynamic
* types with {@link #bind} methods and then call {@link BoundDynamicType#startEvaluation()} on each
* of them to start evaluation. Starting evaluation can be done for batches of dynamic types.
*
* <p>It's the callers responsibility to destroy those dynamic types after use, with {@link
* BoundDynamicType#close()}.
*
* <p>It's the callers responsibility to destroy those dynamic types after use, with {@link
* BoundDynamicType#close()}.
*/
public class DynamicTypeEvaluator implements AutoCloseable {
private static final String TAG = "DynamicTypeEvaluator";
@NonNull
private static final QuotaManager DISABLED_ANIMATIONS_QUOTA_MANAGER =
new QuotaManager() {
@Override
public boolean tryAcquireQuota(int quota) {
return false;
}
@Override
public void releaseQuota(int quota) {
throw new IllegalStateException(
"releaseQuota method is called when no quota is acquired!");
}
};
@NonNull private static final StateStore EMPTY_STATE_STORE = new StateStore(emptyMap());
@NonNull private final Config mConfig;
@NonNull private final StateStore mStateStore;
@NonNull private final QuotaManager mAnimationQuotaManager;
@NonNull private final TimeGateway mTimeGateway;
@Nullable private final EpochTimePlatformDataSource mTimeDataSource;
@Nullable private final SensorGatewayPlatformDataSource mSensorGatewayDataSource;
/** Configuration for {@link #DynamicTypeEvaluator(Config)}. */
public static final class Config {
private final boolean mPlatformDataSourcesInitiallyEnabled;
@Nullable private final StateStore mStateStore;
@Nullable private final QuotaManager mAnimationQuotaManager;
@Nullable private final TimeGateway mTimeGateway;
@Nullable private final SensorGateway mSensorGateway;
Config(
boolean platformDataSourcesInitiallyEnabled,
@Nullable StateStore stateStore,
@Nullable QuotaManager animationQuotaManager,
@Nullable TimeGateway timeGateway,
@Nullable SensorGateway sensorGateway) {
this.mPlatformDataSourcesInitiallyEnabled = platformDataSourcesInitiallyEnabled;
this.mStateStore = stateStore;
this.mAnimationQuotaManager = animationQuotaManager;
this.mTimeGateway = timeGateway;
this.mSensorGateway = sensorGateway;
}
/** Builds a {@link DynamicTypeEvaluator.Config}. */
public static final class Builder {
private boolean mPlatformDataSourcesInitiallyEnabled = false;
@Nullable private StateStore mStateStore = null;
@Nullable private QuotaManager mAnimationQuotaManager = null;
@Nullable private TimeGateway mTimeGateway = null;
@Nullable private SensorGateway mSensorGateway = null;
/**
* Sets whether sending updates from sensor and time sources should be allowed
* initially. After that, enabling updates from sensor and time sources can be done via
* {@link #enablePlatformDataSources()} or {@link #disablePlatformDataSources()}.
*
* <p>Defaults to {@code false}.
*/
@NonNull
public Builder setPlatformDataSourcesInitiallyEnabled(boolean value) {
mPlatformDataSourcesInitiallyEnabled = value;
return this;
}
/**
* Sets the state store that will be used for dereferencing the state keys in the
* dynamic types.
*
* <p>If not set, it's the equivalent of setting an empty state store (state bindings
* will trigger {@link DynamicTypeValueReceiver#onInvalidated()}).
*/
@NonNull
public Builder setStateStore(@NonNull StateStore value) {
mStateStore = value;
return this;
}
/**
* Sets the quota manager used for limiting the number of concurrently running
* animations.
*
* <p>If not set, animations are disabled and non-infinite animations will have the end
* value immediately.
*/
@NonNull
public Builder setAnimationQuotaManager(@NonNull QuotaManager value) {
mAnimationQuotaManager = value;
return this;
}
/**
* Sets the gateway used for time data.
*
* <p>If not set, a default 1hz {@link TimeGateway} implementation that utilizes a
* main-thread {@code Handler} to trigger is used.
*/
@NonNull
public Builder setTimeGateway(@NonNull TimeGateway value) {
mTimeGateway = value;
return this;
}
/**
* Sets the gateway used for sensor data.
*
* <p>If not set, sensor data will not be available (sensor bindings will trigger {@link
* DynamicTypeValueReceiver#onInvalidated()}).
*/
@NonNull
public Builder setSensorGateway(@NonNull SensorGateway value) {
mSensorGateway = value;
return this;
}
@NonNull
public Config build() {
return new Config(
mPlatformDataSourcesInitiallyEnabled,
mStateStore,
mAnimationQuotaManager,
mTimeGateway,
mSensorGateway);
}
}
/**
* Gets whether sending updates from sensor and time sources should be allowed initially.
* After that, enabling updates from sensor and time sources can be done via {@link
* #enablePlatformDataSources()} or {@link #disablePlatformDataSources()}.
*/
public boolean isPlatformDataSourcesInitiallyEnabled() {
return mPlatformDataSourcesInitiallyEnabled;
}
/**
* Gets the state store that will be used for dereferencing the state keys in the dynamic
* types, or {@code null} which is equivalent to an empty state store (state bindings will
* trigger {@link DynamicTypeValueReceiver#onInvalidated()}).
*/
@Nullable
public StateStore getStateStore() {
return mStateStore;
}
/**
* Gets the quota manager used for limiting the number of concurrently running animations,
* or {@code null} if animations are disabled, causing non-infinite animations to have to
* the end value immediately.
*/
@Nullable
public QuotaManager getAnimationQuotaManager() {
return mAnimationQuotaManager;
}
/**
* Gets the gateway used for sensor data, or {@code null} if sensor data is unavailable
* (sensor bindings will trigger {@link DynamicTypeValueReceiver#onInvalidated()}).
*/
@Nullable
public SensorGateway getSensorGateway() {
return mSensorGateway;
}
/**
* Gets the gateway used for time data, or {@code null} if a default 1hz {@link TimeGateway}
* that utilizes a main-thread {@code Handler} to trigger is used.
*/
@Nullable
public TimeGateway getTimeGateway() {
return mTimeGateway;
}
}
/** Constructs a {@link DynamicTypeEvaluator}. */
public DynamicTypeEvaluator(@NonNull Config config) {
this.mConfig = config;
this.mStateStore =
config.getStateStore() != null ? config.getStateStore() : EMPTY_STATE_STORE;
this.mAnimationQuotaManager =
config.getAnimationQuotaManager() != null
? config.getAnimationQuotaManager()
: DISABLED_ANIMATIONS_QUOTA_MANAGER;
Handler uiHandler = new Handler(Looper.getMainLooper());
MainThreadExecutor uiExecutor = new MainThreadExecutor(uiHandler);
this.mTimeGateway =
config.getTimeGateway() != null
? config.getTimeGateway()
: new TimeGatewayImpl(uiHandler);
this.mTimeDataSource = new EpochTimePlatformDataSource(uiExecutor, mTimeGateway);
if (config.isPlatformDataSourcesInitiallyEnabled()
&& this.mTimeGateway instanceof TimeGatewayImpl) {
((TimeGatewayImpl) this.mTimeGateway).enableUpdates();
}
if (config.getSensorGateway() != null) {
if (config.isPlatformDataSourcesInitiallyEnabled()) {
config.getSensorGateway().enableUpdates();
} else {
config.getSensorGateway().disableUpdates();
}
this.mSensorGatewayDataSource =
new SensorGatewayPlatformDataSource(uiExecutor, config.getSensorGateway());
} else {
this.mSensorGatewayDataSource = null;
}
}
/**
* Adds dynamic type from the given {@link DynamicBuilders.DynamicString} for evaluation.
*
* <p>Evaluation of this dynamic type will start when {@link BoundDynamicType#startEvaluation()}
* is called on the returned object.
*
* <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
* the given {@link Executor}.
*
* @param stringSource The given String dynamic type that should be evaluated.
* @param locale The locale used for the given String source.
* @param executor The Executor to run the consumer on.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
*/
@NonNull
public BoundDynamicType bind(
@NonNull DynamicBuilders.DynamicString stringSource,
@NonNull ULocale locale,
@NonNull Executor executor,
@NonNull DynamicTypeValueReceiver<String> consumer) {
return bind(
stringSource.toDynamicStringProto(),
locale,
new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
}
/**
* Adds pending dynamic type from the given {@link DynamicString} for future evaluation.
*
* <p>Evaluation of this dynamic type will start when {@link BoundDynamicType#startEvaluation()}
* is called on the returned object.
*
* @param stringSource The given String dynamic type that should be evaluated.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
* @param locale The locale used for the given String source.
*/
@NonNull
@RestrictTo(Scope.LIBRARY_GROUP)
public BoundDynamicType bind(
@NonNull DynamicString stringSource,
@NonNull ULocale locale,
@NonNull DynamicTypeValueReceiver<String> consumer) {
List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
bindRecursively(
stringSource,
new DynamicTypeValueReceiverOnExecutor<>(consumer),
locale,
resultBuilder);
return new BoundDynamicTypeImpl(resultBuilder);
}
/**
* Adds dynamic type from the given {@link DynamicBuilders.DynamicInt32} for evaluation.
*
* <p>Evaluation of this dynamic type will start when {@link BoundDynamicType#startEvaluation()}
* is called on the returned object.
*
* <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
* the given {@link Executor}.
*
* @param int32Source The given integer dynamic type that should be evaluated.
* @param executor The Executor to run the consumer on.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
*/
@NonNull
public BoundDynamicType bind(
@NonNull DynamicBuilders.DynamicInt32 int32Source,
@NonNull Executor executor,
@NonNull DynamicTypeValueReceiver<Integer> consumer) {
return bind(
int32Source.toDynamicInt32Proto(),
new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
}
/**
* Adds pending dynamic type from the given {@link DynamicInt32} for future evaluation.
*
* <p>Evaluation of this dynamic type will start when {@link BoundDynamicType#startEvaluation()}
* is called on the returned object.
*
* @param int32Source The given integer dynamic type that should be evaluated.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
*/
@NonNull
@RestrictTo(Scope.LIBRARY_GROUP)
public BoundDynamicType bind(
@NonNull DynamicInt32 int32Source,
@NonNull DynamicTypeValueReceiver<Integer> consumer) {
List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
bindRecursively(
int32Source, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder);
return new BoundDynamicTypeImpl(resultBuilder);
}
/**
* Adds dynamic type from the given {@link DynamicBuilders.DynamicFloat} for evaluation.
*
* <p>Evaluation of this dynamic type will start when {@link BoundDynamicType#startEvaluation()}
* is called on the returned object.
*
* <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
* the given {@link Executor}.
*
* @param floatSource The given float dynamic type that should be evaluated.
* @param executor The Executor to run the consumer on.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
*/
@NonNull
public BoundDynamicType bind(
@NonNull DynamicBuilders.DynamicFloat floatSource,
@NonNull Executor executor,
@NonNull DynamicTypeValueReceiver<Float> consumer) {
return bind(
floatSource.toDynamicFloatProto(),
new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
}
/**
* Adds pending dynamic type from the given {@link DynamicFloat} for future evaluation.
*
* <p>Evaluation of this dynamic type will start when {@link BoundDynamicType#startEvaluation()}
* is called on the returned object.
*
* @param floatSource The given float dynamic type that should be evaluated.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
*/
@NonNull
@RestrictTo(Scope.LIBRARY_GROUP)
public BoundDynamicType bind(
@NonNull DynamicFloat floatSource, @NonNull DynamicTypeValueReceiver<Float> consumer) {
List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
bindRecursively(
floatSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder);
return new BoundDynamicTypeImpl(resultBuilder);
}
/**
* Adds dynamic type from the given {@link DynamicBuilders.DynamicColor} for evaluation.
*
* <p>Evaluation of this dynamic type will start when {@link BoundDynamicType#startEvaluation()}
* is called on the returned object.
*
* <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
* the given {@link Executor}.
*
* @param colorSource The given color dynamic type that should be evaluated.
* @param executor The Executor to run the consumer on.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
*/
@NonNull
public BoundDynamicType bind(
@NonNull DynamicBuilders.DynamicColor colorSource,
@NonNull Executor executor,
@NonNull DynamicTypeValueReceiver<Integer> consumer) {
return bind(
colorSource.toDynamicColorProto(),
new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
}
/**
* Adds pending dynamic type from the given {@link DynamicColor} for future evaluation.
*
* <p>Evaluation of this dynamic type will start when {@link BoundDynamicType#startEvaluation()}
* is called on the returned object.
*
* @param colorSource The given color dynamic type that should be evaluated.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
*/
@NonNull
@RestrictTo(Scope.LIBRARY_GROUP)
public BoundDynamicType bind(
@NonNull DynamicColor colorSource,
@NonNull DynamicTypeValueReceiver<Integer> consumer) {
List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
bindRecursively(
colorSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder);
return new BoundDynamicTypeImpl(resultBuilder);
}
/**
* Adds dynamic type from the given {@link DynamicBuilders.DynamicDuration} for evaluation.
*
* <p>Evaluation of this dynamic type will start when {@link BoundDynamicType#startEvaluation()}
* is called on the returned object.
*
* <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
* the given {@link Executor}.
*
* @param durationSource The given duration dynamic type that should be evaluated.
* @param executor The Executor to run the consumer on.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
*/
@NonNull
public BoundDynamicType bind(
@NonNull DynamicBuilders.DynamicDuration durationSource,
@NonNull Executor executor,
@NonNull DynamicTypeValueReceiver<Duration> consumer) {
return bind(
durationSource.toDynamicDurationProto(),
new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
}
/**
* Adds pending dynamic type from the given {@link DynamicDuration} for future evaluation.
*
* <p>Evaluation of this dynamic type will start when {@link BoundDynamicType#startEvaluation()}
* is called on the returned object.
*
* @param durationSource The given durations dynamic type that should be evaluated.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
*/
@NonNull
@RestrictTo(Scope.LIBRARY_GROUP)
public BoundDynamicType bind(
@NonNull DynamicDuration durationSource,
@NonNull DynamicTypeValueReceiver<Duration> consumer) {
List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
bindRecursively(
durationSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder);
return new BoundDynamicTypeImpl(resultBuilder);
}
/**
* Adds dynamic type from the given {@link DynamicBuilders.DynamicInstant} for evaluation.
*
* <p>Evaluation of this dynamic type will start when {@link BoundDynamicType#startEvaluation()}
* is called on the returned object.
*
* <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
* the given {@link Executor}.
*
* @param instantSource The given instant dynamic type that should be evaluated.
* @param executor The Executor to run the consumer on.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
*/
@NonNull
public BoundDynamicType bind(
@NonNull DynamicBuilders.DynamicInstant instantSource,
@NonNull Executor executor,
@NonNull DynamicTypeValueReceiver<Instant> consumer) {
return bind(
instantSource.toDynamicInstantProto(),
new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
}
/**
* Adds pending dynamic type from the given {@link DynamicInstant} for future evaluation.
*
* <p>Evaluation of this dynamic type will start when {@link BoundDynamicType#startEvaluation()}
* is called on the returned object.
*
* @param instantSource The given instant dynamic type that should be evaluated.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
*/
@NonNull
@RestrictTo(Scope.LIBRARY_GROUP)
public BoundDynamicType bind(
@NonNull DynamicInstant instantSource,
@NonNull DynamicTypeValueReceiver<Instant> consumer) {
List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
bindRecursively(
instantSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder);
return new BoundDynamicTypeImpl(resultBuilder);
}
/**
* Adds dynamic type from the given {@link DynamicBuilders.DynamicBool} for evaluation.
* Evaluation will start immediately.
*
* <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
* the given {@link Executor}.
*
* @param boolSource The given boolean dynamic type that should be evaluated.
* @param executor The Executor to run the consumer on.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
*/
@NonNull
public BoundDynamicType bind(
@NonNull DynamicBuilders.DynamicBool boolSource,
@NonNull Executor executor,
@NonNull DynamicTypeValueReceiver<Boolean> consumer) {
return bind(
boolSource.toDynamicBoolProto(),
new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
}
/**
* Adds pending dynamic type from the given {@link DynamicBool} for future evaluation.
*
* <p>Evaluation of this dynamic type will start when {@link BoundDynamicType#startEvaluation()}
* is called on the returned object.
*
* @param boolSource The given boolean dynamic type that should be evaluated.
* @param consumer The registered consumer for results of the evaluation. It will be called from
* UI thread.
*/
@NonNull
@RestrictTo(Scope.LIBRARY_GROUP)
public BoundDynamicType bind(
@NonNull DynamicBool boolSource, @NonNull DynamicTypeValueReceiver<Boolean> consumer) {
List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
bindRecursively(
boolSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder);
return new BoundDynamicTypeImpl(resultBuilder);
}
/**
* Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link
* DynamicDataNode} produced by evaluating given dynamic type are added to the given list.
*/
private void bindRecursively(
@NonNull DynamicString stringSource,
@NonNull DynamicTypeValueReceiverWithPreUpdate<String> consumer,
@NonNull ULocale locale,
@NonNull List<DynamicDataNode<?>> resultBuilder) {
DynamicDataNode<?> node;
switch (stringSource.getInnerCase()) {
case FIXED:
node = new FixedStringNode(stringSource.getFixed(), consumer);
break;
case INT32_FORMAT_OP:
{
NumberFormatter formatter =
new NumberFormatter(stringSource.getInt32FormatOp(), locale);
Int32FormatNode int32FormatNode = new Int32FormatNode(formatter, consumer);
node = int32FormatNode;
bindRecursively(
stringSource.getInt32FormatOp().getInput(),
int32FormatNode.getIncomingCallback(),
resultBuilder);
break;
}
case FLOAT_FORMAT_OP:
{
NumberFormatter formatter =
new NumberFormatter(stringSource.getFloatFormatOp(), locale);
FloatFormatNode floatFormatNode = new FloatFormatNode(formatter, consumer);
node = floatFormatNode;
bindRecursively(
stringSource.getFloatFormatOp().getInput(),
floatFormatNode.getIncomingCallback(),
resultBuilder);
break;
}
case STATE_SOURCE:
{
node =
new StateStringNode(
mStateStore, stringSource.getStateSource(), consumer);
break;
}
case CONDITIONAL_OP:
{
ConditionalOpNode<String> conditionalNode = new ConditionalOpNode<>(consumer);
ConditionalStringOp op = stringSource.getConditionalOp();
bindRecursively(
op.getCondition(),
conditionalNode.getConditionIncomingCallback(),
resultBuilder);
bindRecursively(
op.getValueIfTrue(),
conditionalNode.getTrueValueIncomingCallback(),
locale,
resultBuilder);
bindRecursively(
op.getValueIfFalse(),
conditionalNode.getFalseValueIncomingCallback(),
locale,
resultBuilder);
node = conditionalNode;
break;
}
case CONCAT_OP:
{
StringConcatOpNode concatNode = new StringConcatOpNode(consumer);
node = concatNode;
bindRecursively(
stringSource.getConcatOp().getInputLhs(),
concatNode.getLhsIncomingCallback(),
locale,
resultBuilder);
bindRecursively(
stringSource.getConcatOp().getInputRhs(),
concatNode.getRhsIncomingCallback(),
locale,
resultBuilder);
break;
}
case INNER_NOT_SET:
throw new IllegalArgumentException("DynamicString has no inner source set");
default:
throw new IllegalArgumentException("Unknown DynamicString source type");
}
resultBuilder.add(node);
}
/**
* Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link
* DynamicDataNode} produced by evaluating given dynamic type are added to the given list.
*/
private void bindRecursively(
@NonNull DynamicInt32 int32Source,
@NonNull DynamicTypeValueReceiverWithPreUpdate<Integer> consumer,
@NonNull List<DynamicDataNode<?>> resultBuilder) {
DynamicDataNode<Integer> node;
switch (int32Source.getInnerCase()) {
case FIXED:
node = new FixedInt32Node(int32Source.getFixed(), consumer);
break;
case PLATFORM_SOURCE:
node =
new PlatformInt32SourceNode(
int32Source.getPlatformSource(),
mSensorGatewayDataSource,
consumer);
break;
case ARITHMETIC_OPERATION:
{
ArithmeticInt32Node arithmeticNode =
new ArithmeticInt32Node(int32Source.getArithmeticOperation(), consumer);
node = arithmeticNode;
bindRecursively(
int32Source.getArithmeticOperation().getInputLhs(),
arithmeticNode.getLhsIncomingCallback(),
resultBuilder);
bindRecursively(
int32Source.getArithmeticOperation().getInputRhs(),
arithmeticNode.getRhsIncomingCallback(),
resultBuilder);
break;
}
case STATE_SOURCE:
{
node =
new StateInt32SourceNode(
mStateStore, int32Source.getStateSource(), consumer);
break;
}
case CONDITIONAL_OP:
{
ConditionalOpNode<Integer> conditionalNode = new ConditionalOpNode<>(consumer);
ConditionalInt32Op op = int32Source.getConditionalOp();
bindRecursively(
op.getCondition(),
conditionalNode.getConditionIncomingCallback(),
resultBuilder);
bindRecursively(
op.getValueIfTrue(),
conditionalNode.getTrueValueIncomingCallback(),
resultBuilder);
bindRecursively(
op.getValueIfFalse(),
conditionalNode.getFalseValueIncomingCallback(),
resultBuilder);
node = conditionalNode;
break;
}
case FLOAT_TO_INT:
{
FloatToInt32Node conversionNode =
new FloatToInt32Node(int32Source.getFloatToInt(), consumer);
node = conversionNode;
bindRecursively(
int32Source.getFloatToInt().getInput(),
conversionNode.getIncomingCallback(),
resultBuilder);
break;
}
case DURATION_PART:
{
GetDurationPartOpNode durationPartOpNode =
new GetDurationPartOpNode(int32Source.getDurationPart(), consumer);
node = durationPartOpNode;
bindRecursively(
int32Source.getDurationPart().getInput(),
durationPartOpNode.getIncomingCallback(),
resultBuilder);
break;
}
case ANIMATABLE_FIXED:
// We don't have to check if enableAnimations is true, because if it's false and
// we didn't have static value set, constructor has put QuotaManager that don't
// have any quota, so animations won't be played and they would jump to the end
// value.
node =
new AnimatableFixedInt32Node(
int32Source.getAnimatableFixed(), consumer, mAnimationQuotaManager);
break;
case ANIMATABLE_DYNAMIC:
// We don't have to check if enableAnimations is true, because if it's false and
// we didn't have static value set, constructor has put QuotaManager that don't
// have any quota, so animations won't be played and they would jump to the end
// value.
AnimatableDynamicInt32 dynamicNode = int32Source.getAnimatableDynamic();
DynamicAnimatedInt32Node animationNode =
new DynamicAnimatedInt32Node(
consumer, dynamicNode.getAnimationSpec(), mAnimationQuotaManager);
node = animationNode;
bindRecursively(
dynamicNode.getInput(), animationNode.getInputCallback(), resultBuilder);
break;
case INNER_NOT_SET:
throw new IllegalArgumentException("DynamicInt32 has no inner source set");
default:
throw new IllegalArgumentException("Unknown DynamicInt32 source type");
}
resultBuilder.add(node);
}
/**
* Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link
* DynamicDataNode} produced by evaluating given dynamic type are added to the given list.
*/
private void bindRecursively(
@NonNull DynamicDuration durationSource,
@NonNull DynamicTypeValueReceiverWithPreUpdate<Duration> consumer,
@NonNull List<DynamicDataNode<?>> resultBuilder) {
DynamicDataNode<?> node;
switch (durationSource.getInnerCase()) {
case BETWEEN:
BetweenInstancesNode betweenInstancesNode = new BetweenInstancesNode(consumer);
node = betweenInstancesNode;
bindRecursively(
durationSource.getBetween().getStartInclusive(),
betweenInstancesNode.getLhsIncomingCallback(),
resultBuilder);
bindRecursively(
durationSource.getBetween().getEndExclusive(),
betweenInstancesNode.getRhsIncomingCallback(),
resultBuilder);
break;
case INNER_NOT_SET:
throw new IllegalArgumentException("DynamicDuration has no inner source set");
default:
throw new IllegalArgumentException("Unknown DynamicDuration source type");
}
resultBuilder.add(node);
}
/**
* Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link
* DynamicDataNode} produced by evaluating given dynamic type are added to the given list.
*/
private void bindRecursively(
@NonNull DynamicInstant instantSource,
@NonNull DynamicTypeValueReceiverWithPreUpdate<Instant> consumer,
@NonNull List<DynamicDataNode<?>> resultBuilder) {
DynamicDataNode<?> node;
switch (instantSource.getInnerCase()) {
case FIXED:
node = new FixedInstantNode(instantSource.getFixed(), consumer);
break;
case PLATFORM_SOURCE:
node = new PlatformTimeSourceNode(mTimeDataSource, consumer);
break;
case INNER_NOT_SET:
throw new IllegalArgumentException("DynamicInstant has no inner source set");
default:
throw new IllegalArgumentException("Unknown DynamicInstant source type");
}
resultBuilder.add(node);
}
/**
* Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link
* DynamicDataNode} produced by evaluating given dynamic type are added to the given list.
*/
private void bindRecursively(
@NonNull DynamicFloat floatSource,
@NonNull DynamicTypeValueReceiverWithPreUpdate<Float> consumer,
@NonNull List<DynamicDataNode<?>> resultBuilder) {
DynamicDataNode<?> node;
switch (floatSource.getInnerCase()) {
case FIXED:
node = new FixedFloatNode(floatSource.getFixed(), consumer);
break;
case STATE_SOURCE:
node =
new StateFloatSourceNode(
mStateStore, floatSource.getStateSource(), consumer);
break;
case ARITHMETIC_OPERATION:
{
ArithmeticFloatNode arithmeticNode =
new ArithmeticFloatNode(floatSource.getArithmeticOperation(), consumer);
node = arithmeticNode;
bindRecursively(
floatSource.getArithmeticOperation().getInputLhs(),
arithmeticNode.getLhsIncomingCallback(),
resultBuilder);
bindRecursively(
floatSource.getArithmeticOperation().getInputRhs(),
arithmeticNode.getRhsIncomingCallback(),
resultBuilder);
break;
}
case INT32_TO_FLOAT_OPERATION:
{
Int32ToFloatNode toFloatNode = new Int32ToFloatNode(consumer);
node = toFloatNode;
bindRecursively(
floatSource.getInt32ToFloatOperation().getInput(),
toFloatNode.getIncomingCallback(),
resultBuilder);
break;
}
case CONDITIONAL_OP:
{
ConditionalOpNode<Float> conditionalNode = new ConditionalOpNode<>(consumer);
ConditionalFloatOp op = floatSource.getConditionalOp();
bindRecursively(
op.getCondition(),
conditionalNode.getConditionIncomingCallback(),
resultBuilder);
bindRecursively(
op.getValueIfTrue(),
conditionalNode.getTrueValueIncomingCallback(),
resultBuilder);
bindRecursively(
op.getValueIfFalse(),
conditionalNode.getFalseValueIncomingCallback(),
resultBuilder);
node = conditionalNode;
break;
}
case ANIMATABLE_FIXED:
// We don't have to check if enableAnimations is true, because if it's false and
// we didn't have static value set, constructor has put QuotaManager that don't
// have any quota, so animations won't be played and they would jump to the end
// value.
node =
new AnimatableFixedFloatNode(
floatSource.getAnimatableFixed(), consumer, mAnimationQuotaManager);
break;
case ANIMATABLE_DYNAMIC:
// We don't have to check if enableAnimations is true, because if it's false and
// we didn't have static value set, constructor has put QuotaManager that don't
// have any quota, so animations won't be played and they would jump to the end
// value.
AnimatableDynamicFloat dynamicNode = floatSource.getAnimatableDynamic();
DynamicAnimatedFloatNode animationNode =
new DynamicAnimatedFloatNode(
consumer, dynamicNode.getAnimationSpec(), mAnimationQuotaManager);
node = animationNode;
bindRecursively(
dynamicNode.getInput(), animationNode.getInputCallback(), resultBuilder);
break;
case INNER_NOT_SET:
throw new IllegalArgumentException("DynamicFloat has no inner source set");
default:
throw new IllegalArgumentException("Unknown DynamicFloat source type");
}
resultBuilder.add(node);
}
/**
* Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link
* DynamicDataNode} produced by evaluating given dynamic type are added to the given list.
*/
private void bindRecursively(
@NonNull DynamicColor colorSource,
@NonNull DynamicTypeValueReceiverWithPreUpdate<Integer> consumer,
@NonNull List<DynamicDataNode<?>> resultBuilder) {
DynamicDataNode<?> node;
switch (colorSource.getInnerCase()) {
case FIXED:
node = new FixedColorNode(colorSource.getFixed(), consumer);
break;
case STATE_SOURCE:
node =
new StateColorSourceNode(
mStateStore, colorSource.getStateSource(), consumer);
break;
case ANIMATABLE_FIXED:
// We don't have to check if enableAnimations is true, because if it's false and
// we didn't have static value set, constructor has put QuotaManager that don't
// have any quota, so animations won't be played and they would jump to the end
// value.
node =
new AnimatableFixedColorNode(
colorSource.getAnimatableFixed(), consumer, mAnimationQuotaManager);
break;
case ANIMATABLE_DYNAMIC:
// We don't have to check if enableAnimations is true, because if it's false and
// we didn't have static value set, constructor has put QuotaManager that don't
// have any quota, so animations won't be played and they would jump to the end
// value.
AnimatableDynamicColor dynamicNode = colorSource.getAnimatableDynamic();
DynamicAnimatedColorNode animationNode =
new DynamicAnimatedColorNode(
consumer, dynamicNode.getAnimationSpec(), mAnimationQuotaManager);
node = animationNode;
bindRecursively(
dynamicNode.getInput(), animationNode.getInputCallback(), resultBuilder);
break;
case CONDITIONAL_OP:
ConditionalOpNode<Integer> conditionalNode = new ConditionalOpNode<>(consumer);
ConditionalColorOp op = colorSource.getConditionalOp();
bindRecursively(
op.getCondition(),
conditionalNode.getConditionIncomingCallback(),
resultBuilder);
bindRecursively(
op.getValueIfTrue(),
conditionalNode.getTrueValueIncomingCallback(),
resultBuilder);
bindRecursively(
op.getValueIfFalse(),
conditionalNode.getFalseValueIncomingCallback(),
resultBuilder);
node = conditionalNode;
break;
case INNER_NOT_SET:
throw new IllegalArgumentException("DynamicColor has no inner source set");
default:
throw new IllegalArgumentException("Unknown DynamicColor source type");
}
resultBuilder.add(node);
}
/**
* Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link
* DynamicDataNode} produced by evaluating given dynamic type are added to the given list.
*/
private void bindRecursively(
@NonNull DynamicBool boolSource,
@NonNull DynamicTypeValueReceiverWithPreUpdate<Boolean> consumer,
@NonNull List<DynamicDataNode<?>> resultBuilder) {
DynamicDataNode<?> node;
switch (boolSource.getInnerCase()) {
case FIXED:
node = new FixedBoolNode(boolSource.getFixed(), consumer);
break;
case STATE_SOURCE:
node = new StateBoolNode(mStateStore, boolSource.getStateSource(), consumer);
break;
case INT32_COMPARISON:
{
ComparisonInt32Node compNode =
new ComparisonInt32Node(boolSource.getInt32Comparison(), consumer);
node = compNode;
bindRecursively(
boolSource.getInt32Comparison().getInputLhs(),
compNode.getLhsIncomingCallback(),
resultBuilder);
bindRecursively(
boolSource.getInt32Comparison().getInputRhs(),
compNode.getRhsIncomingCallback(),
resultBuilder);
break;
}
case LOGICAL_OP:
{
LogicalBoolOp logicalNode =
new LogicalBoolOp(boolSource.getLogicalOp(), consumer);
node = logicalNode;
bindRecursively(
boolSource.getLogicalOp().getInputLhs(),
logicalNode.getLhsIncomingCallback(),
resultBuilder);
bindRecursively(
boolSource.getLogicalOp().getInputRhs(),
logicalNode.getRhsIncomingCallback(),
resultBuilder);
break;
}
case NOT_OP:
{
NotBoolOp notNode = new NotBoolOp(consumer);
node = notNode;
bindRecursively(
boolSource.getNotOp().getInput(),
notNode.getIncomingCallback(),
resultBuilder);
break;
}
case FLOAT_COMPARISON:
{
ComparisonFloatNode compNode =
new ComparisonFloatNode(boolSource.getFloatComparison(), consumer);
node = compNode;
bindRecursively(
boolSource.getFloatComparison().getInputLhs(),
compNode.getLhsIncomingCallback(),
resultBuilder);
bindRecursively(
boolSource.getFloatComparison().getInputRhs(),
compNode.getRhsIncomingCallback(),
resultBuilder);
break;
}
case INNER_NOT_SET:
throw new IllegalArgumentException("DynamicBool has no inner source set");
default:
throw new IllegalArgumentException("Unknown DynamicBool source type");
}
resultBuilder.add(node);
}
/** Enables sending updates on sensor and time. */
@UiThread
public void enablePlatformDataSources() {
if (this.mTimeGateway instanceof TimeGatewayImpl) {
((TimeGatewayImpl) mTimeGateway).enableUpdates();
}
if (mConfig.getSensorGateway() != null) {
mConfig.getSensorGateway().enableUpdates();
}
}
/** Disables sending updates on sensor and time. */
@UiThread
public void disablePlatformDataSources() {
if (this.mTimeGateway instanceof TimeGatewayImpl) {
((TimeGatewayImpl) mTimeGateway).disableUpdates();
}
if (mConfig.getSensorGateway() != null) {
mConfig.getSensorGateway().disableUpdates();
}
}
/**
* Closes resources owned by this {@link DynamicTypeEvaluator}.
*
* <p>This will not close provided resources, like the {@link TimeGateway} or {@link
* SensorGateway}.
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@Override
public void close() {
if (mTimeGateway instanceof TimeGatewayImpl) {
try {
((TimeGatewayImpl) mTimeGateway).close();
} catch (RuntimeException ex) {
Log.e(TAG, "Error while cleaning up time gateway", ex);
}
}
}
/**
* Wraps {@link DynamicTypeValueReceiver} and executes its methods on the given {@link
* Executor}.
*/
private static class DynamicTypeValueReceiverOnExecutor<T>
implements DynamicTypeValueReceiverWithPreUpdate<T> {
@NonNull private final Executor mExecutor;
@NonNull private final DynamicTypeValueReceiver<T> mConsumer;
DynamicTypeValueReceiverOnExecutor(@NonNull DynamicTypeValueReceiver<T> consumer) {
this(Runnable::run, consumer);
}
DynamicTypeValueReceiverOnExecutor(
@NonNull Executor executor, @NonNull DynamicTypeValueReceiver<T> consumer) {
this.mConsumer = consumer;
this.mExecutor = executor;
}
/** This method is noop in this class. */
@Override
@SuppressWarnings("ExecutorTaskName")
public void onPreUpdate() {}
@Override
@SuppressWarnings("ExecutorTaskName")
public void onData(@NonNull T newData) {
mExecutor.execute(() -> mConsumer.onData(newData));
}
@Override
@SuppressWarnings("ExecutorTaskName")
public void onInvalidated() {
mExecutor.execute(mConsumer::onInvalidated);
}
}
}