DynamicDataBuilders.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 androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicType;
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.FixedInt32;
import androidx.wear.protolayout.expression.FixedValueBuilders.FixedString;
import androidx.wear.protolayout.expression.proto.DynamicDataProto;
import androidx.wear.protolayout.protobuf.CodedInputStream;
import androidx.wear.protolayout.protobuf.CodedOutputStream;
import androidx.wear.protolayout.protobuf.ExtensionRegistryLite;

import java.io.IOException;

/** Builders for dynamic data value of a provider. */
public final class DynamicDataBuilders {
    private DynamicDataBuilders() {}

    /**
     * Interface defining a dynamic data value.
     *
     * @since 1.2
     */
    public interface DynamicDataValue<T extends DynamicType> {
        /** Get the protocol buffer representation of this object. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        DynamicDataProto.DynamicDataValue toDynamicDataValueProto();

        /**
         * Creates a {@link DynamicDataValue} from a byte array generated by {@link
         * #toDynamicDataValueByteArray()}.
         *
         * @throws IllegalArgumentException if the byte array does not contain a valid serialization
         */
        @NonNull
        static DynamicDataValue<?> fromByteArray(@NonNull byte[] byteArray) {
            return fromByteArray(byteArray, 0, byteArray.length);
        }

        /**
         * Creates a {@link DynamicDataValue} from the provided byte array at the provided offset
         * and length, that was generated by one of the {@link #toDynamicDataValueByteArray}
         * overloads.
         *
         * @throws IllegalArgumentException if the byte array does not contain a valid serialization
         *     in the provided offset and length
         */
        @NonNull
        static DynamicDataValue<?> fromByteArray(
                @NonNull byte[] byteArray, int offset, int length) {
            try {
                return dynamicDataValueFromProto(
                        DynamicDataProto.DynamicDataValue.parseFrom(
                                CodedInputStream.newInstance(byteArray, offset, length),
                                ExtensionRegistryLite.getEmptyRegistry()));
            } catch (IOException e) {
                throw new IllegalArgumentException(
                        "Byte array could not be parsed into DynamicDataValue", e);
            }
        }

        /**
         * Serializes the {@link DynamicDataValue} into a new byte array that can later be used with
         * {@link #fromByteArray(byte[])}.
         */
        @NonNull
        default byte[] toDynamicDataValueByteArray() {
            return toDynamicDataValueProto().toByteArray();
        }

        /**
         * Serializes the {@link DynamicDataValue} into the provided byte array, returning the
         * amount of bytes written, that can later be used with {@code
         * DynamicDataValue.fromByteArray(byteArray, 0, bytesWritten)}.
         *
         * @throws IllegalArgumentException if the byte array is too small
         */
        default int toDynamicDataValueByteArray(@NonNull byte[] byteArray) {
            return toDynamicDataValueByteArray(byteArray, 0, byteArray.length);
        }

        /**
         * Serializes the {@link DynamicDataValue} into the provided byte array, returning the
         * amount of bytes written, limited by the provided offset and length, that can later be
         * used with {@code DynamicDataValue.fromByteArray(byteArray, offset, bytesWritten)}.
         *
         * @throws IllegalArgumentException if the byte array is too small
         */
        default int toDynamicDataValueByteArray(@NonNull byte[] byteArray, int offset, int length) {
            CodedOutputStream stream = CodedOutputStream.newInstance(byteArray, offset, length);
            try {
                toDynamicDataValueProto().writeTo(stream);
            } catch (IOException e) {
                throw new IllegalArgumentException(
                        "Provided byte array not large enough to contain this DynamicDataValue", e);
            }
            return stream.getTotalBytesWritten();
        }

        /** Creates a boolean {@link DynamicDataValue}. */
        @NonNull
        static DynamicDataValue<DynamicBool> fromBool(boolean constant) {
            return new FixedBool.Builder().setValue(constant).build();
        }

        /** Creates a int {@link DynamicDataValue}. */
        @NonNull
        static DynamicDataValue<DynamicInt32> fromInt(int constant) {
            return new FixedInt32.Builder().setValue(constant).build();
        }

        /** Creates a float {@link DynamicDataValue}. */
        @NonNull
        static DynamicDataValue<DynamicFloat> fromFloat(float constant) {
            return new FixedFloat.Builder().setValue(constant).build();
        }

        /** Creates a color {@link DynamicDataValue}. */
        @NonNull
        static DynamicDataValue<DynamicColor> fromColor(@ColorInt int constant) {
            return new FixedColor.Builder().setArgb(constant).build();
        }

        /** Creates a string {@link DynamicDataValue}. */
        @NonNull
        static DynamicDataValue<DynamicString> fromString(@NonNull String constant) {
            return new FixedString.Builder().setValue(constant).build();
        }

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

        /** Builder to create {@link DynamicDataValue} objects. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        interface Builder <T extends DynamicType> {

            /** Builds an instance with values accumulated in this Builder. */
            @NonNull
            DynamicDataValue<T> 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 DynamicDataValue<?> dynamicDataValueFromProto(
            @NonNull DynamicDataProto.DynamicDataValue proto) {
        return dynamicDataValueFromProto(proto, null);
    }

    /** Creates a new wrapper instance from the proto. */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @NonNull
    public static DynamicDataValue<?> dynamicDataValueFromProto(
            @NonNull DynamicDataProto.DynamicDataValue proto, @Nullable Fingerprint fingerprint) {
        if (proto.hasStringVal()) {
            return FixedString.fromProto(proto.getStringVal(), fingerprint);
        }
        if (proto.hasInt32Val()) {
            return FixedInt32.fromProto(proto.getInt32Val(), fingerprint);
        }
        if (proto.hasFloatVal()) {
            return FixedFloat.fromProto(proto.getFloatVal(), fingerprint);
        }
        if (proto.hasBoolVal()) {
            return FixedBool.fromProto(proto.getBoolVal(), fingerprint);
        }
        if (proto.hasColorVal()) {
            return FixedColor.fromProto(proto.getColorVal(), fingerprint);
        }
        throw new IllegalStateException("Proto was not a recognised instance of DynamicDataValue");
    }
}