diff --git a/core/jni/android_location_GpsLocationProvider.cpp b/core/jni/android_location_GpsLocationProvider.cpp index bf0bd65eddbc2..90a04873a5da3 100755 --- a/core/jni/android_location_GpsLocationProvider.cpp +++ b/core/jni/android_location_GpsLocationProvider.cpp @@ -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. - memcpy(&sGpsLocationCopy, &sGpsLocation, sizeof(sGpsLocationCopy)); - memcpy(&sGpsStatusCopy, &sGpsStatus, sizeof(sGpsStatusCopy)); - memcpy(&sGpsSvStatusCopy, &sGpsSvStatus, sizeof(sGpsSvStatusCopy)); - memcpy(&sAGpsStatusCopy, &sAGpsStatus, sizeof(sAGpsStatusCopy)); + 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}, diff --git a/location/java/android/location/GpsStatus.java b/location/java/android/location/GpsStatus.java index 2cda7faafa547..883ee4f2fbb24 100644 --- a/location/java/android/location/GpsStatus.java +++ b/location/java/android/location/GpsStatus.java @@ -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); diff --git a/location/java/android/location/IGpsStatusListener.aidl b/location/java/android/location/IGpsStatusListener.aidl index 5dc0fe8087a16..62b1c6b9b35c1 100644 --- a/location/java/android/location/IGpsStatusListener.aidl +++ b/location/java/android/location/IGpsStatusListener.aidl @@ -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); } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index ca16f1901f34e..8f17e78259900 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -51,6 +51,8 @@ public class LocationManager { private ILocationManager mService; private final HashMap mGpsStatusListeners = new HashMap(); + private final HashMap mNmeaListeners = + new HashMap(); private final GpsStatus mGpsStatus = new GpsStatus(); /** @@ -1123,49 +1125,103 @@ 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 mNmeaBuffer; GpsStatusListenerTransport(GpsStatus.Listener listener) { mListener = listener; + mNmeaListener = null; + } + + GpsStatusListenerTransport(GpsStatus.NmeaListener listener) { + mNmeaListener = listener; + mListener = null; + mNmeaBuffer = new ArrayList(); } public void onGpsStarted() { - Message msg = Message.obtain(); - msg.what = GpsStatus.GPS_EVENT_STARTED; - mGpsHandler.sendMessage(msg); + if (mListener != null) { + Message msg = Message.obtain(); + msg.what = GpsStatus.GPS_EVENT_STARTED; + mGpsHandler.sendMessage(msg); + } } public void onGpsStopped() { - Message msg = Message.obtain(); - msg.what = GpsStatus.GPS_EVENT_STOPPED; - mGpsHandler.sendMessage(msg); + if (mListener != null) { + Message msg = Message.obtain(); + msg.what = GpsStatus.GPS_EVENT_STOPPED; + mGpsHandler.sendMessage(msg); + } } public void onFirstFix(int ttff) { - mGpsStatus.setTimeToFirstFix(ttff); - Message msg = Message.obtain(); - msg.what = GpsStatus.GPS_EVENT_FIRST_FIX; - mGpsHandler.sendMessage(msg); + 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) { - mGpsStatus.setStatus(svCount, prns, snrs, elevations, azimuths, - ephemerisMask, almanacMask, usedInFixMask); + if (mListener != null) { + mGpsStatus.setStatus(svCount, prns, snrs, elevations, azimuths, + ephemerisMask, almanacMask, usedInFixMask); - Message msg = Message.obtain(); - msg.what = GpsStatus.GPS_EVENT_SATELLITE_STATUS; - // remove any SV status messages already in the queue - mGpsHandler.removeMessages(GpsStatus.GPS_EVENT_SATELLITE_STATUS); - mGpsHandler.sendMessage(msg); + Message msg = Message.obtain(); + msg.what = GpsStatus.GPS_EVENT_SATELLITE_STATUS; + // remove any SV status messages already in the queue + 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) { - // synchronize on mGpsStatus to ensure the data is copied atomically. - synchronized(mGpsStatus) { - mListener.onGpsStatusChanged(msg.what); + 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} diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java index aaac192ad3adf..bdef01f75cdb0 100755 --- a/location/java/com/android/internal/location/GpsLocationProvider.java +++ b/location/java/com/android/internal/location/GpsLocationProvider.java @@ -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