Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b16e7800 authored by Mike Lockwood's avatar Mike Lockwood
Browse files

gps: Add GpsStatus.NmeaListener interface for receiving NMEA sentences.



NMEA sentences are passed from the GPS engine to the GpsLocationProvider.
They are then sent via the IGpsStatusListener binder interface to clients
using the same path as the other GPS status information.

Signed-off-by: default avatarMike Lockwood <lockwood@android.com>
parent d4f09592
Loading
Loading
Loading
Loading
+72 −4
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ static jmethodID method_reportLocation;
static jmethodID method_reportStatus;
static jmethodID method_reportSvStatus;
static jmethodID method_reportAGpsStatus;
static jmethodID method_reportNmea;
static jmethodID method_xtraDownloadRequest;

static const GpsInterface* sGpsInterface = NULL;
@@ -44,12 +45,23 @@ static GpsStatus sGpsStatus;
static GpsSvStatus  sGpsSvStatus;
static AGpsStatus   sAGpsStatus;

// buffer for NMEA data
#define NMEA_SENTENCE_LENGTH    100
#define NMEA_SENTENCE_COUNT     40
struct NmeaSentence {
    GpsUtcTime  timestamp;
    char        nmea[NMEA_SENTENCE_LENGTH];
};
static NmeaSentence sNmeaBuffer[NMEA_SENTENCE_LENGTH];
static int mNmeaSentenceCount = 0;

// a copy of the data shared by android_location_GpsLocationProvider_wait_for_event
// and android_location_GpsLocationProvider_read_status
static GpsLocation  sGpsLocationCopy;
static GpsStatus    sGpsStatusCopy;
static GpsSvStatus  sGpsSvStatusCopy;
static AGpsStatus   sAGpsStatusCopy;
static NmeaSentence sNmeaBufferCopy[NMEA_SENTENCE_LENGTH];

enum CallbackType {
    kLocation = 1,
@@ -58,6 +70,7 @@ enum CallbackType {
    kAGpsStatus = 8,
    kXtraDownloadRequest = 16,
    kDisableRequest = 32,
    kNmeaAvailable = 64,
}; 
static int sPendingCallbacks;

@@ -96,6 +109,30 @@ static void sv_status_callback(GpsSvStatus* sv_status)
    pthread_mutex_unlock(&sEventMutex);
}

static void nmea_callback(GpsUtcTime timestamp, const char* nmea, int length)
{
    pthread_mutex_lock(&sEventMutex);

    if (length >= NMEA_SENTENCE_LENGTH) {
        LOGE("NMEA data too long in nmea_callback (length = %d)\n", length);
        length = NMEA_SENTENCE_LENGTH - 1;
    }
    if (mNmeaSentenceCount >= NMEA_SENTENCE_COUNT) {
        LOGE("NMEA data overflowed buffer\n");
        pthread_mutex_unlock(&sEventMutex);
        return;
    }

    sPendingCallbacks |= kNmeaAvailable;
    sNmeaBuffer[mNmeaSentenceCount].timestamp = timestamp;
    memcpy(sNmeaBuffer[mNmeaSentenceCount].nmea, nmea, length);
    sNmeaBuffer[mNmeaSentenceCount].nmea[length] = 0;
    mNmeaSentenceCount++;

    pthread_cond_signal(&sEventCond);
    pthread_mutex_unlock(&sEventMutex);
}

static void agps_status_callback(AGpsStatus* agps_status)
{
    pthread_mutex_lock(&sEventMutex);
@@ -111,6 +148,7 @@ GpsCallbacks sGpsCallbacks = {
    location_callback,
    status_callback,
    sv_status_callback,
    nmea_callback
};

static void
@@ -135,6 +173,7 @@ static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env,
    method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");
    method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "()V");
    method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II)V");
    method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(IJ)V");
    method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V");
}

