ActionBuilders.java

/*
 * Copyright 2021 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.tiles.builders;

import android.annotation.SuppressLint;

import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.wear.tiles.builders.StateBuilders.State;
import androidx.wear.tiles.proto.ActionProto;

/** Builders for actions that can be performed when a user interacts with layout elements. */
public final class ActionBuilders {
    private ActionBuilders() {}

    /** Shortcut for building an {@link AndroidStringExtra}. */
    @NonNull
    public static AndroidStringExtra stringExtra(@NonNull String value) {
        return AndroidStringExtra.builder().setValue(value).build();
    }

    /** Shortcut for building an {@link AndroidIntExtra}. */
    @NonNull
    public static AndroidIntExtra intExtra(int value) {
        return AndroidIntExtra.builder().setValue(value).build();
    }

    /** Shortcut for building an {@link AndroidLongExtra}. */
    @NonNull
    public static AndroidLongExtra longExtra(long value) {
        return AndroidLongExtra.builder().setValue(value).build();
    }

    /** Shortcut for building an {@link AndroidDoubleExtra}. */
    @NonNull
    public static AndroidDoubleExtra doubleExtra(double value) {
        return AndroidDoubleExtra.builder().setValue(value).build();
    }

    /** Shortcut for building an {@link AndroidBooleanExtra}. */
    @NonNull
    public static AndroidBooleanExtra booleanExtra(boolean value) {
        return AndroidBooleanExtra.builder().setValue(value).build();
    }

    /** A string value that can be added to an Android intent's extras. */
    public static final class AndroidStringExtra implements AndroidExtra {
        private final ActionProto.AndroidStringExtra mImpl;

        private AndroidStringExtra(ActionProto.AndroidStringExtra impl) {
            this.mImpl = impl;
        }

