Maneuver.java

/*
 * Copyright 2020 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.car.app.navigation.model;

import static androidx.annotation.RestrictTo.Scope.LIBRARY;

import static java.util.Objects.requireNonNull;

import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.car.app.annotations.CarProtocol;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.constraints.CarIconConstraints;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;

/** Information about a maneuver that the driver will be required to perform. */
// TODO(b/154671667): Update when host(s) updates or a scheme for auto sync is established.
@CarProtocol
public final class Maneuver {
    /**
     * Possible maneuver types.
     *
     * @hide
     */
    @IntDef({
            TYPE_UNKNOWN,
            TYPE_DEPART,
            TYPE_NAME_CHANGE,
            TYPE_KEEP_LEFT,
            TYPE_KEEP_RIGHT,
            TYPE_TURN_SLIGHT_LEFT,
            TYPE_TURN_SLIGHT_RIGHT,
            TYPE_TURN_NORMAL_LEFT,
            TYPE_TURN_NORMAL_RIGHT,
            TYPE_TURN_SHARP_LEFT,
            TYPE_TURN_SHARP_RIGHT,
            TYPE_U_TURN_LEFT,
            TYPE_U_TURN_RIGHT,
            TYPE_ON_RAMP_SLIGHT_LEFT,
            TYPE_ON_RAMP_SLIGHT_RIGHT,
            TYPE_ON_RAMP_NORMAL_LEFT,
            TYPE_ON_RAMP_NORMAL_RIGHT,
            TYPE_ON_RAMP_SHARP_LEFT,
            TYPE_ON_RAMP_SHARP_RIGHT,
            TYPE_ON_RAMP_U_TURN_LEFT,
            TYPE_ON_RAMP_U_TURN_RIGHT,
            TYPE_OFF_RAMP_SLIGHT_LEFT,
            TYPE_OFF_RAMP_SLIGHT_RIGHT,
            TYPE_OFF_RAMP_NORMAL_LEFT,
            TYPE_OFF_RAMP_NORMAL_RIGHT,
            TYPE_FORK_LEFT,
            TYPE_FORK_RIGHT,
            TYPE_MERGE_LEFT,
            TYPE_MERGE_RIGHT,
            TYPE_MERGE_SIDE_UNSPECIFIED,
            TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW,
            TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE,
            TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW,
            TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE,
            TYPE_STRAIGHT,
            TYPE_FERRY_BOAT,
            TYPE_FERRY_TRAIN,
            TYPE_DESTINATION,
            TYPE_DESTINATION_STRAIGHT,
            TYPE_DESTINATION_LEFT,
            TYPE_DESTINATION_RIGHT,
            TYPE_ROUNDABOUT_ENTER_CW,
            TYPE_ROUNDABOUT_EXIT_CW,
            TYPE_ROUNDABOUT_ENTER_CCW,
            TYPE_ROUNDABOUT_EXIT_CCW,
            TYPE_FERRY_BOAT_LEFT,
            TYPE_FERRY_BOAT_RIGHT,
            TYPE_FERRY_TRAIN_LEFT,
            TYPE_FERRY_TRAIN_RIGHT,
    })
    @Retention(RetentionPolicy.SOURCE)
    @RestrictTo(LIBRARY)
    public @interface Type {
    }

    /**
     * Maneuver type is unknown, no maneuver information should be displayed.
     *
     * <p>This type may be interpreted differently depending on the consumer. In some
     * cases the previous maneuver will continue to be shown while in others no maneuver will be
     * shown at all.
     */
    @Type
    public static final int TYPE_UNKNOWN = 0;

    /**
     * Starting point of the navigation.
     *
     * <p>For example, "Start driving on Main St."
     */
    @Type
    public static final int TYPE_DEPART = 1;

    /**
     * No turn, but the street name changes.
     *
     * <p>For example, "Continue on Main St."
     */
    @Type
    public static final int TYPE_NAME_CHANGE = 2;

