Merge change 20290

* changes:
  gps: Add GpsStatus.NmeaListener interface for receiving NMEA sentences.
This commit is contained in:
Android (Google) Code Review
2009-08-06 12:48:08 -07:00
5 changed files with 237 additions and 24 deletions

View File

@@ -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},

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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,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<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() {
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}

View File

@@ -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