        /** Returns a new {@link Builder}. */
        @NonNull
        public static Builder builder() {
            return new Builder();
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public static AndroidStringExtra fromProto(@NonNull ActionProto.AndroidStringExtra proto) {
            return new AndroidStringExtra(proto);
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        ActionProto.AndroidStringExtra toProto() {
            return mImpl;
        }

        /** @hide */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public ActionProto.AndroidExtra toAndroidExtraProto() {
            return ActionProto.AndroidExtra.newBuilder().setStringVal(mImpl).build();
        }

        /** Builder for {@link AndroidStringExtra}. */
        public static final class Builder implements AndroidExtra.Builder {
            private final ActionProto.AndroidStringExtra.Builder mImpl =
                    ActionProto.AndroidStringExtra.newBuilder();

            Builder() {}

            /** Sets the value. */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder setValue(@NonNull String value) {
                mImpl.setValue(value);
                return this;
            }

            @Override
            @NonNull
            public AndroidStringExtra build() {
                return AndroidStringExtra.fromProto(mImpl.build());
            }
        }
    }

    /** An integer value that can be added to an Android intent's extras. */
    public static final class AndroidIntExtra implements AndroidExtra {
        private final ActionProto.AndroidIntExtra mImpl;

        private AndroidIntExtra(ActionProto.AndroidIntExtra impl) {
            this.mImpl = impl;
        }

        /** Returns a new {@link Builder}. */
        @NonNull
        public static Builder builder() {
            return new Builder();
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public static AndroidIntExtra fromProto(@NonNull ActionProto.AndroidIntExtra proto) {
            return new AndroidIntExtra(proto);
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        ActionProto.AndroidIntExtra toProto() {
            return mImpl;
        }

        /** @hide */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public ActionProto.AndroidExtra toAndroidExtraProto() {
            return ActionProto.AndroidExtra.newBuilder().setIntVal(mImpl).build();
        }

        /** Builder for {@link AndroidIntExtra}. */
        public static final class Builder implements AndroidExtra.Builder {
            private final ActionProto.AndroidIntExtra.Builder mImpl =
                    ActionProto.AndroidIntExtra.newBuilder();

            Builder() {}

            /** Sets the value. */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder setValue(int value) {
                mImpl.setValue(value);
                return this;
            }

            @Override
            @NonNull
            public AndroidIntExtra build() {
                return AndroidIntExtra.fromProto(mImpl.build());
            }
        }
    }

    /** A long value that can be added to an Android intent's extras. */
    public static final class AndroidLongExtra implements AndroidExtra {
        private final ActionProto.AndroidLongExtra mImpl;

        private AndroidLongExtra(ActionProto.AndroidLongExtra impl) {
            this.mImpl = impl;
        }

        /** Returns a new {@link Builder}. */
        @NonNull
        public static Builder builder() {
            return new Builder();
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public static AndroidLongExtra fromProto(@NonNull ActionProto.AndroidLongExtra proto) {
            return new AndroidLongExtra(proto);
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        ActionProto.AndroidLongExtra toProto() {
            return mImpl;
        }

        /** @hide */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public ActionProto.AndroidExtra toAndroidExtraProto() {
            return ActionProto.AndroidExtra.newBuilder().setLongVal(mImpl).build();
        }

        /** Builder for {@link AndroidLongExtra}. */
        public static final class Builder implements AndroidExtra.Builder {
            private final ActionProto.AndroidLongExtra.Builder mImpl =
                    ActionProto.AndroidLongExtra.newBuilder();

            Builder() {}

            /** Sets the value. */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder setValue(long value) {
                mImpl.setValue(value);
                return this;
            }

            @Override
            @NonNull
            public AndroidLongExtra build() {
                return AndroidLongExtra.fromProto(mImpl.build());
            }
        }
    }

    /** A double value that can be added to an Android intent's extras. */
    public static final class AndroidDoubleExtra implements AndroidExtra {
        private final ActionProto.AndroidDoubleExtra mImpl;

        private AndroidDoubleExtra(ActionProto.AndroidDoubleExtra impl) {
            this.mImpl = impl;
        }

        /** Returns a new {@link Builder}. */
        @NonNull
        public static Builder builder() {
            return new Builder();
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public static AndroidDoubleExtra fromProto(@NonNull ActionProto.AndroidDoubleExtra proto) {
            return new AndroidDoubleExtra(proto);
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        ActionProto.AndroidDoubleExtra toProto() {
            return mImpl;
        }

        /** @hide */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public ActionProto.AndroidExtra toAndroidExtraProto() {
            return ActionProto.AndroidExtra.newBuilder().setDoubleVal(mImpl).build();
        }

        /** Builder for {@link AndroidDoubleExtra}. */
        public static final class Builder implements AndroidExtra.Builder {
            private final ActionProto.AndroidDoubleExtra.Builder mImpl =
                    ActionProto.AndroidDoubleExtra.newBuilder();

            Builder() {}

            /** Sets the value. */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder setValue(double value) {
                mImpl.setValue(value);
                return this;
            }

            @Override
            @NonNull
            public AndroidDoubleExtra build() {
                return AndroidDoubleExtra.fromProto(mImpl.build());
            }
        }
    }

    /** A boolean value that can be added to an Android intent's extras. */
    public static final class AndroidBooleanExtra implements AndroidExtra {
        private final ActionProto.AndroidBooleanExtra mImpl;

        private AndroidBooleanExtra(ActionProto.AndroidBooleanExtra impl) {
            this.mImpl = impl;
        }

        /** Returns a new {@link Builder}. */
        @NonNull
        public static Builder builder() {
            return new Builder();
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public static AndroidBooleanExtra fromProto(
                @NonNull ActionProto.AndroidBooleanExtra proto) {
            return new AndroidBooleanExtra(proto);
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        ActionProto.AndroidBooleanExtra toProto() {
            return mImpl;
        }

        /** @hide */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public ActionProto.AndroidExtra toAndroidExtraProto() {
            return ActionProto.AndroidExtra.newBuilder().setBooleanVal(mImpl).build();
        }

        /** Builder for {@link AndroidBooleanExtra}. */
        public static final class Builder implements AndroidExtra.Builder {
            private final ActionProto.AndroidBooleanExtra.Builder mImpl =
                    ActionProto.AndroidBooleanExtra.newBuilder();

            Builder() {}

            /** Sets the value. */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder setValue(boolean value) {
                mImpl.setValue(value);
                return this;
            }

            @Override
            @NonNull
            public AndroidBooleanExtra build() {
                return AndroidBooleanExtra.fromProto(mImpl.build());
            }
        }
    }

    /**
     * Interface defining an item that can be included in the extras of an intent that will be sent
     * to an Android activity. Supports types in android.os.PersistableBundle, excluding arrays.
     */
    public interface AndroidExtra {
        /**
         * Get the protocol buffer representation of this object.
         *
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        ActionProto.AndroidExtra toAndroidExtraProto();

        /**
         * Return an instance of one of this object's subtypes, from the protocol buffer
         * representation.
         *
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        static AndroidExtra fromAndroidExtraProto(@NonNull ActionProto.AndroidExtra proto) {
            if (proto.hasStringVal()) {
                return AndroidStringExtra.fromProto(proto.getStringVal());
            }
            if (proto.hasIntVal()) {
                return AndroidIntExtra.fromProto(proto.getIntVal());
            }
            if (proto.hasLongVal()) {
                return AndroidLongExtra.fromProto(proto.getLongVal());
            }
            if (proto.hasDoubleVal()) {
                return AndroidDoubleExtra.fromProto(proto.getDoubleVal());
            }
            if (proto.hasBooleanVal()) {
                return AndroidBooleanExtra.fromProto(proto.getBooleanVal());
            }
            throw new IllegalStateException("Proto was not a recognised instance of AndroidExtra");
        }

        /** Builder to create {@link AndroidExtra} objects. */
        @SuppressLint("StaticFinalBuilder")
        interface Builder {

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

    /** A launch action to send an intent to an Android activity. */
    public static final class AndroidActivity {
        private final ActionProto.AndroidActivity mImpl;

        private AndroidActivity(ActionProto.AndroidActivity impl) {
            this.mImpl = impl;
        }

        /** Returns a new {@link Builder}. */
        @NonNull
        public static Builder builder() {
            return new Builder();
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public static AndroidActivity fromProto(@NonNull ActionProto.AndroidActivity proto) {
            return new AndroidActivity(proto);
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public ActionProto.AndroidActivity toProto() {
            return mImpl;
        }

        /** Builder for {@link AndroidActivity} */
        public static final class Builder {
            private final ActionProto.AndroidActivity.Builder mImpl =
                    ActionProto.AndroidActivity.newBuilder();

            Builder() {}

            /** Sets the package name to send the intent to, for example, "com.google.weather". */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder setPackageName(@NonNull String packageName) {
                mImpl.setPackageName(packageName);
                return this;
            }

            /**
             * Sets the fully qualified class name (including the package) to send the intent to,
             * for example, "com.google.weather.WeatherOverviewActivity".
             */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder setClassName(@NonNull String className) {
                mImpl.setClassName(className);
                return this;
            }

            /** Adds an entry into the extras to be included in the intent. */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder addKeyToExtraMapping(@NonNull String key, @NonNull AndroidExtra extra) {
                mImpl.putKeyToExtra(key, extra.toAndroidExtraProto());
                return this;
            }

            /** Adds an entry into the extras to be included in the intent. */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder addKeyToExtraMapping(
                    @NonNull String key, @NonNull AndroidExtra.Builder extraBuilder) {
                mImpl.putKeyToExtra(key, extraBuilder.build().toAndroidExtraProto());
                return this;
            }

            /** Builds an instance from accumulated values. */
            @NonNull
            public AndroidActivity build() {
                return AndroidActivity.fromProto(mImpl.build());
            }
        }
    }

    /**
     * An action used to launch another activity on the system. This can hold multiple different
     * underlying action types, which will be picked based on what the underlying runtime believes
     * to be suitable.
     */
    public static final class LaunchAction implements Action {
        private final ActionProto.LaunchAction mImpl;

        private LaunchAction(ActionProto.LaunchAction impl) {
            this.mImpl = impl;
        }

        /** Returns a new {@link Builder}. */
        @NonNull
        public static Builder builder() {
            return new Builder();
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public static LaunchAction fromProto(@NonNull ActionProto.LaunchAction proto) {
            return new LaunchAction(proto);
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        ActionProto.LaunchAction toProto() {
            return mImpl;
        }

        /** @hide */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public ActionProto.Action toActionProto() {
            return ActionProto.Action.newBuilder().setLaunchAction(mImpl).build();
        }

        /** Builder for {@link LaunchAction}. */
        public static final class Builder implements Action.Builder {
            private final ActionProto.LaunchAction.Builder mImpl =
                    ActionProto.LaunchAction.newBuilder();

            Builder() {}

            /** Sets an action to launch an Android activity. */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder setAndroidActivity(@NonNull AndroidActivity androidActivity) {
                mImpl.setAndroidActivity(androidActivity.toProto());
                return this;
            }

            /** Sets an action to launch an Android activity. */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder setAndroidActivity(
                    @NonNull AndroidActivity.Builder androidActivityBuilder) {
                mImpl.setAndroidActivity(androidActivityBuilder.build().toProto());
                return this;
            }

            @Override
            @NonNull
            public LaunchAction build() {
                return LaunchAction.fromProto(mImpl.build());
            }
        }
    }

    /** An action used to load (or reload) the tile contents. */
    public static final class LoadAction implements Action {
        private final ActionProto.LoadAction mImpl;

        private LoadAction(ActionProto.LoadAction impl) {
            this.mImpl = impl;
        }

        /** Returns a new {@link Builder}. */
        @NonNull
        public static Builder builder() {
            return new Builder();
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public static LoadAction fromProto(@NonNull ActionProto.LoadAction proto) {
            return new LoadAction(proto);
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        ActionProto.LoadAction toProto() {
            return mImpl;
        }

        /** @hide */
        @Override
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public ActionProto.Action toActionProto() {
            return ActionProto.Action.newBuilder().setLoadAction(mImpl).build();
        }

        /** Builder for {@link LoadAction}. */
        public static final class Builder implements Action.Builder {
            private final ActionProto.LoadAction.Builder mImpl =
                    ActionProto.LoadAction.newBuilder();

            Builder() {}

            /**
             * Sets the state to load the next tile with. This will be included in the TileRequest
             * sent after this action is invoked by a {@link
             * androidx.wear.tiles.builders.ModifiersBuilders.Clickable}.
             */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder setRequestState(@NonNull State requestState) {
                mImpl.setRequestState(requestState.toProto());
                return this;
            }

            /**
             * Sets the state to load the next tile with. This will be included in the TileRequest
             * sent after this action is invoked by a {@link
             * androidx.wear.tiles.builders.ModifiersBuilders.Clickable}.
             */
            @SuppressLint("MissingGetterMatchingBuilder")
            @NonNull
            public Builder setRequestState(@NonNull State.Builder requestStateBuilder) {
                mImpl.setRequestState(requestStateBuilder.build().toProto());
                return this;
            }

            @Override
            @NonNull
            public LoadAction build() {
                return LoadAction.fromProto(mImpl.build());
            }
        }
    }

    /** Interface defining an action that can be used by a layout element. */
    public interface Action {
        /**
         * Get the protocol buffer representation of this object.
         *
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        ActionProto.Action toActionProto();

        /**
         * Return an instance of one of this object's subtypes, from the protocol buffer
         * representation.
         *
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        static Action fromActionProto(@NonNull ActionProto.Action proto) {
            if (proto.hasLaunchAction()) {
                return LaunchAction.fromProto(proto.getLaunchAction());
            }
            if (proto.hasLoadAction()) {
                return LoadAction.fromProto(proto.getLoadAction());
            }
            throw new IllegalStateException("Proto was not a recognised instance of Action");
        }

        /** Builder to create {@link Action} objects. */
        @SuppressLint("StaticFinalBuilder")
        interface Builder {

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