GpsStatusWrapper.java

/*
 * Copyright 2019 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.core.location;

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

import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;

import androidx.annotation.GuardedBy;
import androidx.annotation.RestrictTo;
import androidx.core.util.Preconditions;

import java.util.Iterator;

/** @hide */
@RestrictTo(LIBRARY)
class GpsStatusWrapper extends GnssStatusCompat {

    private static final int GPS_PRN_OFFSET = 0;
    private static final int GPS_PRN_COUNT = 32;
    private static final int SBAS_PRN_MIN = 33;
    private static final int SBAS_PRN_MAX = 64;
    private static final int SBAS_PRN_OFFSET = -87;
    private static final int GLONASS_PRN_OFFSET = 64;
    private static final int GLONASS_PRN_COUNT = 24;
    private static final int QZSS_SVID_MIN = 193;
    private static final int QZSS_SVID_MAX = 200;
    private static final int BEIDOU_PRN_OFFSET = 200;
    private static final int BEIDOU_PRN_COUNT = 35;

    private final GpsStatus mWrapped;

    @GuardedBy("mWrapped")
    private int mCachedSatelliteCount;

    @GuardedBy("mWrapped")
    private Iterator<GpsSatellite> mCachedIterator;
    @GuardedBy("mWrapped")
    private int mCachedIteratorPosition;
    @GuardedBy("mWrapped")
    private GpsSatellite mCachedSatellite;

    GpsStatusWrapper(GpsStatus gpsStatus) {
        mWrapped = Preconditions.checkNotNull(gpsStatus);
        mCachedSatelliteCount = -1;
        mCachedIterator = mWrapped.getSatellites().iterator();
        mCachedIteratorPosition = -1;
        mCachedSatellite = null;
    }

    @Override
    public int getSatelliteCount() {
        synchronized (mWrapped) {
            if (mCachedSatelliteCount == -1) {
                for (@SuppressWarnings("unused") GpsSatellite ignored : mWrapped.getSatellites()) {
                    mCachedSatelliteCount++;
                }
                mCachedSatelliteCount++;
            }

            return mCachedSatelliteCount;
        }
    }

    @Override
    public int getConstellationType(int satelliteIndex) {
        if (VERSION.SDK_INT < VERSION_CODES.N) {
            return CONSTELLATION_GPS;
        } else {
            return getConstellationFromPrn(getSatellite(satelliteIndex).getPrn());
        }
    }

    @Override
    public int getSvid(int satelliteIndex) {
        if (VERSION.SDK_INT < VERSION_CODES.N) {
            return getSatellite(satelliteIndex).getPrn();
        } else {
            return getSvidFromPrn(getSatellite(satelliteIndex).getPrn());
        }
    }

    @Override
    public float getCn0DbHz(int satelliteIndex) {
        return getSatellite(satelliteIndex).getSnr();
    }

    @Override
    public float getElevationDegrees(int satelliteIndex) {
        return getSatellite(satelliteIndex).getElevation();
    }

    @Override
    public float getAzimuthDegrees(int satelliteIndex) {
        return getSatellite(satelliteIndex).getAzimuth();
    }

    @Override
    public boolean hasEphemerisData(int satelliteIndex) {
        return getSatellite(satelliteIndex).hasEphemeris();
    }

    @Override
    public boolean hasAlmanacData(int satelliteIndex) {
        return getSatellite(satelliteIndex).hasAlmanac();
    }

    @Override
    public boolean usedInFix(int satelliteIndex) {
        return getSatellite(satelliteIndex).usedInFix();
    }

    @Override
    public boolean hasCarrierFrequencyHz(int satelliteIndex) {
        return false;
    }

    @Override
    public float getCarrierFrequencyHz(int satelliteIndex) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean hasBasebandCn0DbHz(int satelliteIndex) {
        return false;
    }

    @Override
    public float getBasebandCn0DbHz(int satelliteIndex) {
        throw new UnsupportedOperationException();
    }

    private GpsSatellite getSatellite(int satelliteIndex) {
        GpsSatellite satellite;
        synchronized (mWrapped) {
            if (satelliteIndex < mCachedIteratorPosition) {
                mCachedIterator = mWrapped.getSatellites().iterator();
                mCachedIteratorPosition = -1;
            }
            while (mCachedIteratorPosition < satelliteIndex) {
                mCachedIteratorPosition++;
                if (!mCachedIterator.hasNext()) {
                    mCachedSatellite = null;
                    break;
                } else {
                    mCachedSatellite = mCachedIterator.next();
                }
            }
            satellite = mCachedSatellite;
        }
        return Preconditions.checkNotNull(satellite);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof GpsStatusWrapper)) {
            return false;
        }
        GpsStatusWrapper that = (GpsStatusWrapper) o;
        return mWrapped.equals(that.mWrapped);
    }

    @Override
    public int hashCode() {
        return mWrapped.hashCode();
    }

    private static int getConstellationFromPrn(int prn) {
        if (prn > GPS_PRN_OFFSET && prn <= GPS_PRN_OFFSET + GPS_PRN_COUNT) {
            return CONSTELLATION_GPS;
        } else if (prn >= SBAS_PRN_MIN && prn <= SBAS_PRN_MAX) {
            return CONSTELLATION_SBAS;
        } else if (prn > GLONASS_PRN_OFFSET && prn <= GLONASS_PRN_OFFSET + GLONASS_PRN_COUNT) {
            return CONSTELLATION_GLONASS;
        } else if (prn > BEIDOU_PRN_OFFSET && prn <= BEIDOU_PRN_OFFSET + BEIDOU_PRN_COUNT) {
            return CONSTELLATION_BEIDOU;
        } else if (prn >= QZSS_SVID_MIN && prn <= QZSS_SVID_MAX) {
            return CONSTELLATION_QZSS;
        } else {
            return CONSTELLATION_UNKNOWN;
        }
    }

    private static int getSvidFromPrn(int prn) {
        switch (getConstellationFromPrn(prn)) {
            case CONSTELLATION_SBAS:
                prn -= SBAS_PRN_OFFSET;
                break;
            case CONSTELLATION_GLONASS:
                prn -= GLONASS_PRN_OFFSET;
                break;
            case CONSTELLATION_BEIDOU:
                prn -= BEIDOU_PRN_OFFSET;
                break;
        }
        return prn;
    }
}