DynamicBuilders.java

/*
 * 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;

import static androidx.wear.protolayout.expression.Preconditions.checkNotNull;

import android.annotation.SuppressLint;

import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec;
import androidx.wear.protolayout.expression.ConditionScopes.ConditionScope;
import androidx.wear.protolayout.expression.FixedValueBuilders.FixedBool;
import androidx.wear.protolayout.expression.FixedValueBuilders.FixedColor;
import androidx.wear.protolayout.expression.FixedValueBuilders.FixedFloat;
import androidx.wear.protolayout.expression.FixedValueBuilders.FixedInstant;
import androidx.wear.protolayout.expression.FixedValueBuilders.FixedInt32;
import androidx.wear.protolayout.expression.FixedValueBuilders.FixedString;
import androidx.wear.protolayout.expression.StateEntryBuilders.StateEntryValue;
import androidx.wear.protolayout.expression.proto.DynamicProto;
import androidx.wear.protolayout.protobuf.ExtensionRegistryLite;
import androidx.wear.protolayout.protobuf.InvalidProtocolBufferException;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Instant;

/** Builders for dynamic primitive types used by layout elements. */
public final class DynamicBuilders {
    private DynamicBuilders() {}

    /**
     * The type of data to provide to a {@link PlatformInt32Source}.
     *
     * @since 1.2
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @IntDef({
        PLATFORM_INT32_SOURCE_TYPE_UNDEFINED,
        PLATFORM_INT32_SOURCE_TYPE_CURRENT_HEART_RATE,
        PLATFORM_INT32_SOURCE_TYPE_DAILY_STEP_COUNT
    })
    @Retention(RetentionPolicy.SOURCE)
    @ProtoLayoutExperimental
    @interface PlatformInt32SourceType {}

    /**
     * Undefined source.
     *
     * @since 1.2
     */
    @ProtoLayoutExperimental static final int PLATFORM_INT32_SOURCE_TYPE_UNDEFINED = 0;

    /**
     * The user's current heart rate. Note that to use this data source, your app must already have
     * the "BODY_SENSORS" permission granted to it. If this permission is not present, this source
     * type will never yield any data.
     *
     * @since 1.2
     */
    @ProtoLayoutExperimental static final int PLATFORM_INT32_SOURCE_TYPE_CURRENT_HEART_RATE = 1;

    /**
     * The user's current daily steps. This is the number of steps they have taken since midnight,
     * and will reset to zero at midnight. Note that to use this data source, your app must already
     * have the "ACTIVITY_RECOGNITION" permission granted to it. If this permission is not present,
     * this source type will never yield any data.
     *
     * @since 1.2
     */
    @ProtoLayoutExperimental static final int PLATFORM_INT32_SOURCE_TYPE_DAILY_STEP_COUNT = 2;

    /**
     * The type of arithmetic operation used in {@link ArithmeticInt32Op} and {@link
     * ArithmeticFloatOp}.
     *
     * @since 1.2
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @IntDef({
        ARITHMETIC_OP_TYPE_UNDEFINED,
        ARITHMETIC_OP_TYPE_ADD,
        ARITHMETIC_OP_TYPE_SUBTRACT,
        ARITHMETIC_OP_TYPE_MULTIPLY,
        ARITHMETIC_OP_TYPE_DIVIDE,
        ARITHMETIC_OP_TYPE_MODULO
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface ArithmeticOpType {}

    /**
     * Undefined operation type.
     *
     * @since 1.2
     */
    static final int ARITHMETIC_OP_TYPE_UNDEFINED = 0;

    /**
     * Addition.
     *
     * @since 1.2
     */
    static final int ARITHMETIC_OP_TYPE_ADD = 1;

    /**
     * Subtraction.
     *
     * @since 1.2
     */
    static final int ARITHMETIC_OP_TYPE_SUBTRACT = 2;

    /**
     * Multiplication.
     *
     * @since 1.2
     */
    static final int ARITHMETIC_OP_TYPE_MULTIPLY = 3;

    /**
     * Division.
     *
     * @since 1.2
     */
    static final int ARITHMETIC_OP_TYPE_DIVIDE = 4;

    /**
     * Modulus.
     *
     * @since 1.2
     */
    static final int ARITHMETIC_OP_TYPE_MODULO = 5;

    /**
     * Rounding mode to use when converting a float to an int32.
     *
     * @since 1.2
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @IntDef({ROUND_MODE_UNDEFINED, ROUND_MODE_FLOOR, ROUND_MODE_ROUND, ROUND_MODE_CEILING})
    @Retention(RetentionPolicy.SOURCE)
    @interface FloatToInt32RoundMode {}

    /**
     * An undefined rounding mode.
     *
     * @since 1.2
     */
    static final int ROUND_MODE_UNDEFINED = 0;

    /**
     * Use floor(x) when rounding.
     *
     * @since 1.2
     */
    static final int ROUND_MODE_FLOOR = 1;

    /**
     * Use round(x) when rounding (i.e. rounds to the closest int).
     *
     * @since 1.2
     */
    static final int ROUND_MODE_ROUND = 2;

    /**
     * Use ceil(x) when rounding.
     *
     * @since 1.2
     */
    static final int ROUND_MODE_CEILING = 3;

    /**
     * The type of comparison used in {@link ComparisonInt32Op} and {@link ComparisonFloatOp}.
     *
     * @since 1.2
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @IntDef({
        COMPARISON_OP_TYPE_UNDEFINED,
        COMPARISON_OP_TYPE_EQUALS,
        COMPARISON_OP_TYPE_NOT_EQUALS,
        COMPARISON_OP_TYPE_LESS_THAN,
        COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO,
        COMPARISON_OP_TYPE_GREATER_THAN,
        COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface ComparisonOpType {}

    /**
     * Undefined operation type.
     *
     * @since 1.2
     */
    static final int COMPARISON_OP_TYPE_UNDEFINED = 0;

    /**
     * Equality check (result = LHS == RHS). For floats, for equality check, small epsilon is used,
     * i.e.: (result = abs(LHS - RHS) < epsilon).
     *
     * @since 1.2
     */
    static final int COMPARISON_OP_TYPE_EQUALS = 1;

    /**
     * Not equal check (result = LHS != RHS).
     *
     * @since 1.2
     */
    static final int COMPARISON_OP_TYPE_NOT_EQUALS = 2;

    /**
     * Strictly less than (result = LHS < RHS).
     *
     * @since 1.2
     */
    static final int COMPARISON_OP_TYPE_LESS_THAN = 3;

    /**
     * Less than or equal to (result = LHS <= RHS).
     *
     * @since 1.2
     */
    static final int COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO = 4;

    /**
     * Strictly greater than (result = LHS > RHS).
     *
     * @since 1.2
     */
    static final int COMPARISON_OP_TYPE_GREATER_THAN = 5;

    /**
     * Greater than or equal to (result = LHS >= RHS).
     *
     * @since 1.2
     */
    static final int COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO = 6;

    /**
     * The type of logical operation to carry out in a {@link LogicalBoolOp} operation.
     *
     * @since 1.2
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @IntDef({LOGICAL_OP_TYPE_UNDEFINED, LOGICAL_OP_TYPE_AND, LOGICAL_OP_TYPE_OR})
    @Retention(RetentionPolicy.SOURCE)
    @interface LogicalOpType {}

    /**
     * Undefined operation type.
     *
     * @since 1.2
     */
    static final int LOGICAL_OP_TYPE_UNDEFINED = 0;

    /**
     * Logical AND.
     *
     * @since 1.2
     */
    static final int LOGICAL_OP_TYPE_AND = 1;

    /**
     * Logical OR.
     *
     * @since 1.2
     */
    static final int LOGICAL_OP_TYPE_OR = 2;

    /**
     * The duration part to retrieve using {@link GetDurationPartOp}.
     *
     * @since 1.2
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @IntDef({
        DURATION_PART_TYPE_UNDEFINED,
        DURATION_PART_TYPE_TOTAL_DAYS,
        DURATION_PART_TYPE_TOTAL_HOURS,
        DURATION_PART_TYPE_TOTAL_MINUTES,
        DURATION_PART_TYPE_TOTAL_SECONDS,
        DURATION_PART_TYPE_DAYS,
        DURATION_PART_TYPE_HOURS,
        DURATION_PART_TYPE_MINUTES,
        DURATION_PART_TYPE_SECONDS
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface DurationPartType {}

    /**
     * Undefined duration part type.
     *
     * @since 1.2
     */
    static final int DURATION_PART_TYPE_UNDEFINED = 0;

    /**
     * Total number of days in a duration. The fraction part of the result will be truncated. This
     * is based on the standard definition of a day as 24 hours. Notice that the duration can be
     * negative, in which case total number of days will be also negative.
     *
     * @since 1.2
     */
    static final int DURATION_PART_TYPE_TOTAL_DAYS = 1;

    /**
     * Total number of hours in a duration. The fraction part of the result will be truncated.
     * Notice that the duration can be negative, in which case total number of hours will be also
     * negative.
     *
     * @since 1.2
     */
    static final int DURATION_PART_TYPE_TOTAL_HOURS = 2;

    /**
     * Total number of minutes in a duration. The fraction part of the result will be truncated.
     * Notice that the duration can be negative, in which case total number of minutes will be also
     * negative.
     *
     * @since 1.2
     */
    static final int DURATION_PART_TYPE_TOTAL_MINUTES = 3;

    /**
     * Total number of seconds in a duration. Notice that the duration can be negative, in which
     * case total number of seconds will be also negative.
     *
     * @since 1.2
     */
    static final int DURATION_PART_TYPE_TOTAL_SECONDS = 4;

    /**
     * Number of days part in the duration. This represents the absolute value of the total number
     * of days in the duration based on the 24 hours day definition. The fraction part of the result
     * will be truncated.
     *
     * @since 1.2
     */
    static final int DURATION_PART_TYPE_DAYS = 5;

    /**
     * Number of hours part in the duration. This represents the absolute value of remaining hours
     * when dividing total hours by hours in a day (24 hours).
     *
     * @since 1.2
     */
    static final int DURATION_PART_TYPE_HOURS = 6;

    /**
     * Number of minutes part in the duration. This represents the absolute value of remaining
     * minutes when dividing total minutes by minutes in an hour (60 minutes).
     *
     * @since 1.2
     */
    static final int DURATION_PART_TYPE_MINUTES = 7;

    /**
     * Number of seconds part in the duration. This represents the absolute value of remaining
     * seconds when dividing total seconds by seconds in a minute (60 seconds).
     *
     * @since 1.2
     */
    static final int DURATION_PART_TYPE_SECONDS = 8;

    /**
     * A dynamic Int32 which sources its data from some platform data source, e.g. from sensors, or
     * the current time.
     *
     * @since 1.2
     */
    @ProtoLayoutExperimental
    static final class PlatformInt32Source implements DynamicInt32 {
        private final DynamicProto.PlatformInt32Source mImpl;
        @Nullable private final Fingerprint mFingerprint;

        PlatformInt32Source(
                DynamicProto.PlatformInt32Source impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the source to load data from.
         *
         * @since 1.2
         */
        @PlatformInt32SourceType
        public int getSourceType() {
            return mImpl.getSourceType().getNumber();
        }

        /** @hide */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }
        /**
         * Creates a new wrapper instance from the proto.
         *
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public static PlatformInt32Source fromProto(
                @NonNull DynamicProto.PlatformInt32Source proto,
                @Nullable Fingerprint fingerprint) {
            return new PlatformInt32Source(proto, fingerprint);
        }

        @NonNull
        static PlatformInt32Source fromProto(@NonNull DynamicProto.PlatformInt32Source proto) {
            return fromProto(proto, null);
        }

        /**
         * Returns the internal proto instance.
         *
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        DynamicProto.PlatformInt32Source toProto() {
            return mImpl;
        }

        /** @hide */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicInt32 toDynamicInt32Proto() {
            return DynamicProto.DynamicInt32.newBuilder().setPlatformSource(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "PlatformInt32Source{" + "sourceType=" + getSourceType() + "}";
        }

        /** Builder for {@link PlatformInt32Source}. */
        public static final class Builder implements DynamicInt32.Builder {
            private final DynamicProto.PlatformInt32Source.Builder mImpl =
                    DynamicProto.PlatformInt32Source.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(1355180718);

            public Builder() {}

            /**
             * Sets the source to load data from.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setSourceType(@PlatformInt32SourceType int sourceType) {
                mImpl.setSourceType(DynamicProto.PlatformInt32SourceType.forNumber(sourceType));
                mFingerprint.recordPropertyUpdate(1, sourceType);
                return this;
            }

            @Override
            @NonNull
            public PlatformInt32Source build() {
                return new PlatformInt32Source(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * An arithmetic operation, operating on two Int32 instances. This implements simple binary
     * operations of the form "result = LHS <op> RHS", where the available operation types are
     * described in {@code ArithmeticOpType}.
     *
     * @since 1.2
     */
    static final class ArithmeticInt32Op implements DynamicInt32 {

        private final DynamicProto.ArithmeticInt32Op mImpl;
        @Nullable private final Fingerprint mFingerprint;

        ArithmeticInt32Op(DynamicProto.ArithmeticInt32Op impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets left hand side of the arithmetic operation.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicInt32 getInputLhs() {
            if (mImpl.hasInputLhs()) {
                return DynamicBuilders.dynamicInt32FromProto(mImpl.getInputLhs());
            } else {
                return null;
            }
        }

        /**
         * Gets right hand side of the arithmetic operation.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicInt32 getInputRhs() {
            if (mImpl.hasInputRhs()) {
                return DynamicBuilders.dynamicInt32FromProto(mImpl.getInputRhs());
            } else {
                return null;
            }
        }

        /**
         * Gets the type of operation to carry out.
         *
         * @since 1.2
         */
        @ArithmeticOpType
        public int getOperationType() {
            return mImpl.getOperationType().getNumber();
        }

        /** */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static ArithmeticInt32Op fromProto(@NonNull DynamicProto.ArithmeticInt32Op proto) {
            return new ArithmeticInt32Op(proto, null);
        }

        @NonNull
        DynamicProto.ArithmeticInt32Op toProto() {
            return mImpl;
        }

        /** */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicInt32 toDynamicInt32Proto() {
            return DynamicProto.DynamicInt32.newBuilder().setArithmeticOperation(mImpl).build();
        }

        /** Builder for {@link ArithmeticInt32Op}. */
        public static final class Builder implements DynamicInt32.Builder {

            private final DynamicProto.ArithmeticInt32Op.Builder mImpl =
                    DynamicProto.ArithmeticInt32Op.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-2012727925);

            public Builder() {}

            /**
             * Sets left hand side of the arithmetic operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInputLhs(@NonNull DynamicInt32 inputLhs) {
                mImpl.setInputLhs(inputLhs.toDynamicInt32Proto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(inputLhs.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets right hand side of the arithmetic operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInputRhs(@NonNull DynamicInt32 inputRhs) {
                mImpl.setInputRhs(inputRhs.toDynamicInt32Proto());
                mFingerprint.recordPropertyUpdate(
                        2, checkNotNull(inputRhs.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the type of operation to carry out.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setOperationType(@ArithmeticOpType int operationType) {
                mImpl.setOperationType(DynamicProto.ArithmeticOpType.forNumber(operationType));
                mFingerprint.recordPropertyUpdate(3, operationType);
                return this;
            }

            @Override
            @NonNull
            public ArithmeticInt32Op build() {
                return new ArithmeticInt32Op(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A dynamic Int32 which sources its data from the tile's state.
     *
     * @since 1.2
     */
    static final class StateInt32Source implements DynamicInt32 {
        private final DynamicProto.StateInt32Source mImpl;
        @Nullable private final Fingerprint mFingerprint;

        StateInt32Source(DynamicProto.StateInt32Source impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the key in the state to bind to.
         *
         * @since 1.2
         */
        @NonNull
        public String getSourceKey() {
            return mImpl.getSourceKey();
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static StateInt32Source fromProto(@NonNull DynamicProto.StateInt32Source proto) {
            return new StateInt32Source(proto, null);
        }

        @NonNull
        DynamicProto.StateInt32Source toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicInt32 toDynamicInt32Proto() {
            return DynamicProto.DynamicInt32.newBuilder().setStateSource(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "StateInt32Source{" + "sourceKey=" + getSourceKey() + "}";
        }

        /** Builder for {@link StateInt32Source}. */
        public static final class Builder implements DynamicInt32.Builder {
            private final DynamicProto.StateInt32Source.Builder mImpl =
                    DynamicProto.StateInt32Source.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(58614749);

            public Builder() {}

            /**
             * Sets the key in the state to bind to.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setSourceKey(@NonNull String sourceKey) {
                mImpl.setSourceKey(sourceKey);
                mFingerprint.recordPropertyUpdate(1, sourceKey.hashCode());
                return this;
            }

            @Override
            @NonNull
            public StateInt32Source build() {
                return new StateInt32Source(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A conditional operator which yields an integer depending on the boolean operand. This
     * implements "int result = condition ? value_if_true : value_if_false".
     *
     * @since 1.2
     */
    static final class ConditionalInt32Op implements DynamicInt32 {

        private final DynamicProto.ConditionalInt32Op mImpl;
        @Nullable private final Fingerprint mFingerprint;

        ConditionalInt32Op(
                DynamicProto.ConditionalInt32Op impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the condition to use.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicBool getCondition() {
            if (mImpl.hasCondition()) {
                return DynamicBuilders.dynamicBoolFromProto(mImpl.getCondition());
            } else {
                return null;
            }
        }

        /**
         * Gets the integer to yield if condition is true.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicInt32 getValueIfTrue() {
            if (mImpl.hasValueIfTrue()) {
                return DynamicBuilders.dynamicInt32FromProto(mImpl.getValueIfTrue());
            } else {
                return null;
            }
        }

        /**
         * Gets the integer to yield if condition is false.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicInt32 getValueIfFalse() {
            if (mImpl.hasValueIfFalse()) {
                return DynamicBuilders.dynamicInt32FromProto(mImpl.getValueIfFalse());
            } else {
                return null;
            }
        }

        /** */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static ConditionalInt32Op fromProto(@NonNull DynamicProto.ConditionalInt32Op proto) {
            return new ConditionalInt32Op(proto, null);
        }

        @NonNull
        DynamicProto.ConditionalInt32Op toProto() {
            return mImpl;
        }

        /** */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicInt32 toDynamicInt32Proto() {
            return DynamicProto.DynamicInt32.newBuilder().setConditionalOp(mImpl).build();
        }

        /** Builder for {@link ConditionalInt32Op}. */
        public static final class Builder implements DynamicInt32.Builder {

            private final DynamicProto.ConditionalInt32Op.Builder mImpl =
                    DynamicProto.ConditionalInt32Op.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(1444834226);

            public Builder() {}

            /**
             * Sets the condition to use.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setCondition(@NonNull DynamicBool condition) {
                mImpl.setCondition(condition.toDynamicBoolProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(condition.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the integer to yield if condition is true.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setValueIfTrue(@NonNull DynamicInt32 valueIfTrue) {
                mImpl.setValueIfTrue(valueIfTrue.toDynamicInt32Proto());
                mFingerprint.recordPropertyUpdate(
                        2, checkNotNull(valueIfTrue.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the integer to yield if condition is false.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setValueIfFalse(@NonNull DynamicInt32 valueIfFalse) {
                mImpl.setValueIfFalse(valueIfFalse.toDynamicInt32Proto());
                mFingerprint.recordPropertyUpdate(
                        3, checkNotNull(valueIfFalse.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            @Override
            @NonNull
            public ConditionalInt32Op build() {
                return new ConditionalInt32Op(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A conditional operator which yields a float depending on the boolean operand. This implements
     * "float result = condition ? value_if_true : value_if_false".
     *
     * @since 1.2
     */
    static final class ConditionalFloatOp implements DynamicFloat {

        private final DynamicProto.ConditionalFloatOp mImpl;
        @Nullable private final Fingerprint mFingerprint;

        ConditionalFloatOp(
                DynamicProto.ConditionalFloatOp impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the condition to use.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicBool getCondition() {
            if (mImpl.hasCondition()) {
                return DynamicBuilders.dynamicBoolFromProto(mImpl.getCondition());
            } else {
                return null;
            }
        }

        /**
         * Gets the float to yield if condition is true.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicFloat getValueIfTrue() {
            if (mImpl.hasValueIfTrue()) {
                return DynamicBuilders.dynamicFloatFromProto(mImpl.getValueIfTrue());
            } else {
                return null;
            }
        }

        /**
         * Gets the float to yield if condition is false.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicFloat getValueIfFalse() {
            if (mImpl.hasValueIfFalse()) {
                return DynamicBuilders.dynamicFloatFromProto(mImpl.getValueIfFalse());
            } else {
                return null;
            }
        }

        /** */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static ConditionalFloatOp fromProto(@NonNull DynamicProto.ConditionalFloatOp proto) {
            return new ConditionalFloatOp(proto, null);
        }

        @NonNull
        DynamicProto.ConditionalFloatOp toProto() {
            return mImpl;
        }

        /** */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicFloat toDynamicFloatProto() {
            return DynamicProto.DynamicFloat.newBuilder().setConditionalOp(mImpl).build();
        }

        /** Builder for {@link ConditionalFloatOp}. */
        public static final class Builder implements DynamicFloat.Builder {

            private final DynamicProto.ConditionalFloatOp.Builder mImpl =
                    DynamicProto.ConditionalFloatOp.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(1968171153);

            public Builder() {}

            /**
             * Sets the condition to use.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setCondition(@NonNull DynamicBool condition) {
                mImpl.setCondition(condition.toDynamicBoolProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(condition.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the float to yield if condition is true.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setValueIfTrue(@NonNull DynamicFloat valueIfTrue) {
                mImpl.setValueIfTrue(valueIfTrue.toDynamicFloatProto());
                mFingerprint.recordPropertyUpdate(
                        2, checkNotNull(valueIfTrue.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the float to yield if condition is false.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setValueIfFalse(@NonNull DynamicFloat valueIfFalse) {
                mImpl.setValueIfFalse(valueIfFalse.toDynamicFloatProto());
                mFingerprint.recordPropertyUpdate(
                        3, checkNotNull(valueIfFalse.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            @Override
            @NonNull
            public ConditionalFloatOp build() {
                return new ConditionalFloatOp(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * Converts a Float to an Int32, with a customizable rounding mode.
     *
     * @since 1.2
     */
    static final class FloatToInt32Op implements DynamicInt32 {
        private final DynamicProto.FloatToInt32Op mImpl;
        @Nullable private final Fingerprint mFingerprint;

        FloatToInt32Op(DynamicProto.FloatToInt32Op impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the float to round.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicFloat getInput() {
            if (mImpl.hasInput()) {
                return DynamicBuilders.dynamicFloatFromProto(mImpl.getInput());
            } else {
                return null;
            }
        }

        /**
         * Gets the rounding mode to use. Defaults to ROUND_MODE_FLOOR if not specified.
         *
         * @since 1.2
         */
        @FloatToInt32RoundMode
        public int getRoundMode() {
            return mImpl.getRoundMode().getNumber();
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static FloatToInt32Op fromProto(@NonNull DynamicProto.FloatToInt32Op proto) {
            return new FloatToInt32Op(proto, null);
        }

        @NonNull
        DynamicProto.FloatToInt32Op toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicInt32 toDynamicInt32Proto() {
            return DynamicProto.DynamicInt32.newBuilder().setFloatToInt(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "FloatToInt32Op{"
                    + "input="
                    + getInput()
                    + ", roundMode="
                    + getRoundMode()
                    + "}";
        }

        /** Builder for {@link FloatToInt32Op}. */
        public static final class Builder implements DynamicInt32.Builder {
            private final DynamicProto.FloatToInt32Op.Builder mImpl =
                    DynamicProto.FloatToInt32Op.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-1272973414);

            public Builder() {}

            /**
             * Sets the float to round.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInput(@NonNull DynamicFloat input) {
                mImpl.setInput(input.toDynamicFloatProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(input.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the rounding mode to use. Defaults to ROUND_MODE_FLOOR if not specified.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setRoundMode(@FloatToInt32RoundMode int roundMode) {
                mImpl.setRoundMode(DynamicProto.FloatToInt32RoundMode.forNumber(roundMode));
                mFingerprint.recordPropertyUpdate(2, roundMode);
                return this;
            }

            @Override
            @NonNull
            public FloatToInt32Op build() {
                return new FloatToInt32Op(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A static interpolation node, between two fixed int32 values.
     *
     * @since 1.2
     */
    static final class AnimatableFixedInt32 implements DynamicInt32 {
        private final DynamicProto.AnimatableFixedInt32 mImpl;
        @Nullable private final Fingerprint mFingerprint;

        AnimatableFixedInt32(
                DynamicProto.AnimatableFixedInt32 impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the value to start animating from.
         *
         * @since 1.2
         */
        public int getFromValue() {
            return mImpl.getFromValue();
        }

        /**
         * Gets the value to animate to.
         *
         * @since 1.2
         */
        public int getToValue() {
            return mImpl.getToValue();
        }

        /**
         * Gets the animation parameters for duration, delay, etc.
         *
         * @since 1.2
         */
        @Nullable
        public AnimationSpec getAnimationSpec() {
            if (mImpl.hasAnimationSpec()) {
                return AnimationSpec.fromProto(mImpl.getAnimationSpec());
            } else {
                return null;
            }
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }
        /** Creates a new wrapper instance from the proto. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public static AnimatableFixedInt32 fromProto(
                @NonNull DynamicProto.AnimatableFixedInt32 proto,
                @Nullable Fingerprint fingerprint) {
            return new AnimatableFixedInt32(proto, fingerprint);
        }

        @NonNull
        static AnimatableFixedInt32 fromProto(@NonNull DynamicProto.AnimatableFixedInt32 proto) {
            return fromProto(proto, null);
        }

        /** Returns the internal proto instance. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        DynamicProto.AnimatableFixedInt32 toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicInt32 toDynamicInt32Proto() {
            return DynamicProto.DynamicInt32.newBuilder().setAnimatableFixed(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "AnimatableFixedInt32{"
                    + "fromValue="
                    + getFromValue()
                    + ", toValue="
                    + getToValue()
                    + ", animationSpec="
                    + getAnimationSpec()
                    + "}";
        }

        /** Builder for {@link AnimatableFixedInt32}. */
        public static final class Builder implements DynamicInt32.Builder {
            private final DynamicProto.AnimatableFixedInt32.Builder mImpl =
                    DynamicProto.AnimatableFixedInt32.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-1831435966);

            public Builder() {}

            /**
             * Sets the value to start animating from.
             *
             * @since 1.2
             */
            @NonNull
            public AnimatableFixedInt32.Builder setFromValue(int fromValue) {
                mImpl.setFromValue(fromValue);
                mFingerprint.recordPropertyUpdate(1, fromValue);
                return this;
            }

            /**
             * Sets the value to animate to.
             *
             * @since 1.2
             */
            @NonNull
            public AnimatableFixedInt32.Builder setToValue(int toValue) {
                mImpl.setToValue(toValue);
                mFingerprint.recordPropertyUpdate(2, toValue);
                return this;
            }

            /**
             * Sets the animation parameters for duration, delay, etc.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
                mImpl.setAnimationSpec(animationSpec.toProto());
                mFingerprint.recordPropertyUpdate(
                        3, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            @Override
            @NonNull
            public AnimatableFixedInt32 build() {
                return new AnimatableFixedInt32(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A dynamic interpolation node. This will watch the value of its input and, when the first
     * update arrives, immediately emit that value. On subsequent updates, it will animate between
     * the old and new values.
     *
     * <p>If this node receives an invalid value (e.g. as a result of an upstream node having no
     * value), then it will emit a single invalid value, and forget its "stored" value. The next
     * valid value that arrives is then used as the "first" value again.
     *
     * @since 1.2
     */
    static final class AnimatableDynamicInt32 implements DynamicInt32 {
        private final DynamicProto.AnimatableDynamicInt32 mImpl;
        @Nullable private final Fingerprint mFingerprint;

        AnimatableDynamicInt32(
                DynamicProto.AnimatableDynamicInt32 impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the value to watch, and animate when it changes.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicInt32 getInput() {
            if (mImpl.hasInput()) {
                return dynamicInt32FromProto(mImpl.getInput());
            } else {
                return null;
            }
        }

        /**
         * Gets the animation parameters for duration, delay, etc.
         *
         * @since 1.2
         */
        @Nullable
        public AnimationSpec getAnimationSpec() {
            if (mImpl.hasAnimationSpec()) {
                return AnimationSpec.fromProto(mImpl.getAnimationSpec());
            } else {
                return null;
            }
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }
        /** Creates a new wrapper instance from the proto. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public static AnimatableDynamicInt32 fromProto(
                @NonNull DynamicProto.AnimatableDynamicInt32 proto,
                @Nullable Fingerprint fingerprint) {
            return new AnimatableDynamicInt32(proto, fingerprint);
        }

        @NonNull
        static AnimatableDynamicInt32 fromProto(
                @NonNull DynamicProto.AnimatableDynamicInt32 proto) {
            return fromProto(proto, null);
        }

        /** Returns the internal proto instance. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        DynamicProto.AnimatableDynamicInt32 toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicInt32 toDynamicInt32Proto() {
            return DynamicProto.DynamicInt32.newBuilder().setAnimatableDynamic(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "AnimatableDynamicInt32{"
                    + "input="
                    + getInput()
                    + ", animationSpec="
                    + getAnimationSpec()
                    + "}";
        }

        /** Builder for {@link AnimatableDynamicInt32}. */
        public static final class Builder implements DynamicInt32.Builder {
            private final DynamicProto.AnimatableDynamicInt32.Builder mImpl =
                    DynamicProto.AnimatableDynamicInt32.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-1554674954);

            public Builder() {}

            /**
             * Sets the value to watch, and animate when it changes.
             *
             * @since 1.2
             */
            @NonNull
            public AnimatableDynamicInt32.Builder setInput(@NonNull DynamicInt32 input) {
                mImpl.setInput(input.toDynamicInt32Proto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(input.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the animation parameters for duration, delay, etc.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
                mImpl.setAnimationSpec(animationSpec.toProto());
                mFingerprint.recordPropertyUpdate(
                        2, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            @Override
            @NonNull
            public AnimatableDynamicInt32 build() {
                return new AnimatableDynamicInt32(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * Interface defining a dynamic int32 type.
     *
     * <p>It offers a set of helper methods for creating arithmetic and logical expressions, e.g.
     * {@link #plus(int)}, {@link #times(int)}, {@link #eq(int)}, etc. These helper methods produce
     * expression trees based on the order in which they were called in an expression. Thus, no
     * operator precedence rules are applied.
     *
     * <p>For example the following expression is equivalent to {@code result = ((a + b)*c)/d }:
     *
     * <pre>{@code
     * a.plus(b).times(c).div(d);
     * }</pre>
     *
     * More complex expressions can be created by nesting expressions. For example the following
     * expression is equivalent to {@code result = (a + b)*(c - d) }:
     *
     * <pre>{@code
     * (a.plus(b)).times(c.minus(d));
     * }</pre>
     *
     * @since 1.2
     */
    public interface DynamicInt32 extends DynamicType {
        /** Get the protocol buffer representation of this object. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        DynamicProto.DynamicInt32 toDynamicInt32Proto();

        /**
         * Creates a {@link DynamicInt32} from a byte array generated by {@link
         * #toDynamicInt32ByteArray()}.
         */
        @NonNull
        static DynamicInt32 fromByteArray(@NonNull byte[] byteArray) {
            try {
                return dynamicInt32FromProto(
                        DynamicProto.DynamicInt32.parseFrom(
                                byteArray, ExtensionRegistryLite.getEmptyRegistry()));
            } catch (InvalidProtocolBufferException e) {
                throw new IllegalArgumentException(
                        "Byte array could not be parsed into DynamicInt32", e);
            }
        }

        /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
        @NonNull
        default byte[] toDynamicInt32ByteArray() {
            return toDynamicInt32Proto().toByteArray();
        }

        /** Creates a constant-valued {@link DynamicInt32}. */
        @NonNull
        static DynamicInt32 constant(int constant) {
            return new FixedInt32.Builder().setValue(constant).build();
        }

        /**
         * Creates a {@link DynamicInt32} that is bound to the value of an item of the State.
         *
         * @param stateKey The key to a {@link StateEntryValue} with an int value from the
         *     provider's state.
         */
        @NonNull
        static DynamicInt32 fromState(@NonNull String stateKey) {
            return new StateInt32Source.Builder().setSourceKey(stateKey).build();
        }

        /**
         * Creates a {@link DynamicInt32} which will animate from {@code start} to {@code end}.
         *
         * @param start The start value of the range.
         * @param end The end value of the range.
         */
        @NonNull
        static DynamicInt32 animate(int start, int end) {
            return new AnimatableFixedInt32.Builder().setFromValue(start).setToValue(end).build();
        }

        /**
         * Creates a {@link DynamicInt32} which will animate from {@code start} to {@code end} with
         * the given animation parameters.
         *
         * @param start The start value of the range.
         * @param end The end value of the range.
         * @param animationSpec The animation parameters.
         */
        @NonNull
        static DynamicInt32 animate(int start, int end, @NonNull AnimationSpec animationSpec) {
            return new AnimatableFixedInt32.Builder()
                    .setFromValue(start)
                    .setToValue(end)
                    .setAnimationSpec(animationSpec)
                    .build();
        }

        /**
         * Creates a {@link DynamicInt32} that is bound to the value of an item of the State. Every
         * time the state value changes, this {@link DynamicInt32} will animate from its current
         * value to the new value (from the state).
         *
         * @param stateKey The key to a {@link StateEntryValue} with an int value from the
         *     provider's state.
         */
        @NonNull
        static DynamicInt32 animate(@NonNull String stateKey) {
            return new AnimatableDynamicInt32.Builder().setInput(fromState(stateKey)).build();
        }

        /**
         * Creates a {@link DynamicInt32} that is bound to the value of an item of the State. Every
         * time the state value changes, this {@link DynamicInt32} will animate from its current
         * value to the new value (from the state).
         *
         * @param stateKey The key to a {@link StateEntryValue} with an int value from the
         *     provider's state.
         * @param animationSpec The animation parameters.
         */
        @NonNull
        static DynamicInt32 animate(
                @NonNull String stateKey, @NonNull AnimationSpec animationSpec) {
            return new AnimatableDynamicInt32.Builder()
                    .setInput(fromState(stateKey))
                    .setAnimationSpec(animationSpec)
                    .build();
        }

        /**
         * Returns a {@link DynamicInt32} that is bound to the value of this {@link DynamicInt32}
         * and every time its value is changing, it animates from its current value to the new
         * value.
         *
         * @param animationSpec The animation parameters.
         */
        @NonNull
        default DynamicInt32 animate(@NonNull AnimationSpec animationSpec) {
            return new AnimatableDynamicInt32.Builder()
                    .setInput(this)
                    .setAnimationSpec(animationSpec)
                    .build();
        }

        /**
         * Returns a {@link DynamicInt32} that is bound to the value of this {@link DynamicInt32}
         * and every time its value is changing, it animates from its current value to the new
         * value.
         */
        @NonNull
        default DynamicInt32 animate() {
            return new AnimatableDynamicInt32.Builder().setInput(this).build();
        }

        /**
         * Convert the value represented by this {@link DynamicInt32} into a {@link DynamicFloat}.
         */
        @NonNull
        default DynamicFloat asFloat() {
            return new Int32ToFloatOp.Builder().setInput(this).build();
        }

        /**
         * Bind the value of this {@link DynamicInt32} to the result of a conditional expression.
         * This will use the value given in either {@link ConditionScope#use} or {@link
         * ConditionScopes.IfTrueScope#elseUse} depending on the value yielded from {@code
         * condition}.
         */
        @NonNull
        static ConditionScope<DynamicInt32, Integer> onCondition(@NonNull DynamicBool condition) {
            return new ConditionScopes.ConditionScope<>(
                    (trueValue, falseValue) ->
                            new ConditionalInt32Op.Builder()
                                    .setCondition(condition)
                                    .setValueIfTrue(trueValue)
                                    .setValueIfFalse(falseValue)
                                    .build(),
                    DynamicInt32::constant);
        }

        /**
         * Creates a {@link DynamicInt32} containing the result of adding another {@link
         * DynamicInt32} to this {@link DynamicInt32}; As an example, the following is equal to
         * {@code DynamicInt32.constant(13)}
         *
         * <pre>
         *   DynamicInt32.constant(7).plus(DynamicInt32.constant(6));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicInt32} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicInt32 plus(@NonNull DynamicInt32 other) {
            return new ArithmeticInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_ADD)
                    .build();
        }

        /**
         * Creates a {@link DynamicFlaot} containing the result of adding a {@link DynamicFloat} to
         * this {@link DynamicInt32}; As an example, the following is equal to {@code
         * DynamicFloat.constant(13.5f)}
         *
         * <pre>
         *   DynamicInt32.constant(7).plus(DynamicFloat.constant(6.5f));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat plus(@NonNull DynamicFloat other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this.asFloat())
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_ADD)
                    .build();
        }

        /**
         * Creates a {@link DynamicInt32} containing the result of adding an integer to this {@link
         * DynamicInt32}; As an example, the following is equal to {@code DynamicInt32.constant(13)}
         *
         * <pre>
         *   DynamicInt32.constant(7).plus(6);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicInt32} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicInt32 plus(int other) {
            return new ArithmeticInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_ADD)
                    .build();
        }

        /**
         * Creates a {@link DynamicFlaot} containing the result of adding a float to this {@link
         * DynamicInt32}; As an example, the following is equal to {@code
         * DynamicFloat.constant(13.5f)}
         *
         * <pre>
         *   DynamicInt32.constant(7).plus(6.5f);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat plus(float other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this.asFloat())
                    .setInputRhs(DynamicFloat.constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_ADD)
                    .build();
        }

        /**
         * Creates a {@link DynamicInt32} containing the result of subtracting another {@link
         * DynamicInt32} from this {@link DynamicInt32}; As an example, the following is equal to
         * {@code DynamicInt32.constant(2)}
         *
         * <pre>
         *   DynamicInt32.constant(7).minus(DynamicInt32.constant(5));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicInt32} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicInt32 minus(@NonNull DynamicInt32 other) {
            return new ArithmeticInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_SUBTRACT)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of subtracting a {@link
         * DynamicFloat} from this {@link DynamicInt32}; As an example, the following is equal to
         * {@code DynamicFloat.constant(1.5f)}
         *
         * <pre>
         *   DynamicInt32.constant(7).minus(DynamicFloat.constant(5.5f));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat minus(@NonNull DynamicFloat other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this.asFloat())
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_SUBTRACT)
                    .build();
        }

        /**
         * Creates a {@link DynamicInt32} containing the result of subtracting an integer from this
         * {@link DynamicInt32}; As an example, the following is equal to {@code
         * DynamicInt32.constant(2)}
         *
         * <pre>
         *   DynamicInt32.constant(7).minus(5);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicInt32} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicInt32 minus(int other) {
            return new ArithmeticInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_SUBTRACT)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of subtracting a float from this
         * {@link DynamicInt32}; As an example, the following is equal to {@code
         * DynamicFloat.constant(1.5f)}
         *
         * <pre>
         *   DynamicInt32.constant(7).minus(5.5f);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat minus(float other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this.asFloat())
                    .setInputRhs(DynamicFloat.constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_SUBTRACT)
                    .build();
        }

        /**
         * Creates a {@link DynamicInt32} containing the result of multiplying this {@link
         * DynamicInt32} by another {@link DynamicInt32}; As an example, the following is equal to
         * {@code DynamicInt32.constant(35)}
         *
         * <pre>
         *   DynamicInt32.constant(7).times(DynamicInt32.constant(5));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicInt32} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicInt32 times(@NonNull DynamicInt32 other) {
            return new ArithmeticInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MULTIPLY)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of multiplying this {@link
         * DynamicInt32} by a {@link DynamicFloat}; As an example, the following is equal to {@code
         * DynamicFloat.constant(38.5f)}
         *
         * <pre>
         *   DynamicInt32.constant(7).times(DynamicFloat.constant(5.5f));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat times(@NonNull DynamicFloat other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this.asFloat())
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MULTIPLY)
                    .build();
        }

        /**
         * Creates a {@link DynamicInt32} containing the result of multiplying this {@link
         * DynamicInt32} by an integer; As an example, the following is equal to {@code
         * DynamicInt32.constant(35)}
         *
         * <pre>
         *   DynamicInt32.constant(7).times(5);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicInt32} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicInt32 times(int other) {
            return new ArithmeticInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MULTIPLY)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of multiplying this {@link
         * DynamicInt32} by a float; As an example, the following is equal to {@code
         * DynamicFloat.constant(38.5f)}
         *
         * <pre>
         *   DynamicInt32.constant(7).times(5.5f);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat times(float other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this.asFloat())
                    .setInputRhs(DynamicFloat.constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MULTIPLY)
                    .build();
        }

        /**
         * Creates a {@link DynamicInt32} containing the result of dividing this {@link
         * DynamicInt32} by another {@link DynamicInt32}; As an example, the following is equal to
         * {@code DynamicInt32.constant(1)}
         *
         * <pre>
         *   DynamicInt32.constant(7).div(DynamicInt32.constant(5));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicInt32} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicInt32 div(@NonNull DynamicInt32 other) {
            return new ArithmeticInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_DIVIDE)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of dividing this {@link
         * DynamicInt32} by a {@link DynamicFloat}; As an example, the following is equal to {@code
         * DynamicFloat.constant(1.4f)}
         *
         * <pre>
         *   DynamicInt32.constant(7).div(DynamicFloat.constant(5f));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat div(@NonNull DynamicFloat other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this.asFloat())
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_DIVIDE)
                    .build();
        }

        /**
         * Creates a {@link DynamicInt32} containing the result of dividing this {@link
         * DynamicInt32} by an integer; As an example, the following is equal to {@code
         * DynamicInt32.constant(1)}
         *
         * <pre>
         *   DynamicInt32.constant(7).div(5);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicInt32} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicInt32 div(int other) {
            return new ArithmeticInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_DIVIDE)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of dividing this {@link
         * DynamicInt32} by a float; As an example, the following is equal to {@code
         * DynamicFloat.constant(1.4f)}
         *
         * <pre>
         *   DynamicInt32.constant(7).div(5f);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat div(float other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this.asFloat())
                    .setInputRhs(DynamicFloat.constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_DIVIDE)
                    .build();
        }

        /**
         * Creates a {@link DynamicInt32} containing the reminder of dividing this {@link
         * DynamicInt32} by another {@link DynamicInt32}; As an example, the following is equal to
         * {@code DynamicInt32.constant(2)}
         *
         * <pre>
         *   DynamicInt32.constant(7).rem(DynamicInt32.constant(5));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicInt32} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicInt32 rem(@NonNull DynamicInt32 other) {
            return new ArithmeticInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MODULO)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the reminder of dividing this {@link
         * DynamicInt32} by a {@link DynamicFloat}; As an example, the following is equal to {@code
         * DynamicFloat.constant(1.5f)}
         *
         * <pre>
         *   DynamicInt32.constant(7).rem(DynamicInt32.constant(5.5f));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat rem(@NonNull DynamicFloat other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this.asFloat())
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MODULO)
                    .build();
        }

        /**
         * Creates a {@link DynamicInt32} containing the reminder of dividing this {@link
         * DynamicInt32} by an integer; As an example, the following is equal to {@code
         * DynamicInt32.constant(2)}
         *
         * <pre>
         *   DynamicInt32.constant(7).rem(5);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicInt32} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicInt32 rem(int other) {
            return new ArithmeticInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MODULO)
                    .build();
        }

        /**
         * Creates a {@link DynamicInt32} containing the reminder of dividing this {@link
         * DynamicInt32} by a float; As an example, the following is equal to {@code
         * DynamicFloat.constant(1.5f)}
         *
         * <pre>
         *   DynamicInt32.constant(7).rem(5.5f);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat rem(float other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this.asFloat())
                    .setInputRhs(DynamicFloat.constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MODULO)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicInt32} and
         * {@code other} are equal, otherwise it's false.
         */
        @NonNull
        default DynamicBool eq(@NonNull DynamicInt32 other) {
            return new ComparisonInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_EQUALS)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicInt32} and
         * {@code other} are equal, otherwise it's false.
         */
        @NonNull
        default DynamicBool eq(int other) {
            return new ComparisonInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_EQUALS)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicInt32} and
         * {@code other} are not equal, otherwise it's false.
         */
        @NonNull
        default DynamicBool ne(@NonNull DynamicInt32 other) {
            return new ComparisonInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_NOT_EQUALS)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicInt32} and
         * {@code other} are not equal, otherwise it's false.
         */
        @NonNull
        default DynamicBool ne(int other) {
            return new ComparisonInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_NOT_EQUALS)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicInt32} is
         * less than {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool lt(@NonNull DynamicInt32 other) {
            return new ComparisonInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_LESS_THAN)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicInt32} is
         * less than {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool lt(int other) {
            return new ComparisonInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_LESS_THAN)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicInt32} is
         * less than or equal to {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool lte(@NonNull DynamicInt32 other) {
            return new ComparisonInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicInt32} is
         * less than or equal to {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool lte(int other) {
            return new ComparisonInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicInt32} is
         * greater than {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool gt(@NonNull DynamicInt32 other) {
            return new ComparisonInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_GREATER_THAN)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicInt32} is
         * greater than {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool gt(int other) {
            return new ComparisonInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_GREATER_THAN)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicInt32} is
         * greater than or equal to {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool gte(@NonNull DynamicInt32 other) {
            return new ComparisonInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicInt32} is
         * greater than or equal to {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool gte(int other) {
            return new ComparisonInt32Op.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO)
                    .build();
        }

        /**
         * Returns a {@link DynamicString} that contains the formatted value of this {@link
         * DynamicInt32} (with default formatting parameters). As an example, in the English locale,
         * the following is equal to {@code DynamicString.constant("12")}
         *
         * <pre>
         *   DynamicInt32.constant(12).format()
         * </pre>
         */
        @NonNull
        default DynamicString format() {
            return IntFormatter.with().buildForInput(this);
        }

        /**
         * Returns a {@link DynamicString} that contains the formatted value of this {@link
         * DynamicInt32}. As an example, in the English locale, the following is equal to {@code
         * DynamicString.constant("0,012")}
         *
         * <pre>
         *   DynamicInt32.constant(12)
         *            .format(
         *                IntFormatter.with().minIntegerDigits(4).groupingUsed(true));
         * </pre>
         *
         * @param formatter The formatting parameter.
         */
        @NonNull
        default DynamicString format(@NonNull IntFormatter formatter) {
            return formatter.buildForInput(this);
        }

        /** Allows formatting {@link DynamicInt32} into a {@link DynamicString}. */
        class IntFormatter {
            private final Int32FormatOp.Builder builder;

            private IntFormatter() {
                builder = new Int32FormatOp.Builder();
            }

            /** Creates an instance of {@link IntFormatter} with default configuration. */
            @NonNull
            public static IntFormatter with() {
                return new IntFormatter();
            }

            /**
             * Sets minimum number of integer digits for the formatter. Defaults to one if not
             * specified.
             */
            @NonNull
            public IntFormatter minIntegerDigits(@IntRange(from = 0) int minIntegerDigits) {
                builder.setMinIntegerDigits(minIntegerDigits);
                return this;
            }

            /**
             * Sets whether grouping is used for the formatter. Defaults to false if not specified.
             */
            @NonNull
            public IntFormatter groupingUsed(boolean groupingUsed) {
                builder.setGroupingUsed(groupingUsed);
                return this;
            }

            @NonNull
            Int32FormatOp buildForInput(@NonNull DynamicInt32 dynamicInt32) {
                return builder.setInput(dynamicInt32).build();
            }
        }

        /** Get the fingerprint for this object or null if unknown. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        Fingerprint getFingerprint();

        /** Builder to create {@link DynamicInt32} objects. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        interface Builder {

            /** Builds an instance with values accumulated in this Builder. */
            @NonNull
            DynamicInt32 build();
        }
    }

    /**
     * Creates a new wrapper instance from the proto. Intended for testing purposes only. An object
     * created using this method can't be added to any other wrapper.
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @NonNull
    public static DynamicInt32 dynamicInt32FromProto(@NonNull DynamicProto.DynamicInt32 proto) {
        if (proto.hasFixed()) {
            return FixedInt32.fromProto(proto.getFixed());
        }
        if (proto.hasPlatformSource()) {
            return PlatformInt32Source.fromProto(proto.getPlatformSource());
        }
        if (proto.hasArithmeticOperation()) {
            return ArithmeticInt32Op.fromProto(proto.getArithmeticOperation());
        }
        if (proto.hasStateSource()) {
            return StateInt32Source.fromProto(proto.getStateSource());
        }
        if (proto.hasConditionalOp()) {
            return ConditionalInt32Op.fromProto(proto.getConditionalOp());
        }
        if (proto.hasFloatToInt()) {
            return FloatToInt32Op.fromProto(proto.getFloatToInt());
        }
        if (proto.hasDurationPart()) {
            return GetDurationPartOp.fromProto(proto.getDurationPart());
        }
        if (proto.hasAnimatableFixed()) {
            return AnimatableFixedInt32.fromProto(proto.getAnimatableFixed());
        }
        if (proto.hasAnimatableDynamic()) {
            return AnimatableDynamicInt32.fromProto(proto.getAnimatableDynamic());
        }
        throw new IllegalStateException("Proto was not a recognised instance of DynamicInt32");
    }

    /**
     * Simple formatting for dynamic int32.
     *
     * @since 1.2
     */
    static final class Int32FormatOp implements DynamicString {
        private final DynamicProto.Int32FormatOp mImpl;
        @Nullable private final Fingerprint mFingerprint;

        Int32FormatOp(DynamicProto.Int32FormatOp impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the source of Int32 data to convert to a string.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicInt32 getInput() {
            if (mImpl.hasInput()) {
                return DynamicBuilders.dynamicInt32FromProto(mImpl.getInput());
            } else {
                return null;
            }
        }

        /**
         * Gets minimum integer digits. Sign and grouping characters are not considered when
         * applying minIntegerDigits constraint. If not defined, defaults to one. For example,in the
         * English locale, applying minIntegerDigit=4 to 12 would yield "0012".
         *
         * @since 1.2
         */
        @IntRange(from = 0)
        public int getMinIntegerDigits() {
            return mImpl.getMinIntegerDigits();
        }

        /**
         * Gets digit grouping used. Grouping size and grouping character depend on the current
         * locale. If not defined, defaults to false. For example, in the English locale, using
         * grouping with 1234 would yield "1,234".
         *
         * @since 1.2
         */
        public boolean getGroupingUsed() {
            return mImpl.getGroupingUsed();
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static Int32FormatOp fromProto(@NonNull DynamicProto.Int32FormatOp proto) {
            return new Int32FormatOp(proto, null);
        }

        @NonNull
        DynamicProto.Int32FormatOp toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicString toDynamicStringProto() {
            return DynamicProto.DynamicString.newBuilder().setInt32FormatOp(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "Int32FormatOp{"
                    + "input="
                    + getInput()
                    + ", minIntegerDigits="
                    + getMinIntegerDigits()
                    + ", groupingUsed="
                    + getGroupingUsed()
                    + "}";
        }

        /** Builder for {@link Int32FormatOp}. */
        public static final class Builder implements DynamicString.Builder {
            private final DynamicProto.Int32FormatOp.Builder mImpl =
                    DynamicProto.Int32FormatOp.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(196209833);

            public Builder() {}

            /**
             * Sets the source of Int32 data to convert to a string.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInput(@NonNull DynamicInt32 input) {
                mImpl.setInput(input.toDynamicInt32Proto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(input.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets minimum integer digits. Sign and grouping characters are not considered when
             * applying minIntegerDigits constraint. If not defined, defaults to one. For example,in
             * the English locale, applying minIntegerDigit=4 to 12 would yield "0012".
             *
             * @since 1.2
             */
            @NonNull
            public Builder setMinIntegerDigits(@IntRange(from = 0) int minIntegerDigits) {
                mImpl.setMinIntegerDigits(minIntegerDigits);
                mFingerprint.recordPropertyUpdate(4, minIntegerDigits);
                return this;
            }

            /**
             * Sets digit grouping used. Grouping size and grouping character depend on the current
             * locale. If not defined, defaults to false. For example, in the English locale, using
             * grouping with 1234 would yield "1,234".
             *
             * @since 1.2
             */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder setGroupingUsed(boolean groupingUsed) {
                mImpl.setGroupingUsed(groupingUsed);
                mFingerprint.recordPropertyUpdate(5, Boolean.hashCode(groupingUsed));
                return this;
            }

            @Override
            @NonNull
            public Int32FormatOp build() {
                return new Int32FormatOp(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A dynamic String which sources its data from the tile's state.
     *
     * @since 1.2
     */
    static final class StateStringSource implements DynamicString {
        private final DynamicProto.StateStringSource mImpl;
        @Nullable private final Fingerprint mFingerprint;

        StateStringSource(DynamicProto.StateStringSource impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the key in the state to bind to.
         *
         * @since 1.2
         */
        @NonNull
        public String getSourceKey() {
            return mImpl.getSourceKey();
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static StateStringSource fromProto(@NonNull DynamicProto.StateStringSource proto) {
            return new StateStringSource(proto, null);
        }

        @NonNull
        DynamicProto.StateStringSource toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicString toDynamicStringProto() {
            return DynamicProto.DynamicString.newBuilder().setStateSource(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "StateStringSource{" + "sourceKey=" + getSourceKey() + "}";
        }

        /** Builder for {@link StateStringSource}. */
        public static final class Builder implements DynamicString.Builder {
            private final DynamicProto.StateStringSource.Builder mImpl =
                    DynamicProto.StateStringSource.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(1261652090);

            public Builder() {}

            /**
             * Sets the key in the state to bind to.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setSourceKey(@NonNull String sourceKey) {
                mImpl.setSourceKey(sourceKey);
                mFingerprint.recordPropertyUpdate(1, sourceKey.hashCode());
                return this;
            }

            @Override
            @NonNull
            public StateStringSource build() {
                return new StateStringSource(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A conditional operator which yields an string depending on the boolean operand. This
     * implements "string result = condition ? value_if_true : value_if_false".
     *
     * @since 1.2
     */
    static final class ConditionalStringOp implements DynamicString {
        private final DynamicProto.ConditionalStringOp mImpl;
        @Nullable private final Fingerprint mFingerprint;

        ConditionalStringOp(
                DynamicProto.ConditionalStringOp impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the condition to use.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicBool getCondition() {
            if (mImpl.hasCondition()) {
                return DynamicBuilders.dynamicBoolFromProto(mImpl.getCondition());
            } else {
                return null;
            }
        }

        /**
         * Gets the string to yield if condition is true.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicString getValueIfTrue() {
            if (mImpl.hasValueIfTrue()) {
                return DynamicBuilders.dynamicStringFromProto(mImpl.getValueIfTrue());
            } else {
                return null;
            }
        }

        /**
         * Gets the string to yield if condition is false.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicString getValueIfFalse() {
            if (mImpl.hasValueIfFalse()) {
                return DynamicBuilders.dynamicStringFromProto(mImpl.getValueIfFalse());
            } else {
                return null;
            }
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static ConditionalStringOp fromProto(@NonNull DynamicProto.ConditionalStringOp proto) {
            return new ConditionalStringOp(proto, null);
        }

        @NonNull
        DynamicProto.ConditionalStringOp toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicString toDynamicStringProto() {
            return DynamicProto.DynamicString.newBuilder().setConditionalOp(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "ConditionalStringOp{"
                    + "condition="
                    + getCondition()
                    + ", valueIfTrue="
                    + getValueIfTrue()
                    + ", valueIfFalse="
                    + getValueIfFalse()
                    + "}";
        }

        /** Builder for {@link ConditionalStringOp}. */
        public static final class Builder implements DynamicString.Builder {
            private final DynamicProto.ConditionalStringOp.Builder mImpl =
                    DynamicProto.ConditionalStringOp.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-1535849633);

            public Builder() {}

            /**
             * Sets the condition to use.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setCondition(@NonNull DynamicBool condition) {
                mImpl.setCondition(condition.toDynamicBoolProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(condition.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the string to yield if condition is true.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setValueIfTrue(@NonNull DynamicString valueIfTrue) {
                mImpl.setValueIfTrue(valueIfTrue.toDynamicStringProto());
                mFingerprint.recordPropertyUpdate(
                        2, checkNotNull(valueIfTrue.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the string to yield if condition is false.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setValueIfFalse(@NonNull DynamicString valueIfFalse) {
                mImpl.setValueIfFalse(valueIfFalse.toDynamicStringProto());
                mFingerprint.recordPropertyUpdate(
                        3, checkNotNull(valueIfFalse.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            @Override
            @NonNull
            public ConditionalStringOp build() {
                return new ConditionalStringOp(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * This implements simple string concatenation "result = LHS+RHS".
     *
     * @since 1.2
     */
    static final class ConcatStringOp implements DynamicString {

        private final DynamicProto.ConcatStringOp mImpl;
        @Nullable private final Fingerprint mFingerprint;

        ConcatStringOp(DynamicProto.ConcatStringOp impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets left hand side of the concatenation operation.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicString getInputLhs() {
            if (mImpl.hasInputLhs()) {
                return DynamicBuilders.dynamicStringFromProto(mImpl.getInputLhs());
            } else {
                return null;
            }
        }

        /**
         * Gets right hand side of the concatenation operation.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicString getInputRhs() {
            if (mImpl.hasInputRhs()) {
                return DynamicBuilders.dynamicStringFromProto(mImpl.getInputRhs());
            } else {
                return null;
            }
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static ConcatStringOp fromProto(@NonNull DynamicProto.ConcatStringOp proto) {
            return new ConcatStringOp(proto, null);
        }

        @NonNull
        DynamicProto.ConcatStringOp toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicString toDynamicStringProto() {
            return DynamicProto.DynamicString.newBuilder().setConcatOp(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "ConcatStringOp{"
                    + "inputLhs="
                    + getInputLhs()
                    + ", inputRhs="
                    + getInputRhs()
                    + "}";
        }

        /** Builder for {@link ConcatStringOp}. */
        public static final class Builder implements DynamicString.Builder {
            private final DynamicProto.ConcatStringOp.Builder mImpl =
                    DynamicProto.ConcatStringOp.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-1516620377);

            public Builder() {}

            /**
             * Sets left hand side of the concatenation operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInputLhs(@NonNull DynamicString inputLhs) {
                mImpl.setInputLhs(inputLhs.toDynamicStringProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(inputLhs.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets right hand side of the concatenation operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInputRhs(@NonNull DynamicString inputRhs) {
                mImpl.setInputRhs(inputRhs.toDynamicStringProto());
                mFingerprint.recordPropertyUpdate(
                        2, checkNotNull(inputRhs.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            @Override
            @NonNull
            public ConcatStringOp build() {
                return new ConcatStringOp(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * Simple formatting for dynamic floats.
     *
     * @since 1.2
     */
    static final class FloatFormatOp implements DynamicString {
        private final DynamicProto.FloatFormatOp mImpl;
        @Nullable private final Fingerprint mFingerprint;

        FloatFormatOp(DynamicProto.FloatFormatOp impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the source of Float data to convert to a string.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicFloat getInput() {
            if (mImpl.hasInput()) {
                return DynamicBuilders.dynamicFloatFromProto(mImpl.getInput());
            } else {
                return null;
            }
        }

        /**
         * Gets maximum fraction digits. Rounding will be applied if maxFractionDigits is smaller
         * than number of fraction digits. If not defined, defaults to three. minimumFractionDigits
         * must be <= maximumFractionDigits. If the condition is not satisfied, then
         * minimumFractionDigits will be used for both fields.
         *
         * @since 1.2
         */
        @IntRange(from = 0)
        public int getMaxFractionDigits() {
            return mImpl.getMaxFractionDigits();
        }

        /**
         * Gets minimum fraction digits. Zeros will be appended to the end to satisfy this
         * constraint. If not defined, defaults to zero. minimumFractionDigits must be <=
         * maximumFractionDigits. If the condition is not satisfied, then minimumFractionDigits will
         * be used for both fields.
         *
         * @since 1.2
         */
        @IntRange(from = 0)
        public int getMinFractionDigits() {
            return mImpl.getMinFractionDigits();
        }

        /**
         * Gets minimum integer digits. Sign and grouping characters are not considered when
         * applying minIntegerDigits constraint. If not defined, defaults to one. For example, in
         * the English locale, applying minIntegerDigit=4 to 12.34 would yield "0012.34".
         *
         * @since 1.2
         */
        @IntRange(from = 0)
        public int getMinIntegerDigits() {
            return mImpl.getMinIntegerDigits();
        }

        /**
         * Gets digit grouping used. Grouping size and grouping character depend on the current
         * locale. If not defined, defaults to false. For example, in the English locale, using
         * grouping with 1234.56 would yield "1,234.56".
         *
         * @since 1.2
         */
        public boolean getGroupingUsed() {
            return mImpl.getGroupingUsed();
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static FloatFormatOp fromProto(@NonNull DynamicProto.FloatFormatOp proto) {
            return new FloatFormatOp(proto, null);
        }

        @NonNull
        DynamicProto.FloatFormatOp toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicString toDynamicStringProto() {
            return DynamicProto.DynamicString.newBuilder().setFloatFormatOp(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "FloatFormatOp{"
                    + "input="
                    + getInput()
                    + ", maxFractionDigits="
                    + getMaxFractionDigits()
                    + ", minFractionDigits="
                    + getMinFractionDigits()
                    + ", minIntegerDigits="
                    + getMinIntegerDigits()
                    + ", groupingUsed="
                    + getGroupingUsed()
                    + "}";
        }

        /** Builder for {@link FloatFormatOp}. */
        public static final class Builder implements DynamicString.Builder {
            private final DynamicProto.FloatFormatOp.Builder mImpl =
                    DynamicProto.FloatFormatOp.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-5150153);

            public Builder() {}

            /**
             * Sets the source of Float data to convert to a string.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInput(@NonNull DynamicFloat input) {
                mImpl.setInput(input.toDynamicFloatProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(input.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets maximum fraction digits. Rounding will be applied if maxFractionDigits is
             * smaller than number of fraction digits. If not defined, defaults to three.
             * minimumFractionDigits must be <= maximumFractionDigits. If the condition is not
             * satisfied, then minimumFractionDigits will be used for both fields.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setMaxFractionDigits(@IntRange(from = 0) int maxFractionDigits) {
                mImpl.setMaxFractionDigits(maxFractionDigits);
                mFingerprint.recordPropertyUpdate(2, maxFractionDigits);
                return this;
            }

            /**
             * Sets minimum fraction digits. Zeros will be appended to the end to satisfy this
             * constraint. If not defined, defaults to zero. minimumFractionDigits must be <=
             * maximumFractionDigits. If the condition is not satisfied, then minimumFractionDigits
             * will be used for both fields.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setMinFractionDigits(@IntRange(from = 0) int minFractionDigits) {
                mImpl.setMinFractionDigits(minFractionDigits);
                mFingerprint.recordPropertyUpdate(3, minFractionDigits);
                return this;
            }

            /**
             * Sets minimum integer digits. Sign and grouping characters are not considered when
             * applying minIntegerDigits constraint. If not defined, defaults to one. For example,
             * in the English locale, applying minIntegerDigit=4 to 12.34 would yield "0012.34".
             *
             * @since 1.2
             */
            @NonNull
            public Builder setMinIntegerDigits(@IntRange(from = 0) int minIntegerDigits) {
                mImpl.setMinIntegerDigits(minIntegerDigits);
                mFingerprint.recordPropertyUpdate(4, minIntegerDigits);
                return this;
            }

            /**
             * Sets digit grouping used. Grouping size and grouping character depend on the current
             * locale. If not defined, defaults to false. For example, in the English locale, using
             * grouping with 1234.56 would yield "1,234.56".
             *
             * @since 1.2
             */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder setGroupingUsed(boolean groupingUsed) {
                mImpl.setGroupingUsed(groupingUsed);
                mFingerprint.recordPropertyUpdate(5, Boolean.hashCode(groupingUsed));
                return this;
            }

            @Override
            @NonNull
            public FloatFormatOp build() {
                return new FloatFormatOp(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * Interface defining a dynamic string type.
     *
     * @since 1.2
     */
    public interface DynamicString extends DynamicType {
        /** Get the protocol buffer representation of this object. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        DynamicProto.DynamicString toDynamicStringProto();

        /**
         * Creates a {@link DynamicString} from a byte array generated by {@link
         * #toDynamicStringByteArray()}.
         */
        @NonNull
        static DynamicString fromByteArray(@NonNull byte[] byteArray) {
            try {
                return dynamicStringFromProto(
                        DynamicProto.DynamicString.parseFrom(
                                byteArray, ExtensionRegistryLite.getEmptyRegistry()));
            } catch (InvalidProtocolBufferException e) {
                throw new IllegalArgumentException(
                        "Byte array could not be parsed into DynamicString", e);
            }
        }

        /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
        @NonNull
        default byte[] toDynamicStringByteArray() {
            return toDynamicStringProto().toByteArray();
        }

        /** Creates a constant-valued {@link DynamicString}. */
        @NonNull
        static DynamicString constant(@NonNull String constant) {
            return new FixedString.Builder().setValue(constant).build();
        }

        /**
         * Creates a {@link DynamicString} that is bound to the value of an item of the State.
         *
         * @param stateKey The key to a {@link StateEntryValue} with a string value from the
         *     provider's state.
         */
        @NonNull
        static DynamicString fromState(@NonNull String stateKey) {
            return new StateStringSource.Builder().setSourceKey(stateKey).build();
        }

        /**
         * Creates a {@link DynamicString} that is bound to the result of a conditional expression.
         * It will use the value given in either {@link ConditionScope#use} or {@link
         * ConditionScopes.IfTrueScope#elseUse} depending on the value yielded from {@code
         * condition}.
         *
         * @param condition The value used for evaluting this condition.
         */
        @NonNull
        static ConditionScope<DynamicString, String> onCondition(@NonNull DynamicBool condition) {
            return new ConditionScopes.ConditionScope<>(
                    (trueValue, falseValue) ->
                            new ConditionalStringOp.Builder()
                                    .setCondition(condition)
                                    .setValueIfTrue(trueValue)
                                    .setValueIfFalse(falseValue)
                                    .build(),
                    DynamicString::constant);
        }

        /**
         * Returns a new {@link DynamicString} that has the result of concatenating this {@link
         * DynamicString} with {@code other}. i.e. {@code result = this + other}
         *
         * @param other The right hand side operand of the concatenation.
         */
        @NonNull
        default DynamicString concat(@NonNull DynamicString other) {
            return new DynamicBuilders.ConcatStringOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .build();
        }

        /** Get the fingerprint for this object or null if unknown. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        Fingerprint getFingerprint();

        /** Builder to create {@link DynamicString} objects. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        interface Builder {

            /** Builds an instance with values accumulated in this Builder. */
            @NonNull
            DynamicString build();
        }
    }

    /**
     * Creates a new wrapper instance from the proto. Intended for testing purposes only. An object
     * created using this method can't be added to any other wrapper.
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @NonNull
    public static DynamicString dynamicStringFromProto(@NonNull DynamicProto.DynamicString proto) {
        if (proto.hasFixed()) {
            return FixedString.fromProto(proto.getFixed());
        }
        if (proto.hasInt32FormatOp()) {
            return Int32FormatOp.fromProto(proto.getInt32FormatOp());
        }
        if (proto.hasStateSource()) {
            return StateStringSource.fromProto(proto.getStateSource());
        }
        if (proto.hasConditionalOp()) {
            return ConditionalStringOp.fromProto(proto.getConditionalOp());
        }
        if (proto.hasConcatOp()) {
            return ConcatStringOp.fromProto(proto.getConcatOp());
        }
        if (proto.hasFloatFormatOp()) {
            return FloatFormatOp.fromProto(proto.getFloatFormatOp());
        }
        throw new IllegalStateException("Proto was not a recognised instance of DynamicString");
    }

    /**
     * An arithmetic operation, operating on two Float instances. This implements simple binary
     * operations of the form "result = LHS <op> RHS", where the available operation types are
     * described in {@code ArithmeticOpType}.
     *
     * @since 1.2
     */
    static final class ArithmeticFloatOp implements DynamicFloat {

        private final DynamicProto.ArithmeticFloatOp mImpl;
        @Nullable private final Fingerprint mFingerprint;

        ArithmeticFloatOp(DynamicProto.ArithmeticFloatOp impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets left hand side of the arithmetic operation.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicFloat getInputLhs() {
            if (mImpl.hasInputLhs()) {
                return DynamicBuilders.dynamicFloatFromProto(mImpl.getInputLhs());
            } else {
                return null;
            }
        }

        /**
         * Gets right hand side of the arithmetic operation.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicFloat getInputRhs() {
            if (mImpl.hasInputRhs()) {
                return DynamicBuilders.dynamicFloatFromProto(mImpl.getInputRhs());
            } else {
                return null;
            }
        }

        /**
         * Gets the type of operation to carry out.
         *
         * @since 1.2
         */
        @ArithmeticOpType
        public int getOperationType() {
            return mImpl.getOperationType().getNumber();
        }

        /** */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static ArithmeticFloatOp fromProto(@NonNull DynamicProto.ArithmeticFloatOp proto) {
            return new ArithmeticFloatOp(proto, null);
        }

        @NonNull
        DynamicProto.ArithmeticFloatOp toProto() {
            return mImpl;
        }

        /** */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicFloat toDynamicFloatProto() {
            return DynamicProto.DynamicFloat.newBuilder().setArithmeticOperation(mImpl).build();
        }

        /** Builder for {@link ArithmeticFloatOp}. */
        public static final class Builder implements DynamicFloat.Builder {

            private final DynamicProto.ArithmeticFloatOp.Builder mImpl =
                    DynamicProto.ArithmeticFloatOp.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-1818249334);

            public Builder() {}

            /**
             * Sets left hand side of the arithmetic operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInputLhs(@NonNull DynamicFloat inputLhs) {
                mImpl.setInputLhs(inputLhs.toDynamicFloatProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(inputLhs.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets right hand side of the arithmetic operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInputRhs(@NonNull DynamicFloat inputRhs) {
                mImpl.setInputRhs(inputRhs.toDynamicFloatProto());
                mFingerprint.recordPropertyUpdate(
                        2, checkNotNull(inputRhs.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the type of operation to carry out.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setOperationType(@ArithmeticOpType int operationType) {
                mImpl.setOperationType(DynamicProto.ArithmeticOpType.forNumber(operationType));
                mFingerprint.recordPropertyUpdate(3, operationType);
                return this;
            }

            @Override
            @NonNull
            public ArithmeticFloatOp build() {
                return new ArithmeticFloatOp(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A dynamic Float which sources its data from the tile's state.
     *
     * @since 1.2
     */
    static final class StateFloatSource implements DynamicFloat {
        private final DynamicProto.StateFloatSource mImpl;
        @Nullable private final Fingerprint mFingerprint;

        StateFloatSource(DynamicProto.StateFloatSource impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the key in the state to bind to.
         *
         * @since 1.2
         */
        @NonNull
        public String getSourceKey() {
            return mImpl.getSourceKey();
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static StateFloatSource fromProto(@NonNull DynamicProto.StateFloatSource proto) {
            return new StateFloatSource(proto, null);
        }

        @NonNull
        DynamicProto.StateFloatSource toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicFloat toDynamicFloatProto() {
            return DynamicProto.DynamicFloat.newBuilder().setStateSource(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "StateFloatSource{" + "sourceKey=" + getSourceKey() + "}";
        }

        /** Builder for {@link StateFloatSource}. */
        public static final class Builder implements DynamicFloat.Builder {
            private final DynamicProto.StateFloatSource.Builder mImpl =
                    DynamicProto.StateFloatSource.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(384370154);

            public Builder() {}

            /**
             * Sets the key in the state to bind to.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setSourceKey(@NonNull String sourceKey) {
                mImpl.setSourceKey(sourceKey);
                mFingerprint.recordPropertyUpdate(1, sourceKey.hashCode());
                return this;
            }

            @Override
            @NonNull
            public StateFloatSource build() {
                return new StateFloatSource(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * An operation to convert an Int32 value in the dynamic data pipeline to a Float value.
     *
     * @since 1.2
     */
    static final class Int32ToFloatOp implements DynamicFloat {
        private final DynamicProto.Int32ToFloatOp mImpl;
        @Nullable private final Fingerprint mFingerprint;

        Int32ToFloatOp(DynamicProto.Int32ToFloatOp impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the input Int32 to convert to a Float.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicInt32 getInput() {
            if (mImpl.hasInput()) {
                return DynamicBuilders.dynamicInt32FromProto(mImpl.getInput());
            } else {
                return null;
            }
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static Int32ToFloatOp fromProto(@NonNull DynamicProto.Int32ToFloatOp proto) {
            return new Int32ToFloatOp(proto, null);
        }

        @NonNull
        DynamicProto.Int32ToFloatOp toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicFloat toDynamicFloatProto() {
            return DynamicProto.DynamicFloat.newBuilder().setInt32ToFloatOperation(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "Int32ToFloatOp{" + "input=" + getInput() + "}";
        }

        /** Builder for {@link Int32ToFloatOp}. */
        public static final class Builder implements DynamicFloat.Builder {
            private final DynamicProto.Int32ToFloatOp.Builder mImpl =
                    DynamicProto.Int32ToFloatOp.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-619592745);

            public Builder() {}

            /**
             * Sets the input Int32 to convert to a Float.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInput(@NonNull DynamicInt32 input) {
                mImpl.setInput(input.toDynamicInt32Proto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(input.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            @Override
            @NonNull
            public Int32ToFloatOp build() {
                return new Int32ToFloatOp(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A static interpolation node, between two fixed floating point values.
     *
     * @since 1.2
     */
    static final class AnimatableFixedFloat implements DynamicFloat {
        private final DynamicProto.AnimatableFixedFloat mImpl;
        @Nullable private final Fingerprint mFingerprint;

        AnimatableFixedFloat(
                DynamicProto.AnimatableFixedFloat impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the number to start animating from.
         *
         * @since 1.2
         */
        public float getFromValue() {
            return mImpl.getFromValue();
        }

        /**
         * Gets the number to animate to.
         *
         * @since 1.2
         */
        public float getToValue() {
            return mImpl.getToValue();
        }

        /**
         * Gets the animation parameters for duration, delay, etc.
         *
         * @since 1.2
         */
        @Nullable
        public AnimationSpec getAnimationSpec() {
            if (mImpl.hasAnimationSpec()) {
                return AnimationSpec.fromProto(mImpl.getAnimationSpec());
            } else {
                return null;
            }
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static AnimatableFixedFloat fromProto(@NonNull DynamicProto.AnimatableFixedFloat proto) {
            return new AnimatableFixedFloat(proto, null);
        }

        @NonNull
        DynamicProto.AnimatableFixedFloat toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicFloat toDynamicFloatProto() {
            return DynamicProto.DynamicFloat.newBuilder().setAnimatableFixed(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "AnimatableFixedFloat{"
                    + "fromValue="
                    + getFromValue()
                    + ", toValue="
                    + getToValue()
                    + ", animationSpec="
                    + getAnimationSpec()
                    + "}";
        }

        /** Builder for {@link AnimatableFixedFloat}. */
        public static final class Builder implements DynamicFloat.Builder {
            private final DynamicProto.AnimatableFixedFloat.Builder mImpl =
                    DynamicProto.AnimatableFixedFloat.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(1964707538);

            public Builder() {}

            /**
             * Sets the number to start animating from.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setFromValue(float fromValue) {
                mImpl.setFromValue(fromValue);
                mFingerprint.recordPropertyUpdate(1, Float.floatToIntBits(fromValue));
                return this;
            }

            /**
             * Sets the number to animate to.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setToValue(float toValue) {
                mImpl.setToValue(toValue);
                mFingerprint.recordPropertyUpdate(2, Float.floatToIntBits(toValue));
                return this;
            }

            /**
             * Sets the animation parameters for duration, delay, etc.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
                mImpl.setAnimationSpec(animationSpec.toProto());
                mFingerprint.recordPropertyUpdate(
                        3, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            @Override
            @NonNull
            public AnimatableFixedFloat build() {
                return new AnimatableFixedFloat(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A dynamic interpolation node. This will watch the value of its input and, when the first
     * update arrives, immediately emit that value. On subsequent updates, it will animate between
     * the old and new values.
     *
     * <p>If this node receives an invalid value (e.g. as a result of an upstream node having no
     * value), then it will emit a single invalid value, and forget its "stored" value. The next
     * valid value that arrives is then used as the "first" value again.
     *
     * @since 1.2
     */
    static final class AnimatableDynamicFloat implements DynamicFloat {
        private final DynamicProto.AnimatableDynamicFloat mImpl;
        @Nullable private final Fingerprint mFingerprint;

        AnimatableDynamicFloat(
                DynamicProto.AnimatableDynamicFloat impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the value to watch, and animate when it changes.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicFloat getInput() {
            if (mImpl.hasInput()) {
                return DynamicBuilders.dynamicFloatFromProto(mImpl.getInput());
            } else {
                return null;
            }
        }

        /**
         * Gets the animation parameters for duration, delay, etc.
         *
         * @since 1.2
         */
        @Nullable
        public AnimationSpec getAnimationSpec() {
            if (mImpl.hasAnimationSpec()) {
                return AnimationSpec.fromProto(mImpl.getAnimationSpec());
            } else {
                return null;
            }
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static AnimatableDynamicFloat fromProto(
                @NonNull DynamicProto.AnimatableDynamicFloat proto) {
            return new AnimatableDynamicFloat(proto, null);
        }

        @NonNull
        DynamicProto.AnimatableDynamicFloat toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicFloat toDynamicFloatProto() {
            return DynamicProto.DynamicFloat.newBuilder().setAnimatableDynamic(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "AnimatableDynamicFloat{"
                    + "input="
                    + getInput()
                    + ", animationSpec="
                    + getAnimationSpec()
                    + "}";
        }

        /** Builder for {@link AnimatableDynamicFloat}. */
        public static final class Builder implements DynamicFloat.Builder {
            private final DynamicProto.AnimatableDynamicFloat.Builder mImpl =
                    DynamicProto.AnimatableDynamicFloat.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(1543182280);

            public Builder() {}

            /**
             * Sets the value to watch, and animate when it changes.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInput(@NonNull DynamicFloat input) {
                mImpl.setInput(input.toDynamicFloatProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(input.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the animation parameters for duration, delay, etc.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
                mImpl.setAnimationSpec(animationSpec.toProto());
                mFingerprint.recordPropertyUpdate(
                        3, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            @Override
            @NonNull
            public AnimatableDynamicFloat build() {
                return new AnimatableDynamicFloat(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * Interface defining a dynamic float type.
     *
     * <p>It offers a set of helper methods for creating arithmetic and logical expressions, e.g.
     * {@link #plus(float)}, {@link #times(float)}, {@link #eq(float)}, etc. These helper methods
     * produce expression trees based on the order in which they were called in an expression. Thus,
     * no operator precedence rules are applied.
     *
     * <p>For example the following expression is equivalent to {@code result = ((a + b)*c)/d }:
     *
     * <pre>{@code
     * a.plus(b).times(c).div(d);
     * }</pre>
     *
     * More complex expressions can be created by nesting expressions. For example the following
     * expression is equivalent to {@code result = (a + b)*(c - d) }:
     *
     * <pre>{@code
     * (a.plus(b)).times(c.minus(d));
     * }</pre>
     *
     * @since 1.2
     */
    public interface DynamicFloat extends DynamicType {
        /** Get the protocol buffer representation of this object. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        DynamicProto.DynamicFloat toDynamicFloatProto();

        /**
         * Creates a {@link DynamicFloat} from a byte array generated by {@link
         * #toDynamicFloatByteArray()}.
         */
        @NonNull
        static DynamicFloat fromByteArray(@NonNull byte[] byteArray) {
            try {
                return dynamicFloatFromProto(
                        DynamicProto.DynamicFloat.parseFrom(
                                byteArray, ExtensionRegistryLite.getEmptyRegistry()));
            } catch (InvalidProtocolBufferException e) {
                throw new IllegalArgumentException(
                        "Byte array could not be parsed into DynamicFloat", e);
            }
        }

        /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
        @NonNull
        default byte[] toDynamicFloatByteArray() {
            return toDynamicFloatProto().toByteArray();
        }

        /** Creates a constant-valued {@link DynamicFloat}. */
        @NonNull
        static DynamicFloat constant(float constant) {
            return new FixedFloat.Builder().setValue(constant).build();
        }

        /**
         * Creates a {@link DynamicFloat} that is bound to the value of an item of the State.
         *
         * @param stateKey The key to a {@link StateEntryValue} with a float value from the
         *     provider's state.
         */
        @NonNull
        static DynamicFloat fromState(@NonNull String stateKey) {
            return new StateFloatSource.Builder().setSourceKey(stateKey).build();
        }

        /**
         * Creates a {@link DynamicFloat} which will animate over the range of floats from {@code
         * start} to {@code end}.
         *
         * @param start The start value of the range.
         * @param end The end value of the range.
         */
        @NonNull
        static DynamicFloat animate(float start, float end) {
            return new AnimatableFixedFloat.Builder().setFromValue(start).setToValue(end).build();
        }

        /**
         * Creates a {@link DynamicFloat} which will animate over the range of floats from {@code
         * start} to {@code end} with the given animation parameters.
         *
         * @param start The start value of the range.
         * @param end The end value of the range.
         * @param animationSpec The animation parameters.
         */
        @NonNull
        static DynamicFloat animate(float start, float end, @NonNull AnimationSpec animationSpec) {
            return new AnimatableFixedFloat.Builder()
                    .setFromValue(start)
                    .setToValue(end)
                    .setAnimationSpec(animationSpec)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} that is bound to the value of an item of the State. Every
         * time the state value changes, this {@link DynamicFloat} will animate from its current
         * value to the new value (from the state).
         *
         * @param stateKey The key to a {@link StateEntryValue} with a float value from the
         *     providers state.
         */
        @NonNull
        static DynamicFloat animate(@NonNull String stateKey) {
            return new AnimatableDynamicFloat.Builder().setInput(fromState(stateKey)).build();
        }

        /**
         * Creates a {@link DynamicFloat} that is bound to the value of an item of the State. Every
         * time the state value changes, this {@link DynamicFloat} will animate from its current
         * value to the new value (from the state).
         *
         * @param stateKey The key to a {@link StateEntryValue} with a float value from the
         *     providers state.
         * @param animationSpec The animation parameters.
         */
        @NonNull
        static DynamicFloat animate(
                @NonNull String stateKey, @NonNull AnimationSpec animationSpec) {
            return new AnimatableDynamicFloat.Builder()
                    .setInput(fromState(stateKey))
                    .setAnimationSpec(animationSpec)
                    .build();
        }

        /**
         * Returns a {@link DynamicFloat} that is bound to the value of this {@link DynamicFloat}
         * and every time its value is changing, it animates from its current value to the new
         * value.
         *
         * @param animationSpec The animation parameters.
         */
        @NonNull
        default DynamicFloat animate(@NonNull AnimationSpec animationSpec) {
            return new AnimatableDynamicFloat.Builder()
                    .setInput(this)
                    .setAnimationSpec(animationSpec)
                    .build();
        }

        /**
         * Returns a {@link DynamicFloat} that is bound to the value of this {@link DynamicFloat}
         * and every time its value is changing, it animates from its current value to the new
         * value.
         */
        @NonNull
        default DynamicFloat animate() {
            return new AnimatableDynamicFloat.Builder().setInput(this).build();
        }

        /**
         * 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)}
         */
        @NonNull
        default DynamicInt32 asInt() {
            return new FloatToInt32Op.Builder()
                    .setRoundMode(DynamicBuilders.ROUND_MODE_FLOOR)
                    .setInput(this)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of adding another {@link
         * DynamicFloat} to this {@link DynamicFloat}; As an example, the following is equal to
         * {@code DynamicFloat.constant(13f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).plus(DynamicFloat.constant(5f));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat plus(@NonNull DynamicFloat other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_ADD)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of adding a float to this {@link
         * DynamicFloat}; As an example, the following is equal to {@code
         * DynamicFloat.constant(13f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).plus(5f);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat plus(float other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_ADD)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of adding a {@link DynamicInt32} to
         * this {@link DynamicFloat}; As an example, the following is equal to {@code
         * DynamicFloat.constant(13f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).plus(DynamicInt32.constant(5));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat plus(@NonNull DynamicInt32 other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other.asFloat())
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_ADD)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of subtracting another {@link
         * DynamicFloat} from this {@link DynamicFloat}; As an example, the following is equal to
         * {@code DynamicFloat.constant(2f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).minus(DynamicFloat.constant(5f));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat minus(@NonNull DynamicFloat other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_SUBTRACT)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of subtracting a flaot from this
         * {@link DynamicFloat}; As an example, the following is equal to {@code
         * DynamicFloat.constant(2f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).minus(5f);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat minus(float other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_SUBTRACT)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of subtracting a {@link
         * DynamicInt32} from this {@link DynamicFloat}; As an example, the following is equal to
         * {@code DynamicFloat.constant(2f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).minus(DynamicInt32.constant(5));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat minus(@NonNull DynamicInt32 other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other.asFloat())
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_SUBTRACT)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of multiplying this {@link
         * DynamicFloat} by another {@link DynamicFloat}; As an example, the following is equal to
         * {@code DynamicFloat.constant(35f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).times(DynamicFloat.constant(5f));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat times(@NonNull DynamicFloat other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MULTIPLY)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of multiplying this {@link
         * DynamicFloat} by a flaot; As an example, the following is equal to {@code
         * DynamicFloat.constant(35f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).times(5f);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat times(float other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MULTIPLY)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of multiplying this {@link
         * DynamicFloat} by a {@link DynamicInt32}; As an example, the following is equal to {@code
         * DynamicFloat.constant(35f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).times(DynamicInt32.constant(5));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat times(@NonNull DynamicInt32 other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other.asFloat())
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MULTIPLY)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of dividing this {@link
         * DynamicFloat} by another {@link DynamicFloat}; As an example, the following is equal to
         * {@code DynamicFloat.constant(1.4f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).div(DynamicFloat.constant(5f));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat div(@NonNull DynamicFloat other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_DIVIDE)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of dividing this {@link
         * DynamicFloat} by a float; As an example, the following is equal to {@code
         * DynamicFloat.constant(1.4f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).div(5f);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat div(float other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_DIVIDE)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the result of dividing this {@link
         * DynamicFloat} by a {@link DynamicInt32}; As an example, the following is equal to {@code
         * DynamicFloat.constant(1.4f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).div(DynamicInt32.constant(5));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat div(@NonNull DynamicInt32 other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other.asFloat())
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_DIVIDE)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the reminder of dividing this {@link
         * DynamicFloat} by another {@link DynamicFloat}; As an example, the following is equal to
         * {@code DynamicFloat.constant(1.5f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).rem(DynamicFloat.constant(5.5f));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat rem(@NonNull DynamicFloat other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MODULO)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the reminder of dividing this {@link
         * DynamicFloat} by a float; As an example, the following is equal to {@code
         * DynamicFloat.constant(1.5f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).rem(5.5f);
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat rem(float other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MODULO)
                    .build();
        }

        /**
         * Creates a {@link DynamicFloat} containing the reminder of dividing this {@link
         * DynamicFloat} by a {@link DynamicInt32}; As an example, the following is equal to {@code
         * DynamicFloat.constant(2f)}
         *
         * <pre>
         *   DynamicFloat.constant(7f).rem(DynamicInt32.constant(5));
         * </pre>
         *
         * The operation's evaluation order depends only on its position in the expression; no
         * operator precedence rules are applied. See {@link DynamicFloat} for more information on
         * operation evaluation order.
         *
         * @return a new instance of {@link DynamicFloat} containing the result of the operation.
         */
        @SuppressWarnings("KotlinOperator")
        @NonNull
        default DynamicFloat rem(@NonNull DynamicInt32 other) {
            return new ArithmeticFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other.asFloat())
                    .setOperationType(DynamicBuilders.ARITHMETIC_OP_TYPE_MODULO)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicFloat} and
         * {@code other} are equal, otherwise it's false.
         */
        @NonNull
        default DynamicBool eq(@NonNull DynamicFloat other) {
            return new ComparisonFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_EQUALS)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicFloat} and
         * {@code other} are equal, otherwise it's false.
         */
        @NonNull
        default DynamicBool eq(float other) {
            return new ComparisonFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_EQUALS)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicFloat} and
         * {@code other} are not equal, otherwise it's false.
         */
        @NonNull
        default DynamicBool ne(@NonNull DynamicFloat other) {
            return new ComparisonFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_NOT_EQUALS)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicFloat} and
         * {@code other} are not equal, otherwise it's false.
         */
        @NonNull
        default DynamicBool ne(float other) {
            return new ComparisonFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_NOT_EQUALS)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicFloat} is
         * less than {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool lt(@NonNull DynamicFloat other) {
            return new ComparisonFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_LESS_THAN)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicFloat} is
         * less than {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool lt(float other) {
            return new ComparisonFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_LESS_THAN)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicFloat} is
         * less than or equal to {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool lte(@NonNull DynamicFloat other) {
            return new ComparisonFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicFloat} is
         * less than or equal to {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool lte(float other) {
            return new ComparisonFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicFloat} is
         * greater than {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool gt(@NonNull DynamicFloat other) {
            return new ComparisonFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_GREATER_THAN)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicFloat} is
         * greater than {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool gt(float other) {
            return new ComparisonFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_GREATER_THAN)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicFloat} is
         * greater than or equal to {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool gte(@NonNull DynamicFloat other) {
            return new ComparisonFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(other)
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if the value of this {@link DynamicFloat} is
         * greater than or equal to {@code other}, otherwise it's false.
         */
        @NonNull
        default DynamicBool gte(float other) {
            return new ComparisonFloatOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(constant(other))
                    .setOperationType(DynamicBuilders.COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO)
                    .build();
        }

        /**
         * Bind the value of this {@link DynamicFloat} to the result of a conditional expression.
         * This will use the value given in either {@link ConditionScope#use} or {@link
         * ConditionScopes.IfTrueScope#elseUse} depending on the value yielded from {@code
         * condition}.
         */
        @NonNull
        static ConditionScope<DynamicFloat, Float> onCondition(@NonNull DynamicBool condition) {
            return new ConditionScopes.ConditionScope<>(
                    (trueValue, falseValue) ->
                            new ConditionalFloatOp.Builder()
                                    .setCondition(condition)
                                    .setValueIfTrue(trueValue)
                                    .setValueIfFalse(falseValue)
                                    .build(),
                    DynamicFloat::constant);
        }

        /**
         * Returns a {@link DynamicString} that contains the formatted value of this {@link
         * DynamicFloat} (with default formatting parameters). As an example, in the English locale,
         * the following is equal to {@code DynamicString.constant("12.346")}
         *
         * <pre>
         *   DynamicFloat.constant(12.34567f).format();
         * </pre>
         */
        @NonNull
        default DynamicString format() {
            return FloatFormatter.with().buildForInput(this);
        }

        /**
         * Returns a {@link DynamicString} that contains the formatted value of this {@link
         * DynamicFloat}. As an example, in the English locale, the following is equal to {@code
         * DynamicString.constant("0,012.34")}
         *
         * <pre>
         *   DynamicFloat.constant(12.345f)
         *       .format(
         *           FloatFormatter.with().maxFractionDigits(2).minIntegerDigits(4)
         *                         .groupingUsed(true));
         * </pre>
         *
         * @param formatter The formatting parameter.
         */
        @NonNull
        default DynamicString format(@NonNull FloatFormatter formatter) {
            return formatter.buildForInput(this);
        }

        /** Allows formatting {@link DynamicFloat} into a {@link DynamicString}. */
        class FloatFormatter {
            private final FloatFormatOp.Builder builder;

            private FloatFormatter() {
                builder = new FloatFormatOp.Builder();
            }

            /** Creates an instance of {@link FloatFormatter} with default configuration. */
            @NonNull
            public static FloatFormatter with() {
                return new FloatFormatter();
            }

            /**
             * Sets minimum number of fraction digits for the formatter. Defaults to zero if not
             * specified. minimumFractionDigits must be <= maximumFractionDigits. If the condition
             * is not satisfied, then minimumFractionDigits will be used for both fields.
             */
            @NonNull
            public FloatFormatter minFractionDigits(@IntRange(from = 0) int minFractionDigits) {
                builder.setMinFractionDigits(minFractionDigits);
                return this;
            }

            /**
             * Sets maximum number of fraction digits for the formatter. Defaults to three if not
             * specified. minimumFractionDigits must be <= maximumFractionDigits. If the condition
             * is not satisfied, then minimumFractionDigits will be used for both fields.
             */
            @NonNull
            public FloatFormatter maxFractionDigits(@IntRange(from = 0) int maxFractionDigits) {
                builder.setMaxFractionDigits(maxFractionDigits);
                return this;
            }

            /**
             * Sets minimum number of integer digits for the formatter. Defaults to one if not
             * specified.
             */
            @NonNull
            public FloatFormatter minIntegerDigits(@IntRange(from = 0) int minIntegerDigits) {
                builder.setMinIntegerDigits(minIntegerDigits);
                return this;
            }

            /**
             * Sets whether grouping is used for the formatter. Defaults to false if not specified.
             */
            @NonNull
            public FloatFormatter groupingUsed(boolean groupingUsed) {
                builder.setGroupingUsed(groupingUsed);
                return this;
            }

            @NonNull
            FloatFormatOp buildForInput(@NonNull DynamicFloat dynamicFloat) {
                return builder.setInput(dynamicFloat).build();
            }
        }

        /** Get the fingerprint for this object or null if unknown. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        Fingerprint getFingerprint();

        /** Builder to create {@link DynamicFloat} objects. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        interface Builder {

            /** Builds an instance with values accumulated in this Builder. */
            @NonNull
            DynamicFloat build();
        }
    }

    /**
     * Creates a new wrapper instance from the proto. Intended for testing purposes only. An object
     * created using this method can't be added to any other wrapper.
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @NonNull
    public static DynamicFloat dynamicFloatFromProto(@NonNull DynamicProto.DynamicFloat proto) {
        if (proto.hasFixed()) {
            return FixedFloat.fromProto(proto.getFixed());
        }
        if (proto.hasArithmeticOperation()) {
            return ArithmeticFloatOp.fromProto(proto.getArithmeticOperation());
        }
        if (proto.hasInt32ToFloatOperation()) {
            return Int32ToFloatOp.fromProto(proto.getInt32ToFloatOperation());
        }
        if (proto.hasStateSource()) {
            return StateFloatSource.fromProto(proto.getStateSource());
        }
        if (proto.hasConditionalOp()) {
            return ConditionalFloatOp.fromProto(proto.getConditionalOp());
        }
        if (proto.hasAnimatableFixed()) {
            return AnimatableFixedFloat.fromProto(proto.getAnimatableFixed());
        }
        if (proto.hasAnimatableDynamic()) {
            return AnimatableDynamicFloat.fromProto(proto.getAnimatableDynamic());
        }
        throw new IllegalStateException("Proto was not a recognised instance of DynamicFloat");
    }

    /**
     * A dynamic boolean type which sources its data from the tile's state.
     *
     * @since 1.2
     */
    static final class StateBoolSource implements DynamicBool {
        private final DynamicProto.StateBoolSource mImpl;
        @Nullable private final Fingerprint mFingerprint;

        StateBoolSource(DynamicProto.StateBoolSource impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the key in the state to bind to.
         *
         * @since 1.2
         */
        @NonNull
        public String getSourceKey() {
            return mImpl.getSourceKey();
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static StateBoolSource fromProto(@NonNull DynamicProto.StateBoolSource proto) {
            return new StateBoolSource(proto, null);
        }

        @NonNull
        DynamicProto.StateBoolSource toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicBool toDynamicBoolProto() {
            return DynamicProto.DynamicBool.newBuilder().setStateSource(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "StateBoolSource{" + "sourceKey=" + getSourceKey() + "}";
        }

        /** Builder for {@link StateBoolSource}. */
        public static final class Builder implements DynamicBool.Builder {

            private final DynamicProto.StateBoolSource.Builder mImpl =
                    DynamicProto.StateBoolSource.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(1818702779);

            public Builder() {}

            /**
             * Sets the key in the state to bind to.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setSourceKey(@NonNull String sourceKey) {
                mImpl.setSourceKey(sourceKey);
                mFingerprint.recordPropertyUpdate(1, sourceKey.hashCode());
                return this;
            }

            @Override
            @NonNull
            public StateBoolSource build() {
                return new StateBoolSource(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A comparison operation, operating on two Int32 instances. This implements various comparison
     * operations of the form "boolean result = LHS <op> RHS", where the available operation types
     * are described in {@code ComparisonOpType}.
     *
     * @since 1.2
     */
    static final class ComparisonInt32Op implements DynamicBool {

        private final DynamicProto.ComparisonInt32Op mImpl;
        @Nullable private final Fingerprint mFingerprint;

        ComparisonInt32Op(DynamicProto.ComparisonInt32Op impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the left hand side of the comparison operation.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicInt32 getInputLhs() {
            if (mImpl.hasInputLhs()) {
                return DynamicBuilders.dynamicInt32FromProto(mImpl.getInputLhs());
            } else {
                return null;
            }
        }

        /**
         * Gets the right hand side of the comparison operation.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicInt32 getInputRhs() {
            if (mImpl.hasInputRhs()) {
                return DynamicBuilders.dynamicInt32FromProto(mImpl.getInputRhs());
            } else {
                return null;
            }
        }

        /**
         * Gets the type of the operation.
         *
         * @since 1.2
         */
        @ComparisonOpType
        public int getOperationType() {
            return mImpl.getOperationType().getNumber();
        }

        /** */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static ComparisonInt32Op fromProto(@NonNull DynamicProto.ComparisonInt32Op proto) {
            return new ComparisonInt32Op(proto, null);
        }

        @NonNull
        DynamicProto.ComparisonInt32Op toProto() {
            return mImpl;
        }

        /** */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicBool toDynamicBoolProto() {
            return DynamicProto.DynamicBool.newBuilder().setInt32Comparison(mImpl).build();
        }

        /** Builder for {@link ComparisonInt32Op}. */
        public static final class Builder implements DynamicBool.Builder {

            private final DynamicProto.ComparisonInt32Op.Builder mImpl =
                    DynamicProto.ComparisonInt32Op.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-1112207999);

            public Builder() {}

            /**
             * Sets the left hand side of the comparison operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInputLhs(@NonNull DynamicInt32 inputLhs) {
                mImpl.setInputLhs(inputLhs.toDynamicInt32Proto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(inputLhs.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the right hand side of the comparison operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInputRhs(@NonNull DynamicInt32 inputRhs) {
                mImpl.setInputRhs(inputRhs.toDynamicInt32Proto());
                mFingerprint.recordPropertyUpdate(
                        2, checkNotNull(inputRhs.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the type of the operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setOperationType(@ComparisonOpType int operationType) {
                mImpl.setOperationType(DynamicProto.ComparisonOpType.forNumber(operationType));
                mFingerprint.recordPropertyUpdate(3, operationType);
                return this;
            }

            @Override
            @NonNull
            public ComparisonInt32Op build() {
                return new ComparisonInt32Op(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A comparison operation, operating on two Float instances. This implements various comparison
     * operations of the form "boolean result = LHS <op> RHS", where the available operation types
     * are described in {@code ComparisonOpType}.
     *
     * @since 1.2
     */
    static final class ComparisonFloatOp implements DynamicBool {

        private final DynamicProto.ComparisonFloatOp mImpl;
        @Nullable private final Fingerprint mFingerprint;

        ComparisonFloatOp(DynamicProto.ComparisonFloatOp impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the left hand side of the comparison operation.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicFloat getInputLhs() {
            if (mImpl.hasInputLhs()) {
                return DynamicBuilders.dynamicFloatFromProto(mImpl.getInputLhs());
            } else {
                return null;
            }
        }

        /**
         * Gets the right hand side of the comparison operation.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicFloat getInputRhs() {
            if (mImpl.hasInputRhs()) {
                return DynamicBuilders.dynamicFloatFromProto(mImpl.getInputRhs());
            } else {
                return null;
            }
        }

        /**
         * Gets the type of the operation.
         *
         * @since 1.2
         */
        @ComparisonOpType
        public int getOperationType() {
            return mImpl.getOperationType().getNumber();
        }

        /** */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static ComparisonFloatOp fromProto(@NonNull DynamicProto.ComparisonFloatOp proto) {
            return new ComparisonFloatOp(proto, null);
        }

        @NonNull
        DynamicProto.ComparisonFloatOp toProto() {
            return mImpl;
        }

        /** */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicBool toDynamicBoolProto() {
            return DynamicProto.DynamicBool.newBuilder().setFloatComparison(mImpl).build();
        }

        /** Builder for {@link ComparisonFloatOp}. */
        public static final class Builder implements DynamicBool.Builder {

            private final DynamicProto.ComparisonFloatOp.Builder mImpl =
                    DynamicProto.ComparisonFloatOp.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-1679565270);

            public Builder() {}

            /**
             * Sets the left hand side of the comparison operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInputLhs(@NonNull DynamicFloat inputLhs) {
                mImpl.setInputLhs(inputLhs.toDynamicFloatProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(inputLhs.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the right hand side of the comparison operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInputRhs(@NonNull DynamicFloat inputRhs) {
                mImpl.setInputRhs(inputRhs.toDynamicFloatProto());
                mFingerprint.recordPropertyUpdate(
                        2, checkNotNull(inputRhs.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the type of the operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setOperationType(@ComparisonOpType int operationType) {
                mImpl.setOperationType(DynamicProto.ComparisonOpType.forNumber(operationType));
                mFingerprint.recordPropertyUpdate(3, operationType);
                return this;
            }

            @Override
            @NonNull
            public ComparisonFloatOp build() {
                return new ComparisonFloatOp(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A boolean operation which implements a "NOT" operator, i.e. "boolean result = !input".
     *
     * @since 1.2
     */
    static final class NotBoolOp implements DynamicBool {
        private final DynamicProto.NotBoolOp mImpl;
        @Nullable private final Fingerprint mFingerprint;

        NotBoolOp(DynamicProto.NotBoolOp impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the input, whose value to negate.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicBool getInput() {
            if (mImpl.hasInput()) {
                return DynamicBuilders.dynamicBoolFromProto(mImpl.getInput());
            } else {
                return null;
            }
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static NotBoolOp fromProto(@NonNull DynamicProto.NotBoolOp proto) {
            return new NotBoolOp(proto, null);
        }

        @NonNull
        DynamicProto.NotBoolOp toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicBool toDynamicBoolProto() {
            return DynamicProto.DynamicBool.newBuilder().setNotOp(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "NotBoolOp{" + "input=" + getInput() + "}";
        }

        /** Builder for {@link NotBoolOp}. */
        public static final class Builder implements DynamicBool.Builder {
            private final DynamicProto.NotBoolOp.Builder mImpl =
                    DynamicProto.NotBoolOp.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(91300638);

            public Builder() {}

            /**
             * Sets the input, whose value to negate.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInput(@NonNull DynamicBool input) {
                mImpl.setInput(input.toDynamicBoolProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(input.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            @Override
            @NonNull
            public NotBoolOp build() {
                return new NotBoolOp(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A logical boolean operator, implementing "boolean result = LHS <op> RHS", for various boolean
     * operators (i.e. AND/OR).
     *
     * @since 1.2
     */
    static final class LogicalBoolOp implements DynamicBool {
        private final DynamicProto.LogicalBoolOp mImpl;
        @Nullable private final Fingerprint mFingerprint;

        LogicalBoolOp(DynamicProto.LogicalBoolOp impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the left hand side of the logical operation.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicBool getInputLhs() {
            if (mImpl.hasInputLhs()) {
                return DynamicBuilders.dynamicBoolFromProto(mImpl.getInputLhs());
            } else {
                return null;
            }
        }

        /**
         * Gets the right hand side of the logical operation.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicBool getInputRhs() {
            if (mImpl.hasInputRhs()) {
                return DynamicBuilders.dynamicBoolFromProto(mImpl.getInputRhs());
            } else {
                return null;
            }
        }

        /**
         * Gets the operation type to apply to LHS/RHS.
         *
         * @since 1.2
         */
        @LogicalOpType
        public int getOperationType() {
            return mImpl.getOperationType().getNumber();
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static LogicalBoolOp fromProto(@NonNull DynamicProto.LogicalBoolOp proto) {
            return new LogicalBoolOp(proto, null);
        }

        @NonNull
        DynamicProto.LogicalBoolOp toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicBool toDynamicBoolProto() {
            return DynamicProto.DynamicBool.newBuilder().setLogicalOp(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "LogicalBoolOp{"
                    + "inputLhs="
                    + getInputLhs()
                    + ", inputRhs="
                    + getInputRhs()
                    + ", operationType="
                    + getOperationType()
                    + "}";
        }

        /** Builder for {@link LogicalBoolOp}. */
        public static final class Builder implements DynamicBool.Builder {
            private final DynamicProto.LogicalBoolOp.Builder mImpl =
                    DynamicProto.LogicalBoolOp.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(1067523409);

            public Builder() {}

            /**
             * Sets the left hand side of the logical operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInputLhs(@NonNull DynamicBool inputLhs) {
                mImpl.setInputLhs(inputLhs.toDynamicBoolProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(inputLhs.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the right hand side of the logical operation.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInputRhs(@NonNull DynamicBool inputRhs) {
                mImpl.setInputRhs(inputRhs.toDynamicBoolProto());
                mFingerprint.recordPropertyUpdate(
                        2, checkNotNull(inputRhs.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the operation type to apply to LHS/RHS.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setOperationType(@LogicalOpType int operationType) {
                mImpl.setOperationType(DynamicProto.LogicalOpType.forNumber(operationType));
                mFingerprint.recordPropertyUpdate(3, operationType);
                return this;
            }

            @Override
            @NonNull
            public LogicalBoolOp build() {
                return new LogicalBoolOp(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * Interface defining a dynamic boolean type.
     *
     * @since 1.2
     */
    public interface DynamicBool extends DynamicType {
        /** Get the protocol buffer representation of this object. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        DynamicProto.DynamicBool toDynamicBoolProto();

        /**
         * Creates a {@link DynamicBool} from a byte array generated by {@link
         * #toDynamicBoolByteArray()}.
         */
        @NonNull
        static DynamicBool fromByteArray(@NonNull byte[] byteArray) {
            try {
                return dynamicBoolFromProto(
                        DynamicProto.DynamicBool.parseFrom(
                                byteArray, ExtensionRegistryLite.getEmptyRegistry()));
            } catch (InvalidProtocolBufferException e) {
                throw new IllegalArgumentException(
                        "Byte array could not be parsed into DynamicBool", e);
            }
        }

        /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
        @NonNull
        default byte[] toDynamicBoolByteArray() {
            return toDynamicBoolProto().toByteArray();
        }

        /** Creates a constant-valued {@link DynamicBool}. */
        @NonNull
        static DynamicBool constant(boolean constant) {
            return new FixedBool.Builder().setValue(constant).build();
        }

        /**
         * Creates a {@link DynamicBool} that is bound to the value of an item of the State.
         *
         * @param stateKey The key to a {@link StateEntryValue} with a boolean value from the
         *     provider's state.
         */
        @NonNull
        static DynamicBool fromState(@NonNull String stateKey) {
            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() {
            return new NotBoolOp.Builder().setInput(this).build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if this {@link DynamicBool} and {@code input}
         * are both true, otherwise it is false. i.e. {@code boolean result = this && input}
         *
         * @param input The right hand operand of the "and" operation.
         */
        @NonNull
        default DynamicBool and(@NonNull DynamicBool input) {
            return new LogicalBoolOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(input)
                    .setOperationType(DynamicBuilders.LOGICAL_OP_TYPE_AND)
                    .build();
        }

        /**
         * Returns a {@link DynamicBool} that is true if this {@link DynamicBool} or {@code input}
         * are true, otherwise it is false. i.e. {@code boolean result = this || input}
         *
         * @param input The right hand operand of the "or" operation.
         */
        @NonNull
        default DynamicBool or(@NonNull DynamicBool input) {
            return new LogicalBoolOp.Builder()
                    .setInputLhs(this)
                    .setInputRhs(input)
                    .setOperationType(DynamicBuilders.LOGICAL_OP_TYPE_OR)
                    .build();
        }

        /** Get the fingerprint for this object or null if unknown. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        Fingerprint getFingerprint();

        /** Builder to create {@link DynamicBool} objects. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        interface Builder {

            /** Builds an instance with values accumulated in this Builder. */
            @NonNull
            DynamicBool build();
        }
    }

    /**
     * Creates a new wrapper instance from the proto. Intended for testing purposes only. An object
     * created using this method can't be added to any other wrapper.
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @NonNull
    public static DynamicBool dynamicBoolFromProto(@NonNull DynamicProto.DynamicBool proto) {
        if (proto.hasFixed()) {
            return FixedBool.fromProto(proto.getFixed());
        }
        if (proto.hasStateSource()) {
            return StateBoolSource.fromProto(proto.getStateSource());
        }
        if (proto.hasInt32Comparison()) {
            return ComparisonInt32Op.fromProto(proto.getInt32Comparison());
        }
        if (proto.hasNotOp()) {
            return NotBoolOp.fromProto(proto.getNotOp());
        }
        if (proto.hasLogicalOp()) {
            return LogicalBoolOp.fromProto(proto.getLogicalOp());
        }
        if (proto.hasFloatComparison()) {
            return ComparisonFloatOp.fromProto(proto.getFloatComparison());
        }
        throw new IllegalStateException("Proto was not a recognised instance of DynamicBool");
    }

    /**
     * A dynamic Color which sources its data from the tile's state.
     *
     * @since 1.2
     */
    static final class StateColorSource implements DynamicColor {
        private final DynamicProto.StateColorSource mImpl;
        @Nullable private final Fingerprint mFingerprint;

        StateColorSource(DynamicProto.StateColorSource impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the key in the state to bind to.
         *
         * @since 1.2
         */
        @NonNull
        public String getSourceKey() {
            return mImpl.getSourceKey();
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static StateColorSource fromProto(@NonNull DynamicProto.StateColorSource proto) {
            return new StateColorSource(proto, null);
        }

        @NonNull
        DynamicProto.StateColorSource toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicColor toDynamicColorProto() {
            return DynamicProto.DynamicColor.newBuilder().setStateSource(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "StateColorSource{" + "sourceKey=" + getSourceKey() + "}";
        }

        /** Builder for {@link StateColorSource}. */
        public static final class Builder implements DynamicColor.Builder {
            private final DynamicProto.StateColorSource.Builder mImpl =
                    DynamicProto.StateColorSource.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(1981221690);

            public Builder() {}

            /**
             * Sets the key in the state to bind to.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setSourceKey(@NonNull String sourceKey) {
                mImpl.setSourceKey(sourceKey);
                mFingerprint.recordPropertyUpdate(1, sourceKey.hashCode());
                return this;
            }

            @Override
            @NonNull
            public StateColorSource build() {
                return new StateColorSource(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A static interpolation node, between two fixed color values.
     *
     * @since 1.2
     */
    static final class AnimatableFixedColor implements DynamicColor {
        private final DynamicProto.AnimatableFixedColor mImpl;
        @Nullable private final Fingerprint mFingerprint;

        AnimatableFixedColor(
                DynamicProto.AnimatableFixedColor impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the color value (in ARGB format) to start animating from.
         *
         * @since 1.2
         */
        @ColorInt
        public int getFromArgb() {
            return mImpl.getFromArgb();
        }

        /**
         * Gets the color value (in ARGB format) to animate to.
         *
         * @since 1.2
         */
        @ColorInt
        public int getToArgb() {
            return mImpl.getToArgb();
        }

        /**
         * Gets the animation parameters for duration, delay, etc.
         *
         * @since 1.2
         */
        @Nullable
        public AnimationSpec getAnimationSpec() {
            if (mImpl.hasAnimationSpec()) {
                return AnimationSpec.fromProto(mImpl.getAnimationSpec());
            } else {
                return null;
            }
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static AnimatableFixedColor fromProto(@NonNull DynamicProto.AnimatableFixedColor proto) {
            return new AnimatableFixedColor(proto, null);
        }

        @NonNull
        DynamicProto.AnimatableFixedColor toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicColor toDynamicColorProto() {
            return DynamicProto.DynamicColor.newBuilder().setAnimatableFixed(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "AnimatableFixedColor{"
                    + "fromArgb="
                    + getFromArgb()
                    + ", toArgb="
                    + getToArgb()
                    + ", animationSpec="
                    + getAnimationSpec()
                    + "}";
        }

        /** Builder for {@link AnimatableFixedColor}. */
        public static final class Builder implements DynamicColor.Builder {
            private final DynamicProto.AnimatableFixedColor.Builder mImpl =
                    DynamicProto.AnimatableFixedColor.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(2051778294);

            public Builder() {}

            /**
             * Sets the color value (in ARGB format) to start animating from.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setFromArgb(@ColorInt int fromArgb) {
                mImpl.setFromArgb(fromArgb);
                mFingerprint.recordPropertyUpdate(1, fromArgb);
                return this;
            }

            /**
             * Sets the color value (in ARGB format) to animate to.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setToArgb(@ColorInt int toArgb) {
                mImpl.setToArgb(toArgb);
                mFingerprint.recordPropertyUpdate(2, toArgb);
                return this;
            }

            /**
             * Sets the animation parameters for duration, delay, etc.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
                mImpl.setAnimationSpec(animationSpec.toProto());
                mFingerprint.recordPropertyUpdate(
                        3, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            @Override
            @NonNull
            public AnimatableFixedColor build() {
                return new AnimatableFixedColor(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * A dynamic interpolation node. This will watch the value of its input and, when the first
     * update arrives, immediately emit that value. On subsequent updates, it will animate between
     * the old and new values.
     *
     * <p>If this node receives an invalid value (e.g. as a result of an upstream node having no
     * value), then it will emit a single invalid value, and forget its "stored" value. The next
     * valid value that arrives is then used as the "first" value again.
     *
     * @since 1.2
     */
    static final class AnimatableDynamicColor implements DynamicColor {
        private final DynamicProto.AnimatableDynamicColor mImpl;
        @Nullable private final Fingerprint mFingerprint;

        AnimatableDynamicColor(
                DynamicProto.AnimatableDynamicColor impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the value to watch, and animate when it changes.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicColor getInput() {
            if (mImpl.hasInput()) {
                return DynamicBuilders.dynamicColorFromProto(mImpl.getInput());
            } else {
                return null;
            }
        }

        /**
         * Gets the animation parameters for duration, delay, etc.
         *
         * @since 1.2
         */
        @Nullable
        public AnimationSpec getAnimationSpec() {
            if (mImpl.hasAnimationSpec()) {
                return AnimationSpec.fromProto(mImpl.getAnimationSpec());
            } else {
                return null;
            }
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static AnimatableDynamicColor fromProto(
                @NonNull DynamicProto.AnimatableDynamicColor proto) {
            return new AnimatableDynamicColor(proto, null);
        }

        @NonNull
        DynamicProto.AnimatableDynamicColor toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicColor toDynamicColorProto() {
            return DynamicProto.DynamicColor.newBuilder().setAnimatableDynamic(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "AnimatableDynamicColor{"
                    + "input="
                    + getInput()
                    + ", animationSpec="
                    + getAnimationSpec()
                    + "}";
        }

        /** Builder for {@link AnimatableDynamicColor}. */
        public static final class Builder implements DynamicColor.Builder {
            private final DynamicProto.AnimatableDynamicColor.Builder mImpl =
                    DynamicProto.AnimatableDynamicColor.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-193597422);

            public Builder() {}

            /**
             * Sets the value to watch, and animate when it changes.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInput(@NonNull DynamicColor input) {
                mImpl.setInput(input.toDynamicColorProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(input.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the animation parameters for duration, delay, etc.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
                mImpl.setAnimationSpec(animationSpec.toProto());
                mFingerprint.recordPropertyUpdate(
                        3, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            @Override
            @NonNull
            public AnimatableDynamicColor build() {
                return new AnimatableDynamicColor(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * Interface defining a dynamic color type.
     *
     * @since 1.2
     */
    public interface DynamicColor extends DynamicType {
        /** Get the protocol buffer representation of this object. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        DynamicProto.DynamicColor toDynamicColorProto();

        /**
         * Creates a {@link DynamicColor} from a byte array generated by {@link
         * #toDynamicColorByteArray()}.
         */
        @NonNull
        static DynamicColor fromByteArray(@NonNull byte[] byteArray) {
            try {
                return dynamicColorFromProto(
                        DynamicProto.DynamicColor.parseFrom(
                                byteArray, ExtensionRegistryLite.getEmptyRegistry()));
            } catch (InvalidProtocolBufferException e) {
                throw new IllegalArgumentException(
                        "Byte array could not be parsed into DynamicColor", e);
            }
        }

        /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
        @NonNull
        default byte[] toDynamicColorByteArray() {
            return toDynamicColorProto().toByteArray();
        }

        /** Creates a constant-valued {@link DynamicColor}. */
        @NonNull
        static DynamicColor constant(@ColorInt int constant) {
            return new FixedColor.Builder().setArgb(constant).build();
        }

        /**
         * Creates a {@link DynamicColor} that is bound to the value of an item of the State.
         *
         * @param stateKey The key to a {@link StateEntryValue} with a color value from the
         *     provider's state.
         */
        @NonNull
        static DynamicColor fromState(@NonNull String stateKey) {
            return new StateColorSource.Builder().setSourceKey(stateKey).build();
        }

        /**
         * Creates a {@link DynamicColor} which will animate over the range of colors from {@code
         * start} to {@code end}.
         *
         * @param start The start value of the range.
         * @param end The end value of the range.
         */
        @NonNull
        static DynamicColor animate(@ColorInt int start, @ColorInt int end) {
            return new AnimatableFixedColor.Builder().setFromArgb(start).setToArgb(end).build();
        }

        /**
         * Creates a {@link DynamicColor} which will animate over the range of colors from {@code
         * start} to {@code end} with the given animation parameters.
         *
         * @param start The start value of the range.
         * @param end The end value of the range.
         * @param animationSpec The animation parameters.
         */
        @NonNull
        static DynamicColor animate(
                @ColorInt int start, @ColorInt int end, @NonNull AnimationSpec animationSpec) {
            return new AnimatableFixedColor.Builder()
                    .setFromArgb(start)
                    .setToArgb(end)
                    .setAnimationSpec(animationSpec)
                    .build();
        }

        /**
         * Creates a {@link DynamicColor} that is bound to the value of an item of the State. Every
         * time the state value changes, this {@link DynamicColor} will animate from its current
         * value to the new value (from the state).
         *
         * @param stateKey The key to a {@link StateEntryValue} with a color value from the
         *     provider's state.
         */
        @NonNull
        static DynamicColor animate(@NonNull String stateKey) {
            return new AnimatableDynamicColor.Builder().setInput(fromState(stateKey)).build();
        }

        /**
         * Creates a {@link DynamicColor} that is bound to the value of an item of the State. Every
         * time the state value changes, this {@link DynamicColor} will animate from its current
         * value to the new value (from the state).
         *
         * @param stateKey The key to a {@link StateEntryValue} with a color value from the
         *     provider's state.
         * @param animationSpec The animation parameters.
         */
        @NonNull
        static DynamicColor animate(
                @NonNull String stateKey, @NonNull AnimationSpec animationSpec) {
            return new AnimatableDynamicColor.Builder()
                    .setInput(fromState(stateKey))
                    .setAnimationSpec(animationSpec)
                    .build();
        }

        /**
         * Returns a {@link DynamicColor} that is bound to the value of this {@link DynamicColor}
         * and every time its value is changing, it animates from its current value to the new
         * value.
         *
         * @param animationSpec The animation parameters.
         */
        @NonNull
        default DynamicColor animate(@NonNull AnimationSpec animationSpec) {
            return new AnimatableDynamicColor.Builder()
                    .setInput(this)
                    .setAnimationSpec(animationSpec)
                    .build();
        }

        /**
         * Returns a {@link DynamicColor} that is bound to the value of this {@link DynamicColor}
         * and every time its value is changing, it animates from its current value to the new
         * value.
         */
        @NonNull
        default DynamicColor animate() {
            return new AnimatableDynamicColor.Builder().setInput(this).build();
        }

        /** Get the fingerprint for this object or null if unknown. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        Fingerprint getFingerprint();

        /** Builder to create {@link DynamicColor} objects. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        interface Builder {

            /** Builds an instance with values accumulated in this Builder. */
            @NonNull
            DynamicColor build();
        }
    }

    /**
     * Creates a new wrapper instance from the proto. Intended for testing purposes only. An object
     * created using this method can't be added to any other wrapper.
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @NonNull
    public static DynamicColor dynamicColorFromProto(@NonNull DynamicProto.DynamicColor proto) {
        if (proto.hasFixed()) {
            return FixedColor.fromProto(proto.getFixed());
        }
        if (proto.hasStateSource()) {
            return StateColorSource.fromProto(proto.getStateSource());
        }
        if (proto.hasAnimatableFixed()) {
            return AnimatableFixedColor.fromProto(proto.getAnimatableFixed());
        }
        if (proto.hasAnimatableDynamic()) {
            return AnimatableDynamicColor.fromProto(proto.getAnimatableDynamic());
        }
        throw new IllegalStateException("Proto was not a recognised instance of DynamicColor");
    }

    /**
     * A dynamic time instant that sources its value from the platform.
     *
     * @since 1.2
     */
    static final class PlatformTimeSource implements DynamicInstant {
        private final DynamicProto.PlatformTimeSource mImpl;
        @Nullable private final Fingerprint mFingerprint;

        PlatformTimeSource(
                DynamicProto.PlatformTimeSource impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static PlatformTimeSource fromProto(@NonNull DynamicProto.PlatformTimeSource proto) {
            return new PlatformTimeSource(proto, null);
        }

        @NonNull
        DynamicProto.PlatformTimeSource toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicInstant toDynamicInstantProto() {
            return DynamicProto.DynamicInstant.newBuilder().setPlatformSource(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "PlatformTimeSource";
        }

        /** Builder for {@link PlatformTimeSource}. */
        public static final class Builder implements DynamicInstant.Builder {
            private final DynamicProto.PlatformTimeSource.Builder mImpl =
                    DynamicProto.PlatformTimeSource.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-1895976938);

            public Builder() {}

            @Override
            @NonNull
            public PlatformTimeSource build() {
                return new PlatformTimeSource(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * Interface defining a dynamic time instant type.
     *
     * <p>{@link DynamicInstant} precision is seconds. Thus, any time or duration operation will
     * operate on that precision level.
     *
     * @since 1.2
     */
    public interface DynamicInstant extends DynamicType {
        /** Get the protocol buffer representation of this object. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        DynamicProto.DynamicInstant toDynamicInstantProto();

        /**
         * Creates a {@link DynamicInstant} from a byte array generated by {@link
         * #toDynamicInstantByteArray()}.
         */
        @NonNull
        static DynamicInstant fromByteArray(@NonNull byte[] byteArray) {
            try {
                return dynamicInstantFromProto(
                        DynamicProto.DynamicInstant.parseFrom(
                                byteArray, ExtensionRegistryLite.getEmptyRegistry()));
            } catch (InvalidProtocolBufferException e) {
                throw new IllegalArgumentException(
                        "Byte array could not be parsed into DynamicInstant", e);
            }
        }

        /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
        @NonNull
        default byte[] toDynamicInstantByteArray() {
            return toDynamicInstantProto().toByteArray();
        }

        /**
         * Creates a constant-valued {@link DynamicInstant} from an {@link Instant}. If {@link
         * Instant} precision is greater than seconds, then any excess precision information will be
         * dropped.
         */
        @NonNull
        static DynamicInstant withSecondsPrecision(@NonNull Instant instant) {
            return new FixedInstant.Builder().setEpochSeconds(instant.getEpochSecond()).build();
        }

        /**
         * Creates a {@link DynamicInstant} that updates its value periodically from the system
         * time.
         */
        @NonNull
        static DynamicInstant platformTimeWithSecondsPrecision() {
            return new PlatformTimeSource.Builder().build();
        }

        /**
         * Returns duration between the two {@link DynamicInstant} instances as a {@link
         * DynamicDuration}. The resulted duration is inclusive of the start instant and exclusive
         * of the end; As an example, the following expression yields a duration object representing
         * 10 seconds:
         *
         * <pre>
         *   DynamicInstant.withSecondsPrecision(Instant.ofEpochSecond(10L))
         *      .durationUntil(DynamicInstant.withSecondsPrecision(Instant.ofEpochSecond(20L)));
         * </pre>
         *
         * @return a new instance of {@link DynamicDuration} containing the result of the operation.
         */
        @NonNull
        default DynamicDuration durationUntil(@NonNull DynamicInstant to) {
            return new BetweenDuration.Builder()
                    .setStartInclusive(this)
                    .setEndExclusive(to)
                    .build();
        }

        /** Get the fingerprint for this object or null if unknown. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        Fingerprint getFingerprint();

        /** Builder to create {@link DynamicInstant} objects. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        interface Builder {

            /** Builds an instance with values accumulated in this Builder. */
            @NonNull
            DynamicInstant build();
        }
    }

    /**
     * Creates a new wrapper instance from the proto. Intended for testing purposes only. An object
     * created using this method can't be added to any other wrapper.
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @NonNull
    public static DynamicInstant dynamicInstantFromProto(
            @NonNull DynamicProto.DynamicInstant proto) {
        if (proto.hasFixed()) {
            return FixedInstant.fromProto(proto.getFixed());
        }
        if (proto.hasPlatformSource()) {
            return PlatformTimeSource.fromProto(proto.getPlatformSource());
        }
        throw new IllegalStateException("Proto was not a recognised instance of DynamicInstant");
    }

    /**
     * A dynamic duration type that represents the duration between two dynamic time instants.
     *
     * @since 1.2
     */
    static final class BetweenDuration implements DynamicDuration {
        private final DynamicProto.BetweenDuration mImpl;
        @Nullable private final Fingerprint mFingerprint;

        BetweenDuration(DynamicProto.BetweenDuration impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the time instant value marking the start of the duration.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicInstant getStartInclusive() {
            if (mImpl.hasStartInclusive()) {
                return DynamicBuilders.dynamicInstantFromProto(mImpl.getStartInclusive());
            } else {
                return null;
            }
        }

        /**
         * Gets the time instant value marking the end of the duration.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicInstant getEndExclusive() {
            if (mImpl.hasEndExclusive()) {
                return DynamicBuilders.dynamicInstantFromProto(mImpl.getEndExclusive());
            } else {
                return null;
            }
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static BetweenDuration fromProto(@NonNull DynamicProto.BetweenDuration proto) {
            return new BetweenDuration(proto, null);
        }

        @NonNull
        DynamicProto.BetweenDuration toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicDuration toDynamicDurationProto() {
            return DynamicProto.DynamicDuration.newBuilder().setBetween(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "BetweenDuration{"
                    + "startInclusive="
                    + getStartInclusive()
                    + ", endExclusive="
                    + getEndExclusive()
                    + "}";
        }

        /** Builder for {@link BetweenDuration}. */
        public static final class Builder implements DynamicDuration.Builder {
            private final DynamicProto.BetweenDuration.Builder mImpl =
                    DynamicProto.BetweenDuration.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-1615230958);

            public Builder() {}

            /**
             * Sets the time instant value marking the start of the duration.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setStartInclusive(@NonNull DynamicInstant startInclusive) {
                mImpl.setStartInclusive(startInclusive.toDynamicInstantProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(startInclusive.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the time instant value marking the end of the duration.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setEndExclusive(@NonNull DynamicInstant endExclusive) {
                mImpl.setEndExclusive(endExclusive.toDynamicInstantProto());
                mFingerprint.recordPropertyUpdate(
                        2, checkNotNull(endExclusive.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            @Override
            @NonNull
            public BetweenDuration build() {
                return new BetweenDuration(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * Interface defining a dynamic duration type.
     *
     * @since 1.2
     */
    public interface DynamicDuration extends DynamicType {
        /** Get the protocol buffer representation of this object. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        DynamicProto.DynamicDuration toDynamicDurationProto();

        /**
         * Creates a {@link DynamicDuration} from a byte array generated by {@link
         * #toDynamicDurationByteArray()}.
         */
        @NonNull
        static DynamicDuration fromByteArray(@NonNull byte[] byteArray) {
            try {
                return dynamicDurationFromProto(
                        DynamicProto.DynamicDuration.parseFrom(
                                byteArray, ExtensionRegistryLite.getEmptyRegistry()));
            } catch (InvalidProtocolBufferException e) {
                throw new IllegalArgumentException(
                        "Byte array could not be parsed into DynamicDuration", e);
            }
        }

        /** Creates a byte array that can later be used with {@link #fromByteArray(byte[])}. */
        @NonNull
        default byte[] toDynamicDurationByteArray() {
            return toDynamicDurationProto().toByteArray();
        }

        /**
         * Returns the total number of days in a {@link DynamicDuration} as a {@link DynamicInt32}.
         * The fraction part of the result will be truncated. This is based on the standard
         * definition of a day as 24 hours. As an example, the following is equal to {@code
         * DynamicInt32.constant(1)}
         *
         * <pre>
         *   DynamicInstant.withSecondsPrecision(Instant.EPOCH)
         *      .durationUntil(DynamicInstant.withSecondsPrecision(Instant.ofEpochSecond(123456L)))
         *      .toIntDays();
         * </pre>
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         *     Integer overflow can occur if the result of the operation is larger than {@link
         *     Integer#MAX_VALUE}.
         */
        @NonNull
        default DynamicInt32 toIntDays() {
            return new GetDurationPartOp.Builder()
                    .setInput(this)
                    .setDurationPart(DURATION_PART_TYPE_TOTAL_DAYS)
                    .build();
        }

        /**
         * Returns the total number of hours in a {@link DynamicDuration} as a {@link DynamicInt32}.
         * The fraction part of the result will be truncated. As an example, the following is equal
         * to {@code DynamicInt32.constant(34)}
         *
         * <pre>
         *   DynamicInstant.withSecondsPrecision(Instant.EPOCH)
         *      .durationUntil(DynamicInstant.withSecondsPrecision(Instant.ofEpochSecond(123456L)))
         *      .toIntHours();
         * </pre>
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         *     Integer overflow can occur if the result of the operation is larger than {@link
         *     Integer#MAX_VALUE}.
         */
        @NonNull
        default DynamicInt32 toIntHours() {
            return new GetDurationPartOp.Builder()
                    .setInput(this)
                    .setDurationPart(DURATION_PART_TYPE_TOTAL_HOURS)
                    .build();
        }

        /**
         * Returns the total number of minutes in a {@link DynamicDuration} as a {@link
         * DynamicInt32}. The fraction part of the result will be truncated. As an example, the
         * following is equal to {@code DynamicInt32.constant(2057)}
         *
         * <pre>
         *   DynamicInstant.withSecondsPrecision(Instant.EPOCH)
         *      .durationUntil(DynamicInstant.withSecondsPrecision(Instant.ofEpochSecond(123456L)))
         *      .toIntMinutes();
         * </pre>
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         *     Integer overflow can occur if the result of the operation is larger than {@link
         *     Integer#MAX_VALUE}.
         */
        @NonNull
        default DynamicInt32 toIntMinutes() {
            return new GetDurationPartOp.Builder()
                    .setInput(this)
                    .setDurationPart(DURATION_PART_TYPE_TOTAL_MINUTES)
                    .build();
        }

        /**
         * Returns the total number of seconds in a {@link DynamicDuration} as a {@link
         * DynamicInt32}. As an example, the following is equal to {@code
         * DynamicInt32.constant(123456)}
         *
         * <pre>
         *   DynamicInstant.withSecondsPrecision(Instant.EPOCH)
         *      .durationUntil(DynamicInstant.withSecondsPrecision(Instant.ofEpochSecond(123456L)))
         *      .toIntSeconds();
         * </pre>
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         *     Integer overflow can occur if the result of the operation is larger than {@link
         *     Integer#MAX_VALUE}.
         */
        @NonNull
        default DynamicInt32 toIntSeconds() {
            return new GetDurationPartOp.Builder()
                    .setInput(this)
                    .setDurationPart(DURATION_PART_TYPE_TOTAL_SECONDS)
                    .build();
        }

        /**
         * Returns the total number of days in a duration as a {@link DynamicInt32}. This represents
         * the absolute value of the total number of days in the duration based on the 24 hours day
         * definition. The fraction part of the result will be truncated; As an example, the
         * following is equal to {@code DynamicInt32.constant(1)}
         *
         * <pre>
         *   DynamicInstant.withSecondsPrecision(Instant.EPOCH)
         *      .durationUntil(DynamicInstant.withSecondsPrecision(Instant.ofEpochSecond(123456L)))
         *      .getIntDaysPart();
         * </pre>
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         *     Integer overflow can occur if the result of the operation is larger than {@link
         *     Integer#MAX_VALUE}.
         */
        @NonNull
        default DynamicInt32 getIntDaysPart() {
            return new GetDurationPartOp.Builder()
                    .setInput(this)
                    .setDurationPart(DURATION_PART_TYPE_DAYS)
                    .build();
        }

        /**
         * Returns the number of hours part in the duration as a {@link DynamicInt32}. This
         * represents the absolute value of remaining hours when dividing total hours by hours in a
         * day (24 hours); As an example, the following is equal to {@code
         * DynamicInt32.constant(10)}
         *
         * <pre>
         *   DynamicInstant.withSecondsPrecision(Instant.EPOCH)
         *      .durationUntil(DynamicInstant.withSecondsPrecision(Instant.ofEpochSecond(123456L)))
         *      .getHoursPart();
         * </pre>
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         */
        @NonNull
        default DynamicInt32 getHoursPart() {
            return new GetDurationPartOp.Builder()
                    .setInput(this)
                    .setDurationPart(DURATION_PART_TYPE_HOURS)
                    .build();
        }

        /**
         * Returns the number of minutes part in the duration as a {@link DynamicInt32}. This
         * represents the absolute value of remaining minutes when dividing total minutes by minutes
         * in an hour (60 minutes). As an example, the following is equal to {@code
         * DynamicInt32.constant(17)}
         *
         * <pre>
         *   DynamicInstant.withSecondsPrecision(Instant.EPOCH)
         *      .durationUntil(DynamicInstant.withSecondsPrecision(Instant.ofEpochSecond(123456L)))
         *      .getMinutesPart();
         * </pre>
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         */
        @NonNull
        default DynamicInt32 getMinutesPart() {
            return new GetDurationPartOp.Builder()
                    .setInput(this)
                    .setDurationPart(DURATION_PART_TYPE_MINUTES)
                    .build();
        }

        /**
         * Returns the number of seconds part in the duration as a {@link DynamicInt32}. This
         * represents the absolute value of remaining seconds when dividing total seconds by seconds
         * in a minute (60 seconds); As an example, the following is equal to {@code
         * DynamicInt32.constant(36)}
         *
         * <pre>
         *   DynamicInstant.withSecondsPrecision(Instant.EPOCH)
         *      .durationUntil(DynamicInstant.withSecondsPrecision(Instant.ofEpochSecond(123456L)))
         *      .getSecondsPart();
         * </pre>
         *
         * @return a new instance of {@link DynamicInt32} containing the result of the operation.
         */
        @NonNull
        default DynamicInt32 getSecondsPart() {
            return new GetDurationPartOp.Builder()
                    .setInput(this)
                    .setDurationPart(DURATION_PART_TYPE_SECONDS)
                    .build();
        }

        /** Get the fingerprint for this object or null if unknown. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        Fingerprint getFingerprint();

        /** Builder to create {@link DynamicDuration} objects. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        interface Builder {

            /** Builds an instance with values accumulated in this Builder. */
            @NonNull
            DynamicDuration build();
        }
    }

    /**
     * Creates a new wrapper instance from the proto. Intended for testing purposes only. An object
     * created using this method can't be added to any other wrapper.
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @NonNull
    public static DynamicDuration dynamicDurationFromProto(
            @NonNull DynamicProto.DynamicDuration proto) {
        if (proto.hasBetween()) {
            return BetweenDuration.fromProto(proto.getBetween());
        }
        throw new IllegalStateException("Proto was not a recognised instance of DynamicDuration");
    }

    /**
     * Retrieve the specified duration part of a {@link DynamicDuration} instance as a {@link
     * DynamicInt32}.
     *
     * @since 1.2
     */
    static final class GetDurationPartOp implements DynamicInt32 {
        private final DynamicProto.GetDurationPartOp mImpl;
        @Nullable private final Fingerprint mFingerprint;

        GetDurationPartOp(DynamicProto.GetDurationPartOp impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the duration input.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicDuration getInput() {
            if (mImpl.hasInput()) {
                return DynamicBuilders.dynamicDurationFromProto(mImpl.getInput());
            } else {
                return null;
            }
        }

        /**
         * Gets the duration part to retrieve.
         *
         * @since 1.2
         */
        @DurationPartType
        public int getDurationPart() {
            return mImpl.getDurationPart().getNumber();
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Nullable
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        @NonNull
        static GetDurationPartOp fromProto(@NonNull DynamicProto.GetDurationPartOp proto) {
            return new GetDurationPartOp(proto, null);
        }

        @NonNull
        DynamicProto.GetDurationPartOp toProto() {
            return mImpl;
        }

        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public DynamicProto.DynamicInt32 toDynamicInt32Proto() {
            return DynamicProto.DynamicInt32.newBuilder().setDurationPart(mImpl).build();
        }

        @Override
        @NonNull
        public String toString() {
            return "GetDurationPartOp{"
                    + "input="
                    + getInput()
                    + ", durationPart="
                    + getDurationPart()
                    + "}";
        }

        /** Builder for {@link GetDurationPartOp}. */
        public static final class Builder implements DynamicInt32.Builder {
            private final DynamicProto.GetDurationPartOp.Builder mImpl =
                    DynamicProto.GetDurationPartOp.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-225941123);

            public Builder() {}

            /**
             * Sets the duration input.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setInput(@NonNull DynamicDuration input) {
                mImpl.setInput(input.toDynamicDurationProto());
                mFingerprint.recordPropertyUpdate(
                        1, checkNotNull(input.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Sets the duration part to retrieve.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setDurationPart(@DurationPartType int durationPart) {
                mImpl.setDurationPart(DynamicProto.DurationPartType.forNumber(durationPart));
                mFingerprint.recordPropertyUpdate(2, durationPart);
                return this;
            }

            @Override
            @NonNull
            public GetDurationPartOp build() {
                return new GetDurationPartOp(mImpl.build(), mFingerprint);
            }
        }
    }

    /**
     * Interface to be used as a base type for all other dynamic types. This is not consumed by any
     * Tile elements, it exists just as a marker interface for use internally in the Tiles library.
     */
    public interface DynamicType {}
}