@@ -200,13 +239,21 @@ static void android_location_GpsLocationProvider_wait_for_event(JNIEnv* env, job
    // copy and clear the callback flags
    int pendingCallbacks = sPendingCallbacks;
    sPendingCallbacks = 0;
    int nmeaSentenceCount = mNmeaSentenceCount;
    mNmeaSentenceCount = 0;
    
    // copy everything and unlock the mutex before calling into Java code to avoid the possibility
    // of timeouts in the GPS engine.
    if (pendingCallbacks & kLocation)
        memcpy(&sGpsLocationCopy, &sGpsLocation, sizeof(sGpsLocationCopy));
    if (pendingCallbacks & kStatus)
        memcpy(&sGpsStatusCopy, &sGpsStatus, sizeof(sGpsStatusCopy));
    if (pendingCallbacks & kSvStatus)
        memcpy(&sGpsSvStatusCopy, &sGpsSvStatus, sizeof(sGpsSvStatusCopy));
    if (pendingCallbacks & kAGpsStatus)
        memcpy(&sAGpsStatusCopy, &sAGpsStatus, sizeof(sAGpsStatusCopy));
    if (pendingCallbacks & kNmeaAvailable)
        memcpy(&sNmeaBufferCopy, &sNmeaBuffer, nmeaSentenceCount * sizeof(sNmeaBuffer[0]));
    pthread_mutex_unlock(&sEventMutex);   

    if (pendingCallbacks & kLocation) { 
@@ -225,6 +272,11 @@ static void android_location_GpsLocationProvider_wait_for_event(JNIEnv* env, job
    if (pendingCallbacks & kAGpsStatus) {
        env->CallVoidMethod(obj, method_reportAGpsStatus, sAGpsStatusCopy.type, sAGpsStatusCopy.status);
    }  
    if (pendingCallbacks & kNmeaAvailable) {
        for (int i = 0; i < nmeaSentenceCount; i++) {
            env->CallVoidMethod(obj, method_reportNmea, i, sNmeaBuffer[i].timestamp);
        }
    }
    if (pendingCallbacks & kXtraDownloadRequest) {    
        env->CallVoidMethod(obj, method_xtraDownloadRequest);
    }
@@ -264,6 +316,21 @@ static jint android_location_GpsLocationProvider_read_sv_status(JNIEnv* env, job
    return num_svs;
}

static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj, jint index, jbyteArray nmeaArray, jint buffer_size)
{
    // this should only be called from within a call to reportStatus, so we don't need to lock here

    jbyte* nmea = env->GetByteArrayElements(nmeaArray, 0);

    int length = strlen(sNmeaBuffer[index].nmea);
    if (length > buffer_size)
        length = buffer_size;
    memcpy(nmea, sNmeaBuffer[index].nmea, length);

    env->ReleaseByteArrayElements(nmeaArray, nmea, 0);
    return length;
}

static void android_location_GpsLocationProvider_inject_time(JNIEnv* env, jobject obj, jlong time, 
        jlong timeReference, jint uncertainty)
{
@@ -360,6 +427,7 @@ static JNINativeMethod sMethods[] = {
    {"native_delete_aiding_data", "(I)V", (void*)android_location_GpsLocationProvider_delete_aiding_data},
    {"native_wait_for_event", "()V", (void*)android_location_GpsLocationProvider_wait_for_event},
    {"native_read_sv_status", "([I[F[F[F[I)I", (void*)android_location_GpsLocationProvider_read_sv_status},
    {"native_read_nmea", "(I[BI)I", (void*)android_location_GpsLocationProvider_read_nmea},
    {"native_inject_time", "(JJI)V", (void*)android_location_GpsLocationProvider_inject_time},
    {"native_inject_location", "(DDF)V", (void*)android_location_GpsLocationProvider_inject_location},
    {"native_supports_xtra", "()Z", (void*)android_location_GpsLocationProvider_supports_xtra},
+9 −0
Original line number Diff line number Diff line
@@ -115,6 +115,15 @@ public final class GpsStatus {
        void onGpsStatusChanged(int event);
    }

    /**
     * Used for receiving NMEA data from the GPS.
     *
     * {@hide}
     */
    public interface NmeaListener {
        void onNmeaReceived(long timestamp, String nmea);
    }

    GpsStatus() {
        for (int i = 0; i < mSatellites.length; i++) {
            mSatellites[i] = new GpsSatellite(i + 1);
+1 −0
Original line number Diff line number Diff line
@@ -29,4 +29,5 @@ oneway interface IGpsStatusListener
    void onSvStatusChanged(int svCount, in int[] prns, in float[] snrs, 
            in float[] elevations, in float[] azimuths, 
            int ephemerisMask, int almanacMask, int usedInFixMask);
    void onNmeaReceived(long timestamp, String nmea);
}
+126 −20
Original line number Diff line number Diff line
@@ -51,6 +51,8 @@ public class LocationManager {
    private ILocationManager mService;
    private final HashMap<GpsStatus.Listener, GpsStatusListenerTransport> mGpsStatusListeners =
            new HashMap<GpsStatus.Listener, GpsStatusListenerTransport>();
    private final HashMap<GpsStatus.NmeaListener, GpsStatusListenerTransport> mNmeaListeners =
            new HashMap<GpsStatus.NmeaListener, GpsStatusListenerTransport>();
    private final GpsStatus mGpsStatus = new GpsStatus();

    /**
@@ -1123,33 +1125,62 @@ public class LocationManager {
    private class GpsStatusListenerTransport extends IGpsStatusListener.Stub {

        private final GpsStatus.Listener mListener;
        private final GpsStatus.NmeaListener mNmeaListener;

        // This must not equal any of the GpsStatus event IDs
        private static final int NMEA_RECEIVED = 1000;

        private class Nmea {
            long mTimestamp;
            String mNmea;

            Nmea(long timestamp, String nmea) {
                mTimestamp = timestamp;
                mNmea = nmea;
            }
        }
        private ArrayList<Nmea> mNmeaBuffer;

        GpsStatusListenerTransport(GpsStatus.Listener listener) {
            mListener = listener;
            mNmeaListener = null;
        }

        GpsStatusListenerTransport(GpsStatus.NmeaListener listener) {
            mNmeaListener = listener;
            mListener = null;
            mNmeaBuffer = new ArrayList<Nmea>();
        }

        public void onGpsStarted() {
            if (mListener != null) {
                Message msg = Message.obtain();
                msg.what = GpsStatus.GPS_EVENT_STARTED;
                mGpsHandler.sendMessage(msg);
            }
        }

        public void onGpsStopped() {
            if (mListener != null) {
                Message msg = Message.obtain();
                msg.what = GpsStatus.GPS_EVENT_STOPPED;
                mGpsHandler.sendMessage(msg);
            }
        }

        public void onFirstFix(int ttff) {
            if (mListener != null) {
                mGpsStatus.setTimeToFirstFix(ttff);
                Message msg = Message.obtain();
                msg.what = GpsStatus.GPS_EVENT_FIRST_FIX;
                mGpsHandler.sendMessage(msg);
            }
        }

        public void onSvStatusChanged(int svCount, int[] prns, float[] snrs,
                float[] elevations, float[] azimuths, int ephemerisMask,
                int almanacMask, int usedInFixMask) {
            if (mListener != null) {
                mGpsStatus.setStatus(svCount, prns, snrs, elevations, azimuths,
                        ephemerisMask, almanacMask, usedInFixMask);

@@ -1159,15 +1190,40 @@ public class LocationManager {
                mGpsHandler.removeMessages(GpsStatus.GPS_EVENT_SATELLITE_STATUS);
                mGpsHandler.sendMessage(msg);
            }
        }

        public void onNmeaReceived(long timestamp, String nmea) {
            if (mNmeaListener != null) {
                synchronized (mNmeaBuffer) {
                    mNmeaBuffer.add(new Nmea(timestamp, nmea));
                }
                Message msg = Message.obtain();
                msg.what = NMEA_RECEIVED;
                // remove any NMEA_RECEIVED messages already in the queue
                mGpsHandler.removeMessages(NMEA_RECEIVED);
                mGpsHandler.sendMessage(msg);
            }
        }

        private final Handler mGpsHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == NMEA_RECEIVED) {
                    synchronized (mNmeaBuffer) {
                        int length = mNmeaBuffer.size();
                        for (int i = 0; i < length; i++) {
                            Nmea nmea = mNmeaBuffer.get(i);
                            mNmeaListener.onNmeaReceived(nmea.mTimestamp, nmea.mNmea);
                        }
                        mNmeaBuffer.clear();
                    }
                } else {
                    // synchronize on mGpsStatus to ensure the data is copied atomically.
                    synchronized(mGpsStatus) {
                        mListener.onGpsStatusChanged(msg.what);
                    }
                }
            }
        };
    }

@@ -1217,6 +1273,56 @@ public class LocationManager {
        }
    }

    /**
     * Adds an NMEA listener.
     *
     * @param listener NMEA listener object to register
     *
     * @return true if the listener was successfully added
     *
     * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
     *
     * {@hide}
     */
    public boolean addNmeaListener(GpsStatus.NmeaListener listener) {
        boolean result;

        if (mNmeaListeners.get(listener) != null) {
            // listener is already registered
            return true;
        }
        try {
            GpsStatusListenerTransport transport = new GpsStatusListenerTransport(listener);
            result = mService.addGpsStatusListener(transport);
            if (result) {
                mNmeaListeners.put(listener, transport);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in registerGpsStatusListener: ", e);
            result = false;
        }

        return result;
    }

    /**
     * Removes an NMEA listener.
     *
     * @param listener NMEA listener object to remove
     *
     * {@hide}
     */
    public void removeNmeaListener(GpsStatus.NmeaListener listener) {
        try {
            GpsStatusListenerTransport transport = mNmeaListeners.remove(listener);
            if (transport != null) {
                mService.removeGpsStatusListener(transport);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in unregisterGpsStatusListener: ", e);
        }
    }

     /**
     * Retrieves information about the current status of the GPS engine.
     * This should only be called from the {@link GpsStatus.Listener#onGpsStatusChanged}
+29 −0
Original line number Diff line number Diff line
@@ -1014,6 +1014,32 @@ public class GpsLocationProvider extends ILocationProvider.Stub {
        }
    }

    /**
     * called from native code to report NMEA data received
     */
    private void reportNmea(int index, long timestamp) {
        synchronized(mListeners) {
            int size = mListeners.size();
            if (size > 0) {
                // don't bother creating the String if we have no listeners
                int length = native_read_nmea(index, mNmeaBuffer, mNmeaBuffer.length);
                String nmea = new String(mNmeaBuffer, 0, length);

                for (int i = 0; i < size; i++) {
                    Listener listener = mListeners.get(i);
                    try {
                        listener.mListener.onNmeaReceived(timestamp, nmea);
                    } catch (RemoteException e) {
                        Log.w(TAG, "RemoteException in reportNmea");
                        mListeners.remove(listener);
                        // adjust for size of list changing
                        size--;
                    }
                }
            }
        }
    }

    private void xtraDownloadRequest() {
        if (Config.LOGD) Log.d(TAG, "xtraDownloadRequest");
        if (mNetworkThread != null) {
@@ -1194,6 +1220,8 @@ public class GpsLocationProvider extends ILocationProvider.Stub {
    private float mSvAzimuths[] = new float[MAX_SVS];
    private int mSvMasks[] = new int[3];
    private int mSvCount;
    // preallocated to avoid memory allocation in reportNmea()
    private byte[] mNmeaBuffer = new byte[120];

    static { class_init_native(); }
    private static native void class_init_native();
@@ -1211,6 +1239,7 @@ public class GpsLocationProvider extends ILocationProvider.Stub {
    // mask[0] is ephemeris mask and mask[1] is almanac mask
    private native int native_read_sv_status(int[] svs, float[] snrs,
            float[] elevations, float[] azimuths, int[] masks);
    private native int native_read_nmea(int index, byte[] buffer, int bufferSize);
    private native void native_inject_location(double latitude, double longitude, float accuracy);

    // XTRA Support