    /**
     * No turn, from 0 (included) to 10 (excluded) degrees.
     *
     * <p>This is used in contrast to {@link #TYPE_STRAIGHT} for disambiguating cases where there is
     * more than one option to go into the same general direction.
     */
    @Type
    public static final int TYPE_KEEP_LEFT = 3;

    /**
     * No turn, from 0 (included) to 10 (excluded) degrees.
     *
     * <p>This is used in contrast to {@link #TYPE_STRAIGHT} for disambiguating cases where there is
     * more than one option to go into the same general direction.
     */
    @Type
    public static final int TYPE_KEEP_RIGHT = 4;

    /** Slight left turn at an intersection, from 10 (included) to 45 (excluded) degrees. */
    @Type
    public static final int TYPE_TURN_SLIGHT_LEFT = 5;

    /** Slight right turn at an intersection, from 10 (included) to 45 (excluded) degrees. */
    @Type
    public static final int TYPE_TURN_SLIGHT_RIGHT = 6;

    /** Regular left turn at an intersection, from 45 (included) to 135 (excluded) degrees. */
    @Type
    public static final int TYPE_TURN_NORMAL_LEFT = 7;

    /** Regular right turn at an intersection, from 45 (included) to 135 (excluded) degrees. */
    @Type
    public static final int TYPE_TURN_NORMAL_RIGHT = 8;

    /** Sharp left turn at an intersection, from 135 (included) to 175 (excluded) degrees. */
    @Type
    public static final int TYPE_TURN_SHARP_LEFT = 9;

    /** Sharp right turn at an intersection, from 135 (included) to 175 (excluded) degrees. */
    @Type
    public static final int TYPE_TURN_SHARP_RIGHT = 10;

    /**
     * Left turn onto the opposite side of the same street, from 175 (included) to 180 (included)
     * degrees.
     */
    @Type
    public static final int TYPE_U_TURN_LEFT = 11;

    /**
     * A right turn onto the opposite side of the same street, from 175 (included) to 180 (included)
     * degrees.
     */
    @Type
    public static final int TYPE_U_TURN_RIGHT = 12;

    /**
     * Slight left turn to enter a turnpike or freeway, from 10 (included) to 45 (excluded) degrees.
     */
    @Type
    public static final int TYPE_ON_RAMP_SLIGHT_LEFT = 13;

    /**
     * Slight right turn to enter a turnpike or freeway, from 10 (included) to 45 (excluded)
     * degrees.
     */
    @Type
    public static final int TYPE_ON_RAMP_SLIGHT_RIGHT = 14;

    /**
     * Regular left turn to enter a turnpike or freeway, from 45 (included) to 135 (excluded)
     * degrees.
     */
    @Type
    public static final int TYPE_ON_RAMP_NORMAL_LEFT = 15;

    /**
     * Regular right turn to enter a turnpike or freeway, from 45 (included) to 135 (excluded)
     * degrees.
     */
    @Type
    public static final int TYPE_ON_RAMP_NORMAL_RIGHT = 16;

    /**
     * Sharp left turn to enter a turnpike or freeway, from 135 (included) to 175 (excluded)
     * degrees.
     */
    @Type
    public static final int TYPE_ON_RAMP_SHARP_LEFT = 17;

    /**
     * Sharp right turn to enter a turnpike or freeway, from 135 (included) to 175 (excluded)
     * degrees.
     */
    @Type
    public static final int TYPE_ON_RAMP_SHARP_RIGHT = 18;

    /**
     * Left turn onto the opposite side of the same street to enter a turnpike or freeway, from 175
     * (included) to 180 (included).
     */
    @Type
    public static final int TYPE_ON_RAMP_U_TURN_LEFT = 19;

    /**
     * Right turn onto the opposite side of the same street to enter a turnpike or freeway, from 175
     * (included) to 180 (included).
     */
    @Type
    public static final int TYPE_ON_RAMP_U_TURN_RIGHT = 20;

    /** A left turn to exit a turnpike or freeway, from 10 (included) to 45 (excluded) degrees. */
    @Type
    public static final int TYPE_OFF_RAMP_SLIGHT_LEFT = 21;

    /** A right turn to exit a turnpike or freeway, from 10 (included) to 45 (excluded) degrees. */
    @Type
    public static final int TYPE_OFF_RAMP_SLIGHT_RIGHT = 22;

    /** A left turn to exit a turnpike or freeway, from 45 (included) to 135 (excluded) degrees. */
    @Type
    public static final int TYPE_OFF_RAMP_NORMAL_LEFT = 23;

    /** A left right to exit a turnpike or freeway, from 45 (included) to 135 (excluded) degrees. */
    @Type
    public static final int TYPE_OFF_RAMP_NORMAL_RIGHT = 24;

    /**
     * Keep to the left as the road diverges.
     *
     * <p>For example, this is used to indicate "Keep left at the fork".
     */
    @Type
    public static final int TYPE_FORK_LEFT = 25;

    /**
     * Keep to the right as the road diverges.
     *
     * <p>For example, this is used to indicate "Keep right at the fork".
     */
    @Type
    public static final int TYPE_FORK_RIGHT = 26;

    /**
     * Current road joins another on the left.
     *
     * <p>For example, this is used to indicate "Merge left onto Main St.".
     */
    @Type
    public static final int TYPE_MERGE_LEFT = 27;

    /**
     * Current road joins another on the right.
     *
     * <p>For example, this is used to indicate "Merge left onto Main St.".
     */
    @Type
    public static final int TYPE_MERGE_RIGHT = 28;

    /**
     * Current road joins another without direction specified.
     *
     * <p>For example, this is used to indicate "Merge onto Main St.".
     */
    @Type
    public static final int TYPE_MERGE_SIDE_UNSPECIFIED = 29;

    /**
     * Enter a clockwise roundabout and take the Nth exit.
     *
     * <p>The exit number must be passed when created the maneuver.
     *
     * <p>For example, this is used to indicate "At the roundabout, take the Nth exit".
     */
    @Type
    public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW = 32;

    /**
     * Enter a clockwise roundabout and take the Nth exit after angle A degrees.
     *
     * <p>The exit number and angle must be passed when creating the maneuver.
     *
     * <p>For example, this is used to indicate "At the roundabout, take the Nth exit".
     */
    @Type
    public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE = 33;

    /**
     * Enter a counter-clockwise roundabout and take the Nth exit.
     *
     * <p>The exit number must be passed when created the maneuver.
     *
     * <p>For example, this is used to indicate "At the roundabout, take the Nth exit".
     */
    @Type
    public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW = 34;

    /**
     * Enter a counter-clockwise roundabout and take the Nth exit after angle A degrees.
     *
     * <p>The exit number and angle must be passed when creating the maneuver.
     *
     * <p>For example, this is used to indicate "At the roundabout, take a sharp right at the Nth
     * exit".
     */
    @Type
    public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE = 35;

    /** Driver should steer straight. */
    @Type
    public static final int TYPE_STRAIGHT = 36;

    /**
     * Drive towards a boat ferry for vehicles, where the entrance is straight ahead or in an
     * unknown direction.
     *
     * <p>For example, this is used to indicate "Take the ferry".
     */
    @Type
    public static final int TYPE_FERRY_BOAT = 37;

    /**
     * Drive towards a train ferry for vehicles (e.g. "Take the train"), where the entrance is
     * straight ahead or in an unknown direction.
     */
    @Type
    public static final int TYPE_FERRY_TRAIN = 38;

    /** Arrival at a destination. */
    @Type
    public static final int TYPE_DESTINATION = 39;

    /** Arrival to a destination located straight ahead. */
    @Type
    public static final int TYPE_DESTINATION_STRAIGHT = 40;

    /** Arrival to a destination located to the left side of the road. */
    @Type
    public static final int TYPE_DESTINATION_LEFT = 41;

    /** Arrival to a destination located to the right side of the road. */
    @Type
    public static final int TYPE_DESTINATION_RIGHT = 42;

    /**
     * Entrance to a clockwise roundabout on which the current road ends.
     *
     * <p>For example, this is used to indicate "Enter the roundabout".
     */
    @Type
    public static final int TYPE_ROUNDABOUT_ENTER_CW = 43;

    /**
     * Used when leaving a clockwise roundabout when the step starts in it.
     *
     * <p>For example, this is used to indicate "Exit the roundabout".
     */
    @Type
    public static final int TYPE_ROUNDABOUT_EXIT_CW = 44;

    /**
     * Entrance to a counter-clockwise roundabout on which the current road ends.
     *
     * <p>For example, this is used to indicate "Enter the roundabout".
     */
    @Type
    public static final int TYPE_ROUNDABOUT_ENTER_CCW = 45;

    /**
     * Used when leaving a counter-clockwise roundabout when the step starts in it.
     *
     * <p>For example, this is used to indicate "Exit the roundabout".
     */
    @Type
    public static final int TYPE_ROUNDABOUT_EXIT_CCW = 46;

    /**
     * Drive towards a boat ferry for vehicles, where the entrance is to the left.
     *
     * <p>For example, this is used to indicate "Take the ferry".
     */
    @Type
    public static final int TYPE_FERRY_BOAT_LEFT = 47;

    /**
     * Drive towards a boat ferry for vehicles, where the entrance is to the right.
     *
     * <p>For example, this is used to indicate "Take the ferry".
     */
    @Type
    public static final int TYPE_FERRY_BOAT_RIGHT = 48;

    /**
     * Drive towards a train ferry for vehicles (e.g. "Take the train"), where the entrance is to
     * the
     * left.
     */
    @Type
    public static final int TYPE_FERRY_TRAIN_LEFT = 49;

    /**
     * Drive towards a train ferry for vehicles (e.g. "Take the train"), where the entrance is to
     * the
     * right.
     */
    @Type
    public static final int TYPE_FERRY_TRAIN_RIGHT = 50;

    @Keep
    @Type
    private final int mType;
    @Keep
    private final int mRoundaboutExitNumber;
    @Keep
    private final int mRoundaboutExitAngle;
    @Keep
    @Nullable
    private final CarIcon mIcon;

    /**
     * Returns the maneuver type.
     *
     * <p>Required to be set at all times.
     */
    @Type
    public int getType() {
        return mType;
    }

    /**
     * Returns the roundabout exit number, starting from 1 to designate the first exit after joining
     * the roundabout, and increasing in circulation order. Only relevant if the type is any
     * variation of {@code TYPE_ROUNDABOUT_ENTER_AND_EXIT_*}.
     *
     * <p>For example, if the driver is joining a counter-clockwise roundabout with 4 exits, then
     * the exit to the right would be exit #1, the one straight ahead would be exit #2, the one
     * to the left would be exit #3 and the one used by the driver to join the roundabout would
     * be exit #4.
     *
     * <p>Required when the type is a roundabout.
     */
    public int getRoundaboutExitNumber() {
        return mRoundaboutExitNumber;
    }

    /**
     * Returns the roundabout exit angle in degrees to designate the amount of distance to travel
     * around the roundabout. Only relevant if the type is {@link
     * #TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE} or {@link
     * #TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE}.
     *
     * <p>For example, if the drive is joining a counter-clockwise roundabout with equally spaced
     * exits then the exit to the right would be at 90 degrees, the one straight ahead would be
     * at 180 degrees, the one to the left would at 270 degrees and the one used by the driver to
     * join the roundabout would be at 360 degrees.
     *
     * <p>The angle can also be set for irregular roundabouts. For example a roundabout with three
     * exits at 90, 270 and 360 degrees could also have the desired exit angle specified.
     *
     * <p>Required with the type is a roundabout with an angle.
     */
    public int getRoundaboutExitAngle() {
        return mRoundaboutExitAngle;
    }

    /**
     * Returns the icon for the maneuver.
     *
     * <p>Optional field that when not set may be shown in the target display by a generic image
     * representing the specific maneuver.
     */
    @Nullable
    public CarIcon getIcon() {
        return mIcon;
    }

    @Override
    @NonNull
    public String toString() {
        return "[type: "
                + mType
                + ", exit #: "
                + mRoundaboutExitNumber
                + ", exit angle: "
                + mRoundaboutExitAngle
                + ", icon: "
                + mIcon
                + "]";
    }

    @Override
    public int hashCode() {
        return Objects.hash(mType, mRoundaboutExitNumber, mRoundaboutExitAngle, mIcon);
    }

    @Override
    public boolean equals(@Nullable Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof Maneuver)) {
            return false;
        }

        Maneuver otherManeuver = (Maneuver) other;
        return mType == otherManeuver.mType
                && mRoundaboutExitNumber == otherManeuver.mRoundaboutExitNumber
                && mRoundaboutExitAngle == otherManeuver.mRoundaboutExitAngle
                && Objects.equals(mIcon, otherManeuver.mIcon);
    }

    Maneuver(@Type int type, int roundaboutExitNumber, int roundaboutExitAngle,
            @Nullable CarIcon icon) {
        mType = type;
        mRoundaboutExitNumber = roundaboutExitNumber;
        mRoundaboutExitAngle = roundaboutExitAngle;
        CarIconConstraints.DEFAULT.validateOrThrow(icon);
        mIcon = icon;
    }

    /** Constructs an empty instance, used by serialization code. */
    private Maneuver() {
        mType = TYPE_UNKNOWN;
        mRoundaboutExitNumber = 0;
        mRoundaboutExitAngle = 0;
        mIcon = null;
    }

    static boolean isValidType(@Type int type) {
        return (type >= TYPE_UNKNOWN && type <= TYPE_FERRY_TRAIN_RIGHT);
    }

    static boolean isValidTypeWithExitNumber(@Type int type) {
        return (type == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW
                || type == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW
                || type == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE
                || type == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE);
    }

    static boolean isValidTypeWithExitAngle(@Type int type) {
        return (type == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE
                || type == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE);
    }

    /** A builder of {@link Maneuver}. */
    public static final class Builder {
        @Type
        private final int mType;
        private boolean mIsRoundaboutExitNumberSet;
        private int mRoundaboutExitNumber;
        private boolean mIsRoundaboutExitAngleSet;
        private int mRoundaboutExitAngle;
        @Nullable
        private CarIcon mIcon;

        /**
         * Constructs a new instance of a {@link Builder}.
         *
         * <p>The type should be chosen to reflect the closest semantic meaning of the maneuver.
         * In some cases, an exact type match is not possible, but choosing a similar or slightly
         * more general type is preferred. Using {@link #TYPE_UNKNOWN} is allowed, but some head
         * units will not display any information in that case.
         *
         * @param type one of the {@code TYPE_*} static constants defined in this class
         * @throws IllegalArgumentException if {@code type} is not a valid maneuver type
         */
        public Builder(@Type int type) {
            if (!isValidType(type)) {
                throw new IllegalArgumentException("Maneuver must have a valid type");
            }
            mType = type;
        }

        /**
         * Sets an image representing the maneuver.
         *
         * <h4>Icon Sizing Guidance</h4>
         *
         * To minimize scaling artifacts across a wide range of car screens, apps should provide
         * icons targeting a 128 x 128 dp bounding box. If the icon exceeds this maximum size in
         * either one of the dimensions, it will be scaled down to be centered inside the
         * bounding box while preserving its aspect ratio.
         *
         * <p>See {@link CarIcon} for more details related to providing icon and image resources
         * that work with different car screen pixel densities.
         *
         * @throws NullPointerException if {@code icon} is {@code null}
         */
        @NonNull
        public Builder setIcon(@NonNull CarIcon icon) {
            mIcon = requireNonNull(icon);
            return this;
        }

        /**
         * Sets an exit number for roundabout maneuvers.
         *
         * <p>Use for when {@code type} is {@link #TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW}, {@link
         * #TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW},
         * {@link #TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE} or
         * {@link #TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE}. The {@code
         * roundaboutExitNumber} starts from 1 to designate the first exit after joining the
         * roundabout, and increases in circulation order.
         *
         * <p>For example, if the driver is joining a counter-clockwise roundabout with 4 exits,
         * then the exit to the right would be exit #1, the one straight ahead would be exit #2,
         * the one to the left would be exit #3 and the one used by the driver to join the
         * roundabout would be exit #4.
         *
         * @throws IllegalArgumentException if {@code type} does not include a exit number, or
         *                                  if {@code roundaboutExitNumber} is not greater than
         *                                  zero
         */
        @NonNull
        public Builder setRoundaboutExitNumber(@IntRange(from = 1) int roundaboutExitNumber) {
            if (!isValidTypeWithExitNumber(mType)) {
                throw new IllegalArgumentException(
                        "Maneuver does not include roundaboutExitNumber");
            }
            if (roundaboutExitNumber < 1) {
                throw new IllegalArgumentException("Maneuver must include a valid exit number");
            }
            mIsRoundaboutExitNumberSet = true;
            mRoundaboutExitNumber = roundaboutExitNumber;
            return this;
        }

        /**
         * Sets an exit angle for roundabout maneuvers.
         *
         * <p>Use for when {@code type} is {@link #TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE} or
         * {@link #TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE}. The {@code roundaboutExitAngle}
         * represents the degrees traveled in circulation from the entrance to the exit.
         *
         * <p>For example, in a 4 exit example, if all the exits are equally spaced then exit 1
         * would be at 90 degrees, exit 2 at 180, exit 3 at 270 and exit 4 at 360. However if the
         * exits are irregular then a different angle could be provided.
         *
         * @throws IllegalArgumentException if {@code type} does not include a exit angle or if
         *                                  {@code roundaboutExitAngle} is not greater than zero
         *                                  and less than or equal to 360 degrees
         */
        @NonNull
        public Builder setRoundaboutExitAngle(
                @IntRange(from = 1, to = 360) int roundaboutExitAngle) {
            if (!isValidTypeWithExitAngle(mType)) {
                throw new IllegalArgumentException("Maneuver does not include roundaboutExitAngle");
            }
            if (roundaboutExitAngle < 1 || roundaboutExitAngle > 360) {
                throw new IllegalArgumentException("Maneuver must include a valid exit angle");
            }
            mIsRoundaboutExitAngleSet = true;
            mRoundaboutExitAngle = roundaboutExitAngle;
            return this;
        }

        /**
         * Constructs the {@link Maneuver} defined by this builder.
         *
         * @throws IllegalArgumentException if {@code type} includes an exit number and one has
         *                                  not been set, or if it includes an exit angle and one
         *                                  has not been set
         */
        @NonNull
        public Maneuver build() {
            if (isValidTypeWithExitNumber(mType) && !mIsRoundaboutExitNumberSet) {
                throw new IllegalArgumentException("Maneuver missing roundaboutExitNumber");
            }
            if (isValidTypeWithExitAngle(mType) && !mIsRoundaboutExitAngleSet) {
                throw new IllegalArgumentException("Maneuver missing roundaboutExitAngle");
            }
            return new Maneuver(mType, mRoundaboutExitNumber, mRoundaboutExitAngle, mIcon);
        }
    }
}