<HTML>
<style> BODY { font-family:Arial, Helvetica, sans-serif;font-size:12px; }</style>
        
        
        
        <style type="text/css">p { margin-bottom: 0.08in; }a:link { }</style><p class="western" style="margin-bottom: 0in;"><span style="font-style: normal; font-size: 12pt;">Hi,<br>
</span></p><p class="western" style="margin-bottom: 0in;"><br>
<span style="font-style: normal; font-size: 12pt;"></span></p><p class="western" style="margin-bottom: 0in;"><span style="font-style: normal; font-size: 12pt;">First of all. Thanks for the brilliant work!.... Running QT in Android is just mind-blowing!</span></p><p class="western" style="margin-bottom: 0in;"><span style="font-style: normal; font-size: 12pt;"><br>
</span></p><p class="western" style="margin-bottom: 0in;"><span style="font-style: normal; font-size: 12pt;">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.<br>
From a GPS example written in Java (<a href="http://hejp.co.uk/android/android-gps-example/)">http://hejp.co.uk/android/android-gps-example/)</a> I got that the main challenges were:<br>
1- To implement the LocationListener interface<br>
2- To connect to the location manager by Activity.getSystemService(service)<br>
3- To request the locations using the Listener by LocationManager.requestLocationUpdates(gps provider, double, float, listener,looper)<br>
<br>
This main challenges arose from two main constraints:<br>
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:<br>
<br>
package eu.licentia.necessitas.industrius;
<br>
import android.location.Location;
<br>
import android.location.LocationListener;
<br>
import android.location.LocationProvider;
<br>
import android.os.Bundle;
<br>
<br>
public class QtGPSListener implements LocationListener {
<br>
<br>
public void onLocationChanged(Location location)
<br>
{
<br>
// Here we get the location data and create a result
<br>
// string (A really basic way of passing data). We called sndonLocationChanged that is
<br>
// in fact connected to the qt application by JNI's registeNatives
<br>
String accuracy;
<br>
accuracy = String.valueOf(location.getAccuracy());
<br>
<br>
String altitude;
<br>
altitude = String.valueOf(location.getAltitude());
<br>
<br>
String latitude;
<br>
latitude = String.valueOf(location.getLatitude());
<br>
<br>
String longitude;
<br>
longitude = String.valueOf(location.getLongitude());
<br>
<br>
String res;
<br>
<br>
res = "|AC:" + accuracy;
<br>
res = res + "|AL:" + altitude;
<br>
res = res + "|LA:" + latitude;
<br>
res = res + "|LO:" + longitude + "|";
<br>
<br>
sndonLocationChanged(res);
<br>
}
<br>
<br>
<br>
public void onProviderDisabled(String provider)
<br>
{
<br>
String res;
<br>
res = "Provider " + provider + " is disabled";
<br>
sndonProviderDisabled(res);
<br>
}
<br>
<br>
<br>
public void onProviderEnabled(String provider)
<br>
{
<br>
String res;
<br>
res = "Provider " + provider + " is enabled";
<br>
sndonProviderEnabled(res);
<br>
}
<br>
<br>
<br>
public void onStatusChanged(String provider, int status, Bundle extras)
<br>
{
<br>
switch (status) {
<br>
case LocationProvider.OUT_OF_SERVICE:
<br>
sndonStatusChanged("Status Changed: Out of Service");
<br>
<br>
break;
<br>
case LocationProvider.TEMPORARILY_UNAVAILABLE:
<br>
sndonStatusChanged("Status Changed: Temporarily Unavailable");
<br>
<br>
break;
<br>
case LocationProvider.AVAILABLE:
<br>
sndonStatusChanged("Status Changed: Available");
<br>
<br>
break;
<br>
}
<br>
<br>
}
<br>
// List of methods that will be linked in the qt application
<br>
// using JNI's registerNatives
<br>
public static native void sndonLocationChanged(String currLocation);
<br>
public static native void sndonProviderDisabled(String message);
<br>
public static native void sndonProviderEnabled(String message);
<br>
public static native void sndonStatusChanged(String message);
<br>
<br>
}<br>
<br>
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.<br>
<br>
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:<br>
<br>
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)<br>
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)<br>
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 <br>
<br>
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.<br>
<br>
<br>
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.<br>
<br>
This is an snapshot of the QT GPS class where it connects to the service<br>
<br>
<br>
midGetSystemService = currEnv->GetMethodID(actClass,"getSystemService","(Ljava/lang/String;)Ljava/lang/Object;");<br>
if (currEnv->ExceptionOccurred())<br>
{<br>
//We need to handle errors a bit better! --TODO<br>
currEnv->ExceptionClear();<br>
classError = true;<br>
emit error("JNI--Cannot get method getSystemService()");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
<br>
if (midGetSystemService == NULL)<br>
{<br>
classError = true;<br>
emit error("JNI--getSystemService is null");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
<br>
//We get a java String from a normal c++ string<br>
jstring StringArg = currEnv->NewStringUTF("location");<br>
if (currEnv->ExceptionOccurred())<br>
{<br>
classError = true;<br>
emit error("JNI--Error parsing string!");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
<br>
//We get the system service<br>
jSystemServiceObj = currEnv->CallObjectMethod(currAct,midGetSystemService,StringArg);<br>
if (currEnv->ExceptionOccurred())<br>
{<br>
classError = true;<br>
emit error("JNI--Error at getting the System service");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
<br>
//If the service object is not null we continue<br>
if (jSystemServiceObj == NULL)<br>
{<br>
classError = true;<br>
emit error("JNI--jSystemServiceObj is null");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
<br>
//Now that we have the system service we need to create a new Location Listener object.<br>
<br>
// Gets the constructor of the Location Listener class. Please note that<br>
// listenerClass comes from the modified qtmain_android.cpp<br>
// It will not work with the orignal qtmain_android.cpp from android-lighthouse<br>
<br>
//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*/)<br>
<br>
midConstListener = currEnv->GetMethodID(listenerClass, "<init>", "()V");<br>
if (currEnv->ExceptionOccurred())<br>
{<br>
classError = true;<br>
emit error("JNI--Error registering Listener constructor");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
<br>
//If the method id of the constructor is not null we continue<br>
if (midConstListener == NULL)<br>
{<br>
classError = true;<br>
emit error("JNI--midConstListener is null");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
<br>
//Creates a new listener class<br>
jListenerObj = currEnv->NewObject(listenerClass, midConstListener);<br>
if (currEnv->ExceptionOccurred())<br>
{<br>
classError = true;<br>
emit error("JNI--Error creating new listener object");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
<br>
//If the listener class is not null we continue<br>
if (jListenerObj == NULL)<br>
{<br>
classError = true;<br>
emit error("JNI--jListenerObj is null");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
<br>
// Now that we have the new instance of the listener object<br>
// We need to connect its callbacks to our methods<br>
<br>
//Declaring the methods. This will be used by registerNatives<br>
JNINativeMethod methods[] =<br>
{<br>
{"sndonLocationChanged", "(Ljava/lang/String;)V", (void *)&androidGPS::LocationChanged},<br>
{"sndonProviderDisabled", "(Ljava/lang/String;)V", (void *)&androidGPS::ProviderDisabled},<br>
{"sndonProviderEnabled", "(Ljava/lang/String;)V", (void *)&androidGPS::ProviderEnabled},<br>
{"sndonStatusChanged", "(Ljava/lang/String;)V", (void *)&androidGPS::StatusChanged}<br>
};<br>
<br>
<br>
//Register the native methods.<br>
// Basically this joins the methods inside the gpsListener.java class<br>
// with our c++ methods.<br>
int numMethods;<br>
numMethods = sizeof(methods) / sizeof(methods[0]);<br>
if (currEnv->RegisterNatives(listenerClass, methods, numMethods) < 0)<br>
{<br>
if (currEnv->ExceptionOccurred())<br>
{<br>
classError = true;<br>
emit error("JNI--Error running RegisterNatives");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
else<br>
{<br>
emit error("JNI--Error running RegisterNatives");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
}<br>
<br>
<br>
// Now that we have connected the methos is time to call<br>
// requestLocationUpdates using the jSystemServiceObj<br>
<br>
//Get the method id of requestLocationUpdates<br>
midRequestLocationUpdates = currEnv->GetMethodID(locManClass,"requestLocationUpdates","(Ljava/lang/String;JFLandroid/location/LocationListener;Landroid/os/Looper;)V");<br>
if (currEnv->ExceptionOccurred())<br>
{<br>
//We need to handle errors a bit better! --TODO<br>
currEnv->ExceptionClear();<br>
classError = true;<br>
emit error("JNI--Cannot get method midRequestLocationUpdates()");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
<br>
//If the method is not null we continue<br>
if (midRequestLocationUpdates == NULL)<br>
{<br>
classError = true;<br>
emit error("JNI--midRequestLocationUpdates is null");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
<br>
<br>
//Finally we call the Request updates<br>
qDebug() << "Final call!!!";<br>
<br>
//Convert a c++ string into jstring<br>
StringArg = currEnv->NewStringUTF("gps");<br>
<br>
// Call RequestLocationUpdates of the System Service object<br>
// (jlong)20000 means that it will request location updated every 20 seconds<br>
// (jfloat)10 is the minimum distance interval for notifications, in meters<br>
// Note that we pass the listener object that we just created jListenerObj<br>
// and the looper that comes from the modified qtmain_android.cpp</span></p><p class="western" style="margin-bottom: 0in;"><span style="font-style: normal; font-size: 12pt;"> // we can pass such values as user defined!!!!<br>
</span></p><p class="western" style="margin-bottom: 0in;"><span style="font-style: normal; font-size: 12pt;"> currEnv->CallVoidMethod(jSystemServiceObj,midRequestLocationUpdates,StringArg,(jlong)20000,(jfloat)10,jListenerObj,currLoop);<br>
if (currEnv->ExceptionOccurred())<br>
{<br>
classError = true;<br>
emit error("JNI--Error calling midRequestLocationUpdates");<br>
stopGPSService();<br>
return false; //Return with error<br>
}<br>
<br>
//Ready... As it all bound the class the GPS server will update the location<br>
// every 20 seconds using the gpsListener. As the methods of the gpsListener<br>
// are bound to the c++ aplication by registerNatives we can have the readings. <br>
<br>
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.<br>
<br>
I register myself on gitorious. For contributing do I need to work on a clone of master?<br>
</span></p><p class="western" style="margin-bottom: 0in;"><br>
<span style="font-style: normal; font-size: 12pt;"></span></p><p class="western" style="margin-bottom: 0in;"><span style="font-style: normal; font-size: 12pt;">Thanks a lot for the brilliant work!</span></p><p class="western" style="margin-bottom: 0in;"><span style="font-style: normal; font-size: 12pt;"><br>
</span></p>
<BR></HTML>