/*
 * Copyright (C) 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 android.location;

import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;

import com.android.internal.util.Preconditions;

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

/**
 * A class that contains GNSS satellite position, velocity and time information at the
 * same signal transmission time {@link GnssMeasurement#getReceivedSvTimeNanos()}.
 *
 * <p>The position and velocity must be in ECEF coordinates.
 *
 * <p>If {@link GnssMeasurement#getSatellitePvt()} is derived from Broadcast ephemeris, then the
 * position is already w.r.t. the antenna phase center. However, if
 * {@link GnssMeasurement#getSatellitePvt()} is derived from other modeled orbits, such as
 * long-term orbits, or precise orbits, then the orbits may have been computed w.r.t.
 * the satellite center of mass, and then GNSS vendors are expected to correct for the effect
 * on different phase centers (can differ by meters) of different GNSS signals (e.g. L1, L5)
 * on the reported satellite position. Accordingly, we might observe a different satellite
 * position reported for L1 GnssMeasurement struct compared to L5 GnssMeasurement struct.
 *
 * <p>If {@link GnssMeasurement#getReceivedSvTimeNanos()} is not fully decoded,
 * {@link GnssMeasurement#getSatellitePvt()} could still be reported and
 * {@link GnssMeasurement#getReceivedSvTimeUncertaintyNanos()} would be used to provide confidence.
 * @hide
 */
@SystemApi
public final class SatellitePvt implements Parcelable {
    /**
     * Bit mask for {@link #mFlags} indicating valid satellite position, velocity and clock info
     * fields are stored in the SatellitePvt.
     */
    private static final int HAS_POSITION_VELOCITY_CLOCK_INFO = 1 << 0;

    /**
     * Bit mask for {@link #mFlags} indicating a valid iono delay field is stored in the
     * SatellitePvt.
     */
    private static final int HAS_IONO = 1 << 1;

    /**
     * Bit mask for {@link #mFlags} indicating a valid tropo delay field is stored in the
     * SatellitePvt.
     */
    private static final int HAS_TROPO = 1 << 2;

    /**
     * Bit mask for {@link #mFlags} indicating a valid Issue of Data, Clock field is stored in the
     * SatellitePvt.
     */
    private static final int HAS_ISSUE_OF_DATA_CLOCK = 1 << 3;

    /**
     * Bit mask for {@link #mFlags} indicating a valid Issue of Data, Ephemeris field is stored in
     * the SatellitePvt.
     */
    private static final int HAS_ISSUE_OF_DATA_EPHEMERIS = 1 << 4;

    /**
     * Bit mask for {@link #mFlags} indicating a valid Time of Clock field is stored in the
     * SatellitePvt.
     */
    private static final int HAS_TIME_OF_CLOCK = 1 << 5;

    /**
     * Bit mask for {@link #mFlags} indicating a valid Time of Ephemeris field is stored in
     * the SatellitePvt.
     */
    private static final int HAS_TIME_OF_EPHEMERIS = 1 << 6;


    /** Ephemeris demodulated from broadcast signals */
    public static final int EPHEMERIS_SOURCE_DEMODULATED = 0;

    /**
     * Server provided Normal type ephemeris data, which is similar to broadcast ephemeris in
     * longevity (e.g. SUPL) - lasting for few hours and providing satellite orbit and clock
     * with accuracy of 1 - 2 meters.
     */
    public static final int EPHEMERIS_SOURCE_SERVER_NORMAL = 1;

    /**
     * Server provided Long-Term type ephemeris data, which lasts for many hours to several days
     * and often provides satellite orbit and clock accuracy of 2 - 20 meters.
     */
    public static final int EPHEMERIS_SOURCE_SERVER_LONG_TERM = 2;

    /** Other ephemeris source */
    public static final int EPHEMERIS_SOURCE_OTHER = 3;

