Tuesday, April 8, 2008

Do you want to sync your phone contacts to a remote data source? No problem, use the bundled HttpClient and write a service. Do you have a buddy that likes to drunk dial you at 3:00 a.m.? Why not have a service that can filter his calls out during certain hours. Things like this would be hard or downright impossible in the past. Now we have Android!

I thought I'd do a little tutorial showcasing the Location API that ships with android. This is definitely a fun API to play with, so allow me to introduce the TrivialGPS application.

Android ships with a mock LocationProvider, which simulates a route in the bay area. You can use it to develop and test GPS enabled applications in a simulated device.

The aptly named TrivialGPS application will display a MapView, and center it on our current location as we move through the bay in "real-time". We use the observer pattern with the LocationManager, so our application can receive updates about changes in our current position and update the MapView accordingly.

At this point, I'm going to assume that you have either looked at the Android tutorials and have at least a rudimentary understanding of the framework, or that you're so damn intelligent that you don't need to.

As of the m5-rc14 version of the SDK, your manifest file must declare a couple of permissions for this application to work. The following is what I've placed in my manifest file.


...






The TrivialGPS is a single activity that displays a map, so to do this our activity must extend MapActivity.

Android allows a form of IPC via Intents. When you create an Intent, you need an "action", which is basically a string that will uniquely identify the Intent. There are many of these built into the Intent class itself (DIAL_ACTION, EDIT_ACTION, etc). For TrivialGPS we will just invent our own action called LOC_UPDATE. We'll use this Intent to receive periodic updates from the LocationManager.

We also need three instance fields: The MapView which we will be displaying, a MapController which can center the map, and a LocationManager which we can query for providers and request geo information from.


public class TrivialGPS extends MapActivity {

public static final String UPDATE_LOC = "com.test.TrivialGPS.LOC_UPDATE";

private MapController mapController;
private MapView mapView;
private LocationManager locationManager;
...


Our onCreate method starts out very simple. We create a new MapView, set the zoom level to 22 (pretty close up, so we can see the streets), store a reference to the MapController, and then tell Android to display the map. We'll be revisiting this method a little bit later.


@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);

mapView = new MapView(this);
mapController = mapView.getController();
mapController.zoomTo(22);
setContentView(mapView);
}


In order to receive notifications about location updates, we need an IntentReceiver. An IntentReceiver is basically a callback handler. The simplest way to do this is with an inner class. The class overrides the onReceiveIntent method. The guts of this pull the Location object from the intent bundle, create a latitude/longitude point, and then uses the MapController to center the view on the new point.


public class handleLocationUpdate extends IntentReceiver {
public void onReceiveIntent(Context context, Intent intent) {
Location loc = (Location) intent.getExtra("location");

Double lat = loc.getLatitude()*1E6;
Double lng = loc.getLongitude()*1E6;
Point point = new Point(lat.intValue(), lng.intValue());
mapController.centerMapTo(point, false);

setContentView(mapView);
}
}


As promised, we now return to onCreate method.

The first thing we've added is a call to initialize the LocationManager.

Next, we retrieve the mock location provider, "gps" using the getProvider method from the LocationManager.

The observer pattern here is decoupled, our handleLocationUpdate doesn't register itself directly with the LocationManager. Rather, we register with android itself and tell it the particular Intent that we're interested in. We do this by calling registerReceiver and handing it the IntentReceiver and an IntentFilter that specifies what actions we are interested in hearing about.

The other end of this is telling the LocationManager to broadcast an Intent continually that will be handled by handleLocationUpdate. We create an Intent identified by our LOC_UPDATE action, and then tell the LocationManager to continuously broadcast updates via a call to requestUpdates.

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);

// create a map view
mapView = new MapView(this);
mapController = mapView.getController();
mapController.zoomTo(22);
setContentView(mapView);

// get a hangle on the location manager
locationManager =
(LocationManager) getSystemService(Context.LOCATION_SERVICE);

// get the "gps" mock provider
LocationProvider provider = locationManager.getProvider("gps");

// register our IntentReceiver as an observer.
registerReceiver(new handleLocationUpdate(), new IntentFilter(UPDATE_LOC));

// instruct the location manager to broad cast the UPDATE_LOC intent
// continually as we move.
Intent intent = new Intent(UPDATE_LOC);
locationManager.requestUpdates(provider, 0, 0, intent);
}


I should mention that when you run TrivialGPS, it takes a couple of minutes to see the thing start moving. Perhaps when they designed the mock provider they also wanted to simulate a car with engine problems. =)

I hope some of you can find this useful. It took me several hours to figure this stuff out and get it working in my own application, so perhaps this tutorial will be a nice time saver for some of you out there, that way you can get back to your eggnog and Xmas toys.

No comments: