This tutorial covers the integration of the ALOQA TraX Java library in a Java MIDlet.
The first part shows how to embed the library in your MIDlet project and how to initialize the TraX Client in order to access the location data of this device via any local or remote API, like the AJAX API. The next section deals with the ALOQA TraX API itself and presents different request types. You can poll the position of a target or register for certain location-based events like a target entering an area or two persons approaching each other. The full example is available for download. In order to use the APIs, you need to request an API key for your application.
We're looking forward to your feedback, so create a ticket at our helpdesk or send us an email to support@aloqa.com.
During this tutorial we will create our own location-based Java MIDlet using the ALOQA TraX services. Therefore we're going to install and setup the required software and development environment in this section. Afterwards we'll add the ALOQA TraX Java library to our project and embed the Javadoc-based documentation into our development platform.
In order to conveniently develop the MIDlet we need the following tools:
Of course, you can use other tools than listed above. The next two sections will explain the steps of integrating the library and documentation according to this setup.
If you do not already have an existing J2ME project, we create a new one. Select the menu item File -> New -> Project... In the wizard choose J2ME Midlet Suite
(in folder J2ME). On the next page enter a name for the project and click through the wizard.
Then a right click on the project opens a contextual menu, where you have to select Properties. On the left side of the window go to Java Build Path and choose the Libraries tab. Click on the
Button Add External JARs... and select the location you unpacked the ALOQA TraX Java API for J2ME package.
Afterwards go to the next tab called Order and Export and check the box before ALOQA_TraX_J2ME_API.jar to export the library to the final midlet file. Otherwise the library would be missing on the mobile device.
Now expand the project folder in Eclipse and you should see a folder called Referenced Libraries containing the ALOQA TraX library. Again a right click on the library opens a contextual menu,
where you have to select Properties. On the left side of the window go to Javadoc Location and enter the following url as Javadoc location path:
http://dev.aloqa.com/javadoc
Then close the window by pressing OK. Now the Javadoc-based documention is integrated in Eclipse and you can access it while writing code.
In this section we will setup the TraX Client and open a connection to the ALOQA TraX Servers.
Now we create a new instance of TraXClient. The TraXClient will automatically start the positioning procedure. For that purpose it uses the built-in Location API.
TraXClient tc = new TraXClient("appID", "username");
"AppID" stands for your application key. You must provide a valid key, in order to be able to connect to the TraX Servers. So just request an API key for your application or you can use "aloqatest" during development. In case of "aloqatest" be aware, that anyone gets access to your position data since this is a public account.
"username" must be replaced with the name of the target. Therefore you should ask the user of your application for a username. Of course you can pass the same user name utilized in your service to the TraX Client. Note: The user names only have to be unique within your application key.
In case we want to access an external gps receiver being connected to the mobile phone via Bluetooth, we must add another position provider to the constructor. For NMEA based Bluetooth gps receivcer we can use the built-in BluetoothNMEAFactory:
TraXClient tc = new TraXClient("appID", "username", new BluetoothNMEAFactory());
If we want to use simulated position data for development purpose, we can either configure the emulator to do so or tell TraXClient to simulate position data according to a certain movement pattern. For the built-in Random Waypoints change the line above to this one:
TraXClient tc = new TraXClient("appID", "username",
new RandomWaypointFactory(40,41,10,11,300000,1000,2000,20000));
For more details on the parameters of Random Waypoints or how to create your own movement patttern see the Class Reference of RandomWaypointFactory as well as PositionProvider and PositionProviderFactory.
The connection parameters consist right now of only the password for your application key. Therefore the password is identical for all targets and the user should not be asked for.
ConnectionParameters con = new ConnectionParameters("password");
Note: If "aloqatest" is used as application key, the password will not be checked.
Now we open the connection to the ALOQA TraX Servers.
tc.getConnection_SAP_Target().startConnecting(con, Connection_SAP_Target.POLICY_NEW_THREAD_FOR_EACH_EVENT);
By default it is recommended to use the "new-thread-policy". This means, that for each update a new thread is created. This policy is the least efficient, but most robust of all. The only problem is, that if you receive a lot of updates at the same time, you could get an OutOfMemoryExceptions depending on the size of the virtual machine on the mobile device. Therefore we provide also a policy, which uses the same thread for each update (POLICY_SINGLE_THREAD). It is very efficient, but can create a deadlock depending on the calls made by your application within the update thread. A compromise between efficiency and usability is the thread pool policy (POLICY_SIMPLE_THREAD_POOL), where the number of threads is limited. For more information and examples see the Class Reference of Connection_SAP_Target.
As from now the position data of the target and associated events can be accessed via the different APIs, like the AJAX API. The next chapter describes the local API, which lets us sending requests to other targets from within the MIDlet. Before, we have a look at the status of the TraX Client.
Often you want to visualize the state of your application to the user. In case of the TraX Client, it could be waiting for a position fix (e.g. using GPS) or being connected or diconnected. Therefore we have to implement the TraXStatusListener interface.
public class LocationMIDlet extends MIDlet implements CommandListener, TraXStatusListener { ... }
The interface consists of one method, named traXStatusChanged(TraXStatus newStatus), which is executed each time the status changes. So we implement this method and check for two properties.
public void traXStatusChanged(TraXStatus newStatus) {
// Check for connection
if (newStatus.isConnected()) { ... }
// Check for position fix
if (newStatus.isPositionAvailable()) { ... }
}
Then we register the current class for status updates after initializing the TraXClient instance.
TraXClient tc = new TraXClient("appID", "username");
tc.setStatusListener(this);
In this section we will configure our class for the Java API as well as send requests and receive updates from the Java API.
First of all we have to add another interface (App_SAP_TraXAPI) to our class.
public class LocationMIDlet extends MIDlet implements CommandListener, TraXStatusListener, App_SAP_TraXAPI { ... }
Now we have to implement the update method of App_SAP_TraXAPI, which is called by the TraX Client on each detected event.
public void update(String jobId, AsynchronousResponse res) { ... }
We will look at this function in more detail later in this tutorial and focus now on sending requests to the TraX Client.
Therefore we need a reference to the TraX Java API.
TraXAPI_SAP_App api = tc.getTraXAPI_SAP_App();
Then we need a valid handle to access the API functions.
TraXHandle handle = tc.getHandleManager().createHandle();
Finally, we set this class as the receiver of asynchronous (spatial) events. These events will be sent to the update method, we implemented before.
handle.setApp(this);
For initializing the application we may want a current snapshot of our friends' position. Therefore we use the synchronous PollingRequest
String[] friends = new String[] { "dummy", "mygirlfriend", "Fred",
"Fritz", "Freud", "Anna", "Hanna", "Nanna" };
PollingRequest pr = new PollingRequest(friends);
Let TraX poll all of our friends in parallel and then cast the response to a PollingResponse.
PollingResponse response = (PollingResponse) api.synchRequest(handle.getId(), pr);
Now we can read out all of the positions of our friends.
PositionUpdate[] pus = response.getPositionUpdates();
for (int i = 0; i < pus.length; i++) {
if (pus[i].isValid()) {
System.out.println(friends[i] + " has position: " + pus[i].getPosition());
}
}
Note that for the synchronous pollings to work, the TraXClient needs to be connected. Otherwise a RejectedAPICallException is thrown.
Let's create several Position Update Requests to track the user "dummy": one is based on a distance-based tracking, one on periodic tracking and one on zone-based tracking. Note that, in order to receive updates so-called Position Update Responses, user "dummy" needs to be connected to the Aloqa server as well.
The Distance Job will generate a Position Update Response each time the target moves by 100m.
DistanceJob dj = new DistanceJob(); dj.setDistance(100);
Then we create a new Position Update Request. It has the id "distance", upon which the job can be identified later. Also we pass the handle, the target to be tracked ("dummy") and the DistanceJob to it.
DistanceBasedPositionUpdateRequest req1 = new DistanceBasedPositionUpdateRequest("distance",
handle.getId(), "dummy", dj);
Finally, we sent the request to TraX.
api.addObservation(req1);
The PeriodicJob will generate a Position Update Response of "dummy" every 100 seconds. It has the id "time", upon which it can be identified later.
PeriodicJob pj = new PeriodicJob();
pj.setMillis(100000);
PeriodicPositionUpdateRequest req2 = new PeriodicPositionUpdateRequest("time",
handle.getId(), "dummy", pj);
api.addObservation(req2);
The ZoneJob will generate a Position Update Response each time "dummy" enters or leaves a certain zone.
ZoneJob zj = new ZoneJob(); // be notified when "dummy" enters or leaves the zone. zj.setMode(ZoneJob.MODE_ENTER_LEAVE); // we want to receive more than one position update zj.setTriggerPolicy(ZoneJob.TRIGGER_POLICY_MULTIPLE_PUs);
Other options include only reporting on entering (MODE_ENTER) or on leaving (MODE_LEAVE) the zone as well as only reporting once and then deleting the job (TRIGGER_POLICY_SINGLE_PU).
After initializing the ZoneJob we have to define a ZoneTrigger, in this example a CircularZoneTrigger.
// The center of the desired zone Coord center = new Coord(48.11, 11.2, 0); // we want an alarm when "dummy" comes closer than 100 m to this spot. int innerRadius = 100; // you want an alarm again when "dummy" departs by more than 250 m from this spot. int outerRadius = 250; // create the Zone Trigger ZoneTrigger ca = new CircularZoneTrigger(center, innerRadius, outerRadius); // Set it to the ZoneJob zj.setZoneTrigger(ca);
Finally, we create the Zone based Position Update Request with id "zone" and send the request to TraX.
ZoneBasedPositionUpdateRequest req3 = new ZoneBasedPositionUpdateRequest("zone",
handle.getId(), "dummy", zj);
api.addObservation(req3);
After sending out requests we want to receive updates reported by these requests. Therefore we need to extend the update method.
public void update(String jobId, AsynchronousResponse res) {
if (jobId.equals("distance") || jobId.equals("time") || jobId.equals("zone")) {
// as we know the job id, we can cast it to a PositionUpdateResponse
PositionUpdateResponse lr = (PositionUpdateResponse) res;
System.out.println("dummy has new position: " + lr.getPositionUpdate().getPosition());
}
}
Every update caused by a request will result in a callback of the update method. The String jobId contains the id of the associated job. According to the job id we cast the AsynchronousResponse to the adequate type. The code example above receives all updates by the created DistanceJob, PeriodicJob and ZoneJob and prints it to the standard output. Since this method handles all updates, we will expand it in the next sections for other types of requests.
Now we create a Proximity Request to detect proximity or seperation of two mobile targets: Let's say we want to be notified whenever "dummy" comes close to us. Note that, in order to receive updates so-called Proximity Responses, user "dummy" needs to be connected to the Aloqa server as well.
int proximityDistance = 1000;
int separationDistance = 2000;
int borderlineTolerance = 500;
// Create this particular observation for dummy and ourself.
ProximityObservation po1 = new ProximityObservation("dummy", "username",
proximityDistance, separationDistance, borderlineTolerance);
We want to get a notification when "dummy" is closer than 1000 m and again when he departs by more than 2000 m. In order to allow an efficient tracking we set the borderline tolerance to 500 m. For more details see the Class Reference of ProximityObservation. "username" must be replaced with the user name specified during the initialization of the TraX Client.
Of course, we can create an observation request, where we do not participate in.
ProximityObservation po2 = new ProximityObservation("dummy", "mygirlfriend",
proximityDistance, separationDistance, borderlineTolerance);
Like in the case of a Position Update Request, we create a Proximity Request by specifing the id of the request ("proximity") and add the two observation requests to it. Afterwards we send it to TraX.
ProximityRequest req4 = new ProximityRequest("proximity", handle.getId(),
new ProximityObservation[] { po1, po2 });
api.addObservation(req4);
After sending out the Proximity Request we want to receive the corresponding updates. Therefore we need to extend the update method.
public void update(String jobId, AsynchronousResponse res) {
if (jobId.equals("proximity")) {
// as we know the job id, we can cast it to a ProximityResponse
ProximityResponse pr = (ProximityResponse) res;
// One Proximity Response may carry events for several user pairs
ProximityObserved[] pos = pr.getResponses();
for (int i = 0; i < pos.length; i++) {
if (pos[i].getEvent() == ProximityObserved.PROXIMITY_EVENT)
System.out.println(pos[i].getMT1() + " is close to " + pos[i].getMT2() + ", dist="
+ pos[i].getDistance() + "m ");
else if (pos[i].getEvent() == ProximityObserved.SEPARATION_EVENT)
System.out.println(pos[i].getMT1() + " is far from " + pos[i].getMT2() + ", dist="
+ pos[i].getDistance() + "m ");
}
}
}
Now we create a KNNRequest to detect the k-nearest neighbors. Since we have more friends than "dummy", we look at the following scenario:
We want to stay up-to-date about their closeness all the time by using a KNNRequest, which always notifies us when the distance-sorted list of our k nearest friends changes.
Note that, in order to receive updates so-called HLPMResponses, all "friends" need to be connected to the Aloqa server as well.
String[] friends = new String[] { "dummy", "mygirlfriend", "Fred",
"Fritz", "Freud", "Anna", "Hanna", "Nanna" };
int k = 8;
int knnBorderlineTolerance = 500;
// we want OUR k nearest friends.
KNNRequest req5 = new KNNRequest("knn", handle.getId(), "username", friends, k, knnBorderlineTolerance);
api.addObservation(req5);
First we have to choose how big k is (note that we will be notified only about the k closest friends). In this case we want all of them. Like the HLPMObservation we have to specify the borderline tolerance. For more details on that see the Class Reference of KNNRequest. Ther "username" must be replaced with the user name specified during the initialization of the TraX Client, in order to track our friends with respect to our position.
After sending out the KNNRequest we want to receive the corresponding updates. Therefore we need - once again - to extend the update method.
public void update(String jobId, AsynchronousResponse res) {
if (jobId.equals("knn")) {
// as we know the job id, we can cast it to an KNNResponse
KNNResponse kr = (KNNResponse) res;
System.out.println("new list of closest friends: ");
for (int i = 0; i < kr.getMTs().length; i++)
System.out.println(kr.getMTs()[i] + ": " + kr.getLastMeasuredDistances()[i] + "m .");
}
}
In our application, e.g. an instant messenger, we want to sent a message to another client, which is not related to any change of a position. A naive way would be to send the message to a central server and make all clients periodically request updates from the messaging server. This leads to unnecessary data traffic, a delayed delivery to the client and load on the server.
Aloqa TraX provides a push notification, an easy and efficient way to sent messages directly to other clients. So you do not need to setup an additional server, you can simply use the Aloqa TraX framework. Even if your application is not location-based you can use TraX for pushing data to mobile devices. Also, if you need or already own a server-based solution, it is also possible to send messages from the server to mobile devices via our various APIs.
In order to make this feature work in our demo application, we must implement a sending part and functions for receiving messages. Let's start with sending a notification.
For sending a message to another client we use the synchronous NotificationRequest.
String[] friends = new String[] { "dummy", "mygirlfriend", "Fred",
"Fritz", "Freud", "Anna", "Hanna", "Nanna" };
SingleTargetNotificationRequest stnr = new SingleTargetNotificationRequest("Hi friends!".getBytes());
NotificationRequest nr = new NotificationRequest(friends, stnr, NotificationRequest.DO_ACK);
First of all we create a SingleTargetNotificationRequest containing the message to be sent as a byte array. Then we add the receivers of the message (our friends) and the created SingleTargetNotificationRequest to a new NotificationRequest. Also we have to specify, whether we want to be informed about the successful delivery and a response from the receiver (NotificationRequest.DO_ACK). Otherwise we would use NotificationRequest.NO_ACK.
Note: In principle you can send any volume of data via this channel. In order to not congest the channel we recommend to send only small amounts of data and use this channel as a trigger mechanismen. So in case you want to push large images or video files, you should store them on a central server and only sent an URL to the file via the push notification. Thus the client is immediately informed, can download the files via another connection and the push channel for this device is free for new notifications. Also the end user does not notice this technical trick and the client is receiving other notifications in the meantime.
Let TraX send our message to all of our friends in parallel and then cast the response to a NotificationResponse.
NotificationResponse nResponse = (NotificationResponse) api.synchRequest(handle.getId(), nr);
Since we requested an acknowledgement from the receivers (NotificationRequest.DO_ACK), we can read out all the responses.
SingleTargetNotificationResponse[] stnrs = nResponse.getResponses();
for (int i = 0; i < stnrs.length; i++) {
System.out.println("DeliveryStatus: " + stnrs[i].getDeliveryStatus()
+ " Response: " + new String(stnrs[i].getResponseMessage()));
}
Note that for the push notification to work, the TraXClient needs to be connected. Otherwise a RejectedAPICallException is thrown.
After sending a push notification, we want to handle incoming notifications. Therefore we have to implement an additional interface, the so-called App_SAP_App interface. The name App_SAP_App illustrates, that this interface or Service Access Point (SAP) is between the Application layers of two clients.
public class LocationMIDlet extends MIDlet implements CommandListener, TraXStatusListener,
App_SAP_TraXAPI, App_SAP_App{ ... }
The interface consists of two methods, named handlePushMessageDoAck(byte[] arg0) and handlePushMessageNoAck(byte[] arg0). One of them is called every time a push notification arrives at the client depending on the acknowledgement type of NotificationRequest (NotificationRequest.DO_ACK or NotificationRequest.NO_ACK).
public byte[] handlePushMessageDoAck(byte[] arg0) {
System.out.println("Got Message:" + new String(arg0) + ", sending Greetings back");
return "Greetings".getBytes();
}
Note: Do not implement any blocking code (I/O or GUI) within this function, otherwise no additional push notifications can be received. Therefore the response should be sent immediately. If you are implementing an instant messenger for example, you should use another push notification to transmit the response of the user.
public void handlePushMessageNoAck(byte[] arg0) {
System.out.println("Got Message:" + new String(arg0));
}
At last we register the current class for receiving push notifications after initializing the TraXClient instance.
tc.setApp_SAP_App(this);
For debugging the TraX Java API provides a logging interface for getting detailed log information of the TraX Client. In order to access this information we have to implement a new interface called LogListener.
public class LocationMIDlet extends MIDlet implements CommandListener, TraXStatusListener, LogListener { ... }
The interface consists of two methods, named newLogMessage(String msg) and newLogMessage(String msg, Throwable t), which are called when a new log message is created. So we implement these methods.
public void newLogMessage(String msg) {
System.out.println(msg);
}
public void newLogMessage(String msg, Throwable t) {
System.out.println(msg + ":" + t);
}
As last step we set the desired log level and register this class as a listener for log messages.
TraXLog.setLoglevel(TraXLog.LOG_LEVEL_INFO); TraXLog.setLogListener(this);
For more details on the log level see the Class Reference of TraXLog.
Send an email to requestkey@aloqa.com containing your contact info and a short description of your service.
Please check any firewall rules, whether the client can establish a connection to the ports 31001 and 31002.
We're really happy to answer your questions or to get feedback from you. So send us an email (support@aloqa.com) and we'll reply as soon as possible.