    /**
     * Satellite ephemeris source
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({EPHEMERIS_SOURCE_DEMODULATED, EPHEMERIS_SOURCE_SERVER_NORMAL,
            EPHEMERIS_SOURCE_SERVER_LONG_TERM, EPHEMERIS_SOURCE_OTHER})
    public @interface EphemerisSource {
    }

    /**
     * A bitfield of flags indicating the validity of the fields in this SatellitePvt.
     * The bit masks are defined in the constants with prefix HAS_*
     *
     * <p>Fields for which there is no corresponding flag must be filled in with a valid value.
     * For convenience, these are marked as mandatory.
     *
     * <p>Others fields may have invalid information in them, if not marked as valid by the
     * corresponding bit in flags.
     */
    private final int mFlags;

    @Nullable
    private final PositionEcef mPositionEcef;
    @Nullable
    private final VelocityEcef mVelocityEcef;
    @Nullable
    private final ClockInfo mClockInfo;
    private final double mIonoDelayMeters;
    private final double mTropoDelayMeters;
    private final long mTimeOfClockSeconds;
    private final long mTimeOfEphemerisSeconds;
    private final int mIssueOfDataClock;
    private final int mIssueOfDataEphemeris;
    @EphemerisSource
    private final int mEphemerisSource;

    /**
     * Class containing estimates of the satellite position fields in ECEF coordinate frame.
     *
     * <p>The satellite position must be defined at the time of transmission of the signal
     * receivedSvTimeNs.
     */
    public static final class PositionEcef implements Parcelable {
        private final double mXMeters;
        private final double mYMeters;
        private final double mZMeters;
        private final double mUreMeters;

        public PositionEcef(
                double xMeters,
                double yMeters,
                double zMeters,
                double ureMeters) {
            mXMeters = xMeters;
            mYMeters = yMeters;
            mZMeters = zMeters;
            mUreMeters = ureMeters;
        }

        public static final @NonNull Creator<PositionEcef> CREATOR =
                new Creator<PositionEcef>() {
                    @Override
                    public PositionEcef createFromParcel(Parcel in) {
                        return new PositionEcef(
                                in.readDouble(),
                                in.readDouble(),
                                in.readDouble(),
                                in.readDouble()
                        );
                    }

                    @Override
                    public PositionEcef[] newArray(int size) {
                        return new PositionEcef[size];
                    }
                };

        /**
         * Returns the satellite position X in WGS84 ECEF (meters).
         */
        @FloatRange()
        public double getXMeters() {
            return mXMeters;
        }

        /**
         * Returns the satellite position Y in WGS84 ECEF (meters).
         */
        @FloatRange()
        public double getYMeters() {
            return mYMeters;
        }

        /**
         * Returns the satellite position Z in WGS84 ECEF (meters).
         */
        @FloatRange()
        public double getZMeters() {
            return mZMeters;
        }

        /**
         * Returns the signal in Space User Range Error (URE) (meters).
         */
        @FloatRange(from = 0.0f, fromInclusive = false)
        public double getUreMeters() {
            return mUreMeters;
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeDouble(mXMeters);
            dest.writeDouble(mYMeters);
            dest.writeDouble(mZMeters);
            dest.writeDouble(mUreMeters);
        }

        @Override
        public String toString() {
            return "PositionEcef{"
                    + "xMeters=" + mXMeters
                    + ", yMeters=" + mYMeters
                    + ", zMeters=" + mZMeters
                    + ", ureMeters=" + mUreMeters
                    + "}";
        }
    }

    /**
     * Class containing estimates of the satellite velocity fields in the ECEF coordinate frame.
     *
     * <p>The satellite velocity must be defined at the time of transmission of the signal
     * receivedSvTimeNs.
     */
    public static final class VelocityEcef implements Parcelable {
        private final double mXMetersPerSecond;
        private final double mYMetersPerSecond;
        private final double mZMetersPerSecond;
        private final double mUreRateMetersPerSecond;

        public VelocityEcef(
                double xMetersPerSecond,
                double yMetersPerSecond,
                double zMetersPerSecond,
                double ureRateMetersPerSecond) {
            mXMetersPerSecond = xMetersPerSecond;
            mYMetersPerSecond = yMetersPerSecond;
            mZMetersPerSecond = zMetersPerSecond;
            mUreRateMetersPerSecond = ureRateMetersPerSecond;
        }

        public static final @NonNull Creator<VelocityEcef> CREATOR =
                new Creator<VelocityEcef>() {
                    @Override
                    public VelocityEcef createFromParcel(Parcel in) {
                        return new VelocityEcef(
                                in.readDouble(),
                                in.readDouble(),
                                in.readDouble(),
                                in.readDouble()
                        );
                    }

                    @Override
                    public VelocityEcef[] newArray(int size) {
                        return new VelocityEcef[size];
                    }
                };

        /**
         * Returns the satellite velocity X in WGS84 ECEF (meters per second).
         */
        @FloatRange()
        public double getXMetersPerSecond() {
            return mXMetersPerSecond;
        }

        /**
         * Returns the satellite velocity Y in WGS84 ECEF (meters per second).
         */
        @FloatRange()
        public double getYMetersPerSecond() {
            return mYMetersPerSecond;
        }

        /**
         *Returns the satellite velocity Z in WGS84 ECEF (meters per second).
         */
        @FloatRange()
        public double getZMetersPerSecond() {
            return mZMetersPerSecond;
        }

        /**
         * Returns the signal in Space User Range Error Rate (URE Rate) (meters per second).
         *
         * <p>It covers satellite velocity error and Satellite clock drift
         * projected to the pseudorange rate measurements.
         */
        @FloatRange(from = 0.0f, fromInclusive = false)
        public double getUreRateMetersPerSecond() {
            return mUreRateMetersPerSecond;
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeDouble(mXMetersPerSecond);
            dest.writeDouble(mYMetersPerSecond);
            dest.writeDouble(mZMetersPerSecond);
            dest.writeDouble(mUreRateMetersPerSecond);
        }

        @Override
        public String toString() {
            return "VelocityEcef{"
                    + "xMetersPerSecond=" + mXMetersPerSecond
                    + ", yMetersPerSecond=" + mYMetersPerSecond
                    + ", zMetersPerSecond=" + mZMetersPerSecond
                    + ", ureRateMetersPerSecond=" + mUreRateMetersPerSecond
                    + "}";
        }
    }

    /**
     * Class containing estimates of the satellite clock info.
     */
    public static final class ClockInfo implements Parcelable {
        private final double mHardwareCodeBiasMeters;
        private final double mTimeCorrectionMeters;
        private final double mClockDriftMetersPerSecond;

        public ClockInfo(
                double hardwareCodeBiasMeters,
                double timeCorrectionMeters,
                double clockDriftMetersPerSecond) {
            mHardwareCodeBiasMeters = hardwareCodeBiasMeters;
            mTimeCorrectionMeters = timeCorrectionMeters;
            mClockDriftMetersPerSecond = clockDriftMetersPerSecond;
        }

        public static final @NonNull Creator<ClockInfo> CREATOR =
                new Creator<ClockInfo>() {
                    @Override
                    public ClockInfo createFromParcel(Parcel in) {
                        return new ClockInfo(
                                in.readDouble(),
                                in.readDouble(),
                                in.readDouble()
                        );
                    }

                    @Override
                    public ClockInfo[] newArray(int size) {
                        return new ClockInfo[size];
                    }
                };

        /**
         * Returns the satellite hardware code bias of the reported code type w.r.t
         * ionosphere-free measurement in meters.
         *
         * <p>When broadcast ephemeris is used, this is the offset caused
         * by the satellite hardware delays at different frequencies;
         * e.g. in IS-GPS-705D, this term is described in Section
         * 20.3.3.3.1.2.1.
         *
         * <p>For GPS this term is ~10ns, and affects the satellite position
         * computation by less than a millimeter.
         */
        @FloatRange()
        public double getHardwareCodeBiasMeters() {
            return mHardwareCodeBiasMeters;
        }

        /**
         * Returns the satellite time correction for ionospheric-free signal measurement
         * (meters). The satellite clock correction for the given signal type
         * = satTimeCorrectionMeters - satHardwareCodeBiasMeters.
         *
         * <p>When broadcast ephemeris is used, this is the offset modeled in the
         * clock terms broadcast over the air by the satellites;
         * e.g. in IS-GPS-200H, Section 20.3.3.3.3.1, this term is
         * ∆tsv = af0 + af1(t - toc) + af2(t - toc)^2 + ∆tr.
         *
         * <p>If another source of ephemeris is used for SatellitePvt, then the
         * equivalent value of satTimeCorrection must be provided.
         *
         * <p>For GPS this term is ~1ms, and affects the satellite position
         * computation by ~1m.
         */
        @FloatRange()
        public double getTimeCorrectionMeters() {
            return mTimeCorrectionMeters;
        }

        /**
         * Returns the satellite clock drift (meters per second).
         */
        @FloatRange()
        public double getClockDriftMetersPerSecond() {
            return mClockDriftMetersPerSecond;
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeDouble(mHardwareCodeBiasMeters);
            dest.writeDouble(mTimeCorrectionMeters);
            dest.writeDouble(mClockDriftMetersPerSecond);
        }

        @Override
        public String toString() {
            return "ClockInfo{"
                    + "hardwareCodeBiasMeters=" + mHardwareCodeBiasMeters
                    + ", timeCorrectionMeters=" + mTimeCorrectionMeters
                    + ", clockDriftMetersPerSecond=" + mClockDriftMetersPerSecond
                    + "}";
        }
    }

    private SatellitePvt(
            int flags,
            @Nullable PositionEcef positionEcef,
            @Nullable VelocityEcef velocityEcef,
            @Nullable ClockInfo clockInfo,
            double ionoDelayMeters,
            double tropoDelayMeters,
            long timeOfClockSeconds,
            long timeOfEphemerisSeconds,
            int issueOfDataClock,
            int issueOfDataEphemeris,
            @EphemerisSource int ephemerisSource) {
        mFlags = flags;
        mPositionEcef = positionEcef;
        mVelocityEcef = velocityEcef;
        mClockInfo = clockInfo;
        mIonoDelayMeters = ionoDelayMeters;
        mTropoDelayMeters = tropoDelayMeters;
        mTimeOfClockSeconds = timeOfClockSeconds;
        mTimeOfEphemerisSeconds = timeOfEphemerisSeconds;
        mIssueOfDataClock = issueOfDataClock;
        mIssueOfDataEphemeris = issueOfDataEphemeris;
        mEphemerisSource = ephemerisSource;
    }

    /**
     * Returns a {@link PositionEcef} object that contains estimates of the satellite
     * position fields in ECEF coordinate frame.
     */
    @Nullable
    public PositionEcef getPositionEcef() {
        return mPositionEcef;
    }

    /**
     * Returns a {@link VelocityEcef} object that contains estimates of the satellite
     * velocity fields in the ECEF coordinate frame.
     */
    @Nullable
    public VelocityEcef getVelocityEcef() {
        return mVelocityEcef;
    }

    /**
     * Returns a {@link ClockInfo} object that contains estimates of the satellite
     * clock info.
     */
    @Nullable
    public ClockInfo getClockInfo() {
        return mClockInfo;
    }

    /**
     * Returns the ionospheric delay in meters.
     */
    @FloatRange()
    public double getIonoDelayMeters() {
        return mIonoDelayMeters;
    }

    /**
     * Returns the tropospheric delay in meters.
     */
    @FloatRange()
    public double getTropoDelayMeters() {
        return mTropoDelayMeters;
    }

    /**
     * Issue of Data, Clock.
     *
     * <p>This is defined in GPS ICD200 documentation (e.g.,
     * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>).
     *
     * <p>This field is valid if {@link #hasIssueOfDataClock()} is true.
     */
    @IntRange(from = 0, to = 1023)
    public int getIssueOfDataClock() {
        return mIssueOfDataClock;
    }

    /**
     * Issue of Data, Ephemeris.
     *
     * <p>This is defined in GPS ICD200 documentation (e.g.,
     * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>).
     *
     * <p>This field is valid if {@link #hasIssueOfDataEphemeris()} is true.
     */
    @IntRange(from = 0, to = 1023)
    public int getIssueOfDataEphemeris() {
        return mIssueOfDataEphemeris;
    }

    /**
     * Time of Clock in seconds.
     *
     * <p>The value is in seconds since GPS epoch, regardless of the constellation.
     *
     * <p>The value is not encoded as in GPS ICD200 documentation.
     *
     * <p>This field is valid if {@link #hasTimeOfClockSeconds()} is true.
     */
    @IntRange(from = 0)
    public long getTimeOfClockSeconds() {
        return mTimeOfClockSeconds;
    }

    /**
     * Time of ephemeris in seconds.
     *
     * <p>The value is in seconds since GPS epoch, regardless of the constellation.
     *
     * <p>The value is not encoded as in GPS ICD200 documentation.
     *
     * <p>This field is valid if {@link #hasTimeOfEphemerisSeconds()} is true.
     */
    @IntRange(from = 0)
    public long getTimeOfEphemerisSeconds() {
        return mTimeOfEphemerisSeconds;
    }

    /**
     * Satellite ephemeris source.
     */
    @EphemerisSource
    public int getEphemerisSource() {
        return mEphemerisSource;
    }

    /** Returns {@code true} if {@link #getPositionEcef()}, {@link #getVelocityEcef()},
     * and {@link #getClockInfo()} are valid.
     */
    public boolean hasPositionVelocityClockInfo() {
        return (mFlags & HAS_POSITION_VELOCITY_CLOCK_INFO) != 0;
    }

    /** Returns {@code true} if {@link #getIonoDelayMeters()} is valid. */
    public boolean hasIono() {
        return (mFlags & HAS_IONO) != 0;
    }

    /** Returns {@code true} if {@link #getTropoDelayMeters()} is valid. */
    public boolean hasTropo() {
        return (mFlags & HAS_TROPO) != 0;
    }

    /** Returns {@code true} if {@link #getIssueOfDataClock()} is valid. */
    public boolean hasIssueOfDataClock() {
        return (mFlags & HAS_ISSUE_OF_DATA_CLOCK) != 0;
    }

    /** Returns {@code true} if {@link #getIssueOfDataEphemeris()} is valid. */
    public boolean hasIssueOfDataEphemeris() {
        return (mFlags & HAS_ISSUE_OF_DATA_EPHEMERIS) != 0;
    }

    /** Returns {@code true} if {@link #getTimeOfClockSeconds()} ()} is valid. */
    public boolean hasTimeOfClockSeconds() {
        return (mFlags & HAS_TIME_OF_CLOCK) != 0;
    }

    /** Returns {@code true} if {@link #getTimeOfEphemerisSeconds()} is valid. */
    public boolean hasTimeOfEphemerisSeconds() {
        return (mFlags & HAS_TIME_OF_EPHEMERIS) != 0;
    }

    public static final @android.annotation.NonNull Creator<SatellitePvt> CREATOR =
            new Creator<SatellitePvt>() {
                @Override
                @Nullable
                public SatellitePvt createFromParcel(Parcel in) {
                    int flags = in.readInt();
                    ClassLoader classLoader = getClass().getClassLoader();
                    PositionEcef positionEcef = in.readParcelable(classLoader,
                            android.location.SatellitePvt.PositionEcef.class);
                    VelocityEcef velocityEcef = in.readParcelable(classLoader,
                            android.location.SatellitePvt.VelocityEcef.class);
                    ClockInfo clockInfo = in.readParcelable(classLoader,
                            android.location.SatellitePvt.ClockInfo.class);
                    double ionoDelayMeters = in.readDouble();
                    double tropoDelayMeters = in.readDouble();
                    long toc = in.readLong();
                    long toe = in.readLong();
                    int iodc = in.readInt();
                    int iode = in.readInt();
                    int ephemerisSource = in.readInt();

                    return new SatellitePvt(
                            flags,
                            positionEcef,
                            velocityEcef,
                            clockInfo,
                            ionoDelayMeters,
                            tropoDelayMeters,
                            toc,
                            toe,
                            iodc,
                            iode,
                            ephemerisSource);
                }

                @Override
                public SatellitePvt[] newArray(int size) {
                    return new SatellitePvt[size];
                }
            };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel parcel, int flags) {
        parcel.writeInt(mFlags);
        parcel.writeParcelable(mPositionEcef, flags);
        parcel.writeParcelable(mVelocityEcef, flags);
        parcel.writeParcelable(mClockInfo, flags);
        parcel.writeDouble(mIonoDelayMeters);
        parcel.writeDouble(mTropoDelayMeters);
        parcel.writeLong(mTimeOfClockSeconds);
        parcel.writeLong(mTimeOfEphemerisSeconds);
        parcel.writeInt(mIssueOfDataClock);
        parcel.writeInt(mIssueOfDataEphemeris);
        parcel.writeInt(mEphemerisSource);
    }

    @Override
    public String toString() {
        return "SatellitePvt["
                + "Flags=" + mFlags
                + ", PositionEcef=" + mPositionEcef
                + ", VelocityEcef=" + mVelocityEcef
                + ", ClockInfo=" + mClockInfo
                + ", IonoDelayMeters=" + mIonoDelayMeters
                + ", TropoDelayMeters=" + mTropoDelayMeters
                + ", TimeOfClockSeconds=" + mTimeOfClockSeconds
                + ", TimeOfEphemerisSeconds=" + mTimeOfEphemerisSeconds
                + ", IssueOfDataClock=" + mIssueOfDataClock
                + ", IssueOfDataEphemeris=" + mIssueOfDataEphemeris
                + ", EphemerisSource=" + mEphemerisSource
                + "]";
    }

    /**
     * Builder class for SatellitePvt.
     */
    public static final class Builder {
        /**
         * For documentation of below fields, see corresponding fields in {@link
         * SatellitePvt}.
         */
        private int mFlags;
        @Nullable private PositionEcef mPositionEcef;
        @Nullable private VelocityEcef mVelocityEcef;
        @Nullable private ClockInfo mClockInfo;
        private double mIonoDelayMeters;
        private double mTropoDelayMeters;
        private long mTimeOfClockSeconds;
        private long mTimeOfEphemerisSeconds;
        private int mIssueOfDataClock;
        private int mIssueOfDataEphemeris;
        @EphemerisSource
        private int mEphemerisSource = EPHEMERIS_SOURCE_OTHER;

        /**
         * Set position ECEF.
         *
         * @param positionEcef position ECEF object
         * @return builder object
         */
        @NonNull
        public Builder setPositionEcef(@NonNull PositionEcef positionEcef) {
            mPositionEcef = positionEcef;
            updateFlags();
            return this;
        }

        /**
         * Set velocity ECEF.
         *
         * @param velocityEcef velocity ECEF object
         * @return builder object
         */
        @NonNull
        public Builder setVelocityEcef(@NonNull VelocityEcef velocityEcef) {
            mVelocityEcef = velocityEcef;
            updateFlags();
            return this;
        }

        /**
         * Set clock info.
         *
         * @param clockInfo clock info object
         * @return builder object
         */
        @NonNull
        public Builder setClockInfo(@NonNull ClockInfo clockInfo) {
            mClockInfo = clockInfo;
            updateFlags();
            return this;
        }

        private void updateFlags() {
            if (mPositionEcef != null && mVelocityEcef != null && mClockInfo != null) {
                mFlags = (byte) (mFlags | HAS_POSITION_VELOCITY_CLOCK_INFO);
            }
        }

        /**
         * Set ionospheric delay in meters.
         *
         * @param ionoDelayMeters ionospheric delay (meters)
         * @return builder object
         */
        @NonNull
        public Builder setIonoDelayMeters(
                @FloatRange(from = 0.0f, to = 100.0f) double ionoDelayMeters) {
            mIonoDelayMeters = ionoDelayMeters;
            mFlags = (byte) (mFlags | HAS_IONO);
            return this;
        }

        /**
         * Set tropospheric delay in meters.
         *
         * @param tropoDelayMeters tropospheric delay (meters)
         * @return builder object
         */
        @NonNull
        public Builder setTropoDelayMeters(
                @FloatRange(from = 0.0f, to = 100.0f) double tropoDelayMeters) {
            mTropoDelayMeters = tropoDelayMeters;
            mFlags = (byte) (mFlags | HAS_TROPO);
            return this;
        }

        /**
         * Set time of clock in seconds.
         *
         * <p>The value is in seconds since GPS epoch, regardless of the constellation.
         *
         * <p>The value is not encoded as in GPS ICD200 documentation.
         *
         * @param timeOfClockSeconds time of clock (seconds)
         * @return builder object
         */
        @NonNull
        public Builder setTimeOfClockSeconds(@IntRange(from = 0) long timeOfClockSeconds) {
            Preconditions.checkArgumentNonnegative(timeOfClockSeconds);
            mTimeOfClockSeconds = timeOfClockSeconds;
            mFlags = (byte) (mFlags | HAS_TIME_OF_CLOCK);
            return this;
        }

        /**
         * Set time of ephemeris in seconds.
         *
         * <p>The value is in seconds since GPS epoch, regardless of the constellation.
         *
         * <p>The value is not encoded as in GPS ICD200 documentation.
         *
         * @param timeOfEphemerisSeconds time of ephemeris (seconds)
         * @return builder object
         */
        @NonNull
        public Builder setTimeOfEphemerisSeconds(@IntRange(from = 0) long timeOfEphemerisSeconds) {
            Preconditions.checkArgumentNonnegative(timeOfEphemerisSeconds);
            mTimeOfEphemerisSeconds = timeOfEphemerisSeconds;
            mFlags = (byte) (mFlags | HAS_TIME_OF_EPHEMERIS);
            return this;
        }

        /**
         * Set issue of data, clock.
         *
         * @param issueOfDataClock issue of data, clock.
         * @return builder object
         */
        @NonNull
        public Builder setIssueOfDataClock(@IntRange(from = 0, to = 1023) int issueOfDataClock) {
            Preconditions.checkArgumentInRange(issueOfDataClock, 0, 1023, "issueOfDataClock");
            mIssueOfDataClock = issueOfDataClock;
            mFlags = (byte) (mFlags | HAS_ISSUE_OF_DATA_CLOCK);
            return this;
        }

        /**
         * Set issue of data, ephemeris.
         *
         * @param issueOfDataEphemeris issue of data, ephemeris.
         * @return builder object
         */
        @NonNull
        public Builder setIssueOfDataEphemeris(
                @IntRange(from = 0, to = 1023) int issueOfDataEphemeris) {
            Preconditions.checkArgumentInRange(issueOfDataEphemeris, 0, 1023,
                    "issueOfDataEphemeris");
            mIssueOfDataEphemeris = issueOfDataEphemeris;
            mFlags = (byte) (mFlags | HAS_ISSUE_OF_DATA_EPHEMERIS);
            return this;
        }

        /**
         * Set satellite ephemeris source.
         *
         * @param ephemerisSource satellite ephemeris source
         * @return builder object
         */
        @NonNull
        public Builder setEphemerisSource(@EphemerisSource int ephemerisSource) {
            Preconditions.checkArgument(ephemerisSource == EPHEMERIS_SOURCE_DEMODULATED
                    || ephemerisSource == EPHEMERIS_SOURCE_SERVER_NORMAL
                    || ephemerisSource == EPHEMERIS_SOURCE_SERVER_LONG_TERM
                    || ephemerisSource == EPHEMERIS_SOURCE_OTHER);
            mEphemerisSource = ephemerisSource;
            return this;
        }

        /**
         * Build SatellitePvt object.
         *
         * @return instance of SatellitePvt
         */
        @NonNull
        public SatellitePvt build() {
            return new SatellitePvt(mFlags, mPositionEcef, mVelocityEcef, mClockInfo,
                    mIonoDelayMeters, mTropoDelayMeters, mTimeOfClockSeconds,
                    mTimeOfEphemerisSeconds,
                    mIssueOfDataClock, mIssueOfDataEphemeris,
                    mEphemerisSource);
        }
    }
}
