GPS service for android and suggested changes in Necessitas.
BogDan
bog_dan_ro at yahoo.com
Tue May 31 11:14:06 CEST 2011
Hi,
I just come from a long trip, I'll take a look to your mail today.
Thank you !
BogDan.
>________________________________
>From: "frameworks at qlands.com" <frameworks at qlands.com>
>To: necessitas-devel at kde.org
>Sent: Sunday, May 29, 2011 3:58 PM
>Subject: GPS service for android and suggested changes in Necessitas.
>
>
>
>Hi,
>
>
>
>First of all. Thanks for the brilliant work!.... Running QT in Android is just mind-blowing!
>
>
>I just joined the list of developers to contribute to the project. Just after I got a copy of Necessitas 0.1 I had to see how to make the GPS work. I wanted to implement it as most as possible with JNI and with the minimum of java code.
> From a GPS example written in Java (http://hejp.co.uk/android/android-gps-example/) I got that the main challenges were:
>1- To implement the LocationListener interface
>2- To connect to the location manager by Activity.getSystemService(service)
>3- To request the locations using the Listener by LocationManager.requestLocationUpdates(gps provider, double, float, listener,looper)
>
>This main challenges arose from two main constraints:
>1- The LocationListener is an interface so needs to be implemented in a class in a very basic way. Thus I had to include such implementation as part of the industrius java classes: QtGPSListener.java with the following implementation:
>
>package eu.licentia.necessitas.industrius;
> import android.location.Location;
>import android.location.LocationListener;
>import android.location.LocationProvider;
>import android.os.Bundle;
>
>public class QtGPSListener implements LocationListener {
>
> public void onLocationChanged(Location location)
> {
> // Here we get the location data and create a result
> // string (A really basic way of passing data). We called sndonLocationChanged that is
> // in fact connected to the qt application by JNI's registeNatives
> String accuracy;
> accuracy = String.valueOf(location.getAccuracy());
>
> String altitude;
> altitude = String.valueOf(location.getAltitude());
>
> String latitude;
> latitude = String.valueOf(location.getLatitude());
>
> String longitude;
> longitude = String.valueOf(location.getLongitude());
>
> String res;
>
> res = "|AC:" + accuracy;
> res = res + "|AL:" + altitude;
> res = res + "|LA:" + latitude;
> res = res + "|LO:" + longitude + "|";
>
> sndonLocationChanged(res);
> }
>
>
> public void onProviderDisabled(String provider)
> {
> String res;
> res = "Provider " + provider + " is disabled";
> sndonProviderDisabled(res);
> }
>
>
> public void onProviderEnabled(String provider)
> {
> String res;
> res = "Provider " + provider + " is enabled";
> sndonProviderEnabled(res);
> }
>
>
> public void onStatusChanged(String provider, int status, Bundle extras)
> {
> switch (status) {
> case LocationProvider.OUT_OF_SERVICE:
> sndonStatusChanged("Status Changed: Out of Service");
>
> break;
> case LocationProvider.TEMPORARILY_UNAVAILABLE:
> sndonStatusChanged("Status Changed: Temporarily Unavailable");
>
> break;
> case LocationProvider.AVAILABLE:
> sndonStatusChanged("Status Changed: Available");
>
> break;
> }
>
> }
> // List of methods that will be linked in the qt application
> // using JNI's registerNatives
> public static native void sndonLocationChanged(String currLocation);
> public static native void sndonProviderDisabled(String message);
> public static native void sndonProviderEnabled(String message);
> public static native void sndonStatusChanged(String message);
>
>}
>
>2- I had to use the activity running the qt application so I can use getSystemService(String). I though there were some Android API to get an instance to the current activity running (so I can use JNI to get it) but it seems that there is none.
>
>So the only alternative that I had was to pass the activity from QtApplication.java to qtmain_android.cpp. For this I made the following changes:
>
>2.1 - In QtApplication.java: FROM: public static native void startQtApp(String params,String env) TO: public static native void startQtApp(String params,String env,Object currAct)
>2.2 - In qtmain_android.cpp: FROM: static jboolean startQtApp(JNIEnv* env, jobject /*object*/, jstring paramsString, jstring environmentString) TO: tatic jboolean startQtApp(JNIEnv* env, jobject /*object*/, jstring paramsString, jstring environmentString,jobject currAct)
>2.3 - In qtmain_android.cpp: I created a jobject currActivity = NULL that I can extern in my sample application. Plus assigning it a global reference of the current activity: currActivity = env->NewGlobalRef(currAct). This to use it in JNI thread of execution
>
>With this changes I create a QT GPS class with JNI code that access the GPS. I just need to pass it the JavaVM that I extern from qtmain_android.cpp and currActivity.
>
>
>I can see that in 0.2 necessitas include QtLocation.java (which implements the GPS). Although it is fine, I reckon it is better to implement much of that code in the c++ side with JNI and only have the minimum java code just for implementing interfaces for example.
>
>This is an snapshot of the QT GPS class where it connects to the service
>
>
>midGetSystemService = currEnv->GetMethodID(actClass,"getSystemService","(Ljava/lang/String;)Ljava/lang/Object;");
> if (currEnv->ExceptionOccurred())
> {
> //We need to handle errors a bit better! --TODO
> currEnv->ExceptionClear();
> classError = true;
> emit error("JNI--Cannot get method getSystemService()");
> stopGPSService();
> return false; //Return with error
> }
>
> if (midGetSystemService == NULL)
> {
> classError = true;
> emit error("JNI--getSystemService is null");
> stopGPSService();
> return false; //Return with error
> }
>
> //We get a java String from a normal c++ string
> jstring StringArg = currEnv->NewStringUTF("location");
> if (currEnv->ExceptionOccurred())
> {
> classError = true;
> emit error("JNI--Error parsing string!");
> stopGPSService();
> return false; //Return with error
> }
>
> //We get the system service
> jSystemServiceObj = currEnv->CallObjectMethod(currAct,midGetSystemService,StringArg);
> if (currEnv->ExceptionOccurred())
> {
> classError = true;
> emit error("JNI--Error at getting the System service");
> stopGPSService();
> return false; //Return with error
> }
>
> //If the service object is not null we continue
> if (jSystemServiceObj == NULL)
> {
> classError = true;
> emit error("JNI--jSystemServiceObj is null");
> stopGPSService();
> return false; //Return with error
> }
>
> //Now that we have the system service we need to create a new Location Listener object.
>
> // Gets the constructor of the Location Listener class. Please note that
> // listenerClass comes from the modified qtmain_android.cpp
> // It will not work with the orignal qtmain_android.cpp from android-lighthouse
>
> //In a better implementation like a mobility plugin we can get the class using Q_DECL_EXPORT JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
>
> midConstListener = currEnv->GetMethodID(listenerClass, "<init>", "()V");
> if (currEnv->ExceptionOccurred())
> {
> classError = true;
> emit error("JNI--Error registering Listener constructor");
> stopGPSService();
> return false; //Return with error
> }
>
> //If the method id of the constructor is not null we continue
> if (midConstListener == NULL)
> {
> classError = true;
> emit error("JNI--midConstListener is null");
> stopGPSService();
> return false; //Return with error
> }
>
> //Creates a new listener class
> jListenerObj = currEnv->NewObject(listenerClass, midConstListener);
> if (currEnv->ExceptionOccurred())
> {
> classError = true;
> emit error("JNI--Error creating new listener object");
> stopGPSService();
> return false; //Return with error
> }
>
> //If the listener class is not null we continue
> if (jListenerObj == NULL)
> {
> classError = true;
> emit error("JNI--jListenerObj is null");
> stopGPSService();
> return false; //Return with error
> }
>
> // Now that we have the new instance of the listener object
> // We need to connect its callbacks to our methods
>
> //Declaring the methods. This will be used by registerNatives
> JNINativeMethod methods[] =
> {
> {"sndonLocationChanged", "(Ljava/lang/String;)V", (void *)&androidGPS::LocationChanged},
> {"sndonProviderDisabled", "(Ljava/lang/String;)V", (void *)&androidGPS::ProviderDisabled},
> {"sndonProviderEnabled", "(Ljava/lang/String;)V", (void *)&androidGPS::ProviderEnabled},
> {"sndonStatusChanged", "(Ljava/lang/String;)V", (void *)&androidGPS::StatusChanged}
> };
>
>
> //Register the native methods.
> // Basically this joins the methods inside the gpsListener.java class
> // with our c++ methods.
> int numMethods;
> numMethods = sizeof(methods) / sizeof(methods[0]);
> if (currEnv->RegisterNatives(listenerClass, methods, numMethods) < 0)
> {
> if (currEnv->ExceptionOccurred())
> {
> classError = true;
> emit error("JNI--Error running RegisterNatives");
> stopGPSService();
> return false; //Return with error
> }
> else
> {
> emit error("JNI--Error running RegisterNatives");
> stopGPSService();
> return false; //Return with error
> }
> }
>
>
> // Now that we have connected the methos is time to call
> // requestLocationUpdates using the jSystemServiceObj
>
> //Get the method id of requestLocationUpdates
> midRequestLocationUpdates = currEnv->GetMethodID(locManClass,"requestLocationUpdates","(Ljava/lang/String;JFLandroid/location/LocationListener;Landroid/os/Looper;)V");
> if (currEnv->ExceptionOccurred())
> {
> //We need to handle errors a bit better! --TODO
> currEnv->ExceptionClear();
> classError = true;
> emit error("JNI--Cannot get method midRequestLocationUpdates()");
> stopGPSService();
> return false; //Return with error
> }
>
> //If the method is not null we continue
> if (midRequestLocationUpdates == NULL)
> {
> classError = true;
> emit error("JNI--midRequestLocationUpdates is null");
> stopGPSService();
> return false; //Return with error
> }
>
>
> //Finally we call the Request updates
> qDebug() << "Final call!!!";
>
> //Convert a c++ string into jstring
> StringArg = currEnv->NewStringUTF("gps");
>
> // Call RequestLocationUpdates of the System Service object
> // (jlong)20000 means that it will request location updated every 20 seconds
> // (jfloat)10 is the minimum distance interval for notifications, in meters
> // Note that we pass the listener object that we just created jListenerObj
> // and the looper that comes from the modified qtmain_android.cpp
> // we can pass such values as user defined!!!!
>
> currEnv->CallVoidMethod(jSystemServiceObj,midRequestLocationUpdates,StringArg,(jlong)20000,(jfloat)10,jListenerObj,currLoop);
> if (currEnv->ExceptionOccurred())
> {
> classError = true;
> emit error("JNI--Error calling midRequestLocationUpdates");
> stopGPSService();
> return false; //Return with error
> }
>
> //Ready... As it all bound the class the GPS server will update the location
> // every 20 seconds using the gpsListener. As the methods of the gpsListener
> // are bound to the c++ aplication by registerNatives we can have the readings.
>
>To conclude. A JNI implementation of the Android GPS service with very basic java code will require to have a Jobject pointing to the current activity running the QT application. This I reckon would make a Mobility plugin less dependent on external java coding. But where it would be the best place to implement this change? I made it on qtmain_android.cpp and qtApplication.java but for a Mobility plugin this might not be the case.
>
>I register myself on gitorious. For contributing do I need to work on a clone of master?
>
>
>
>Thanks a lot for the brilliant work!
>
>
>
>_______________________________________________
>Necessitas-devel mailing list
>Necessitas-devel at kde.org
>https://mail.kde.org/mailman/listinfo/necessitas-devel
>
>
>
More information about the Necessitas-devel
mailing list