Archive for May, 2010

Capstone Presentation

My capstone presentation can be downloaded here. It also contains step-by-step usage instructions with screenshots.

Guided Tour

Download

My final project! It’s long, and I covered a lot of the interesting parts in the MapView and GPS examples.

I want to start by outlining my biggest regret in regards to my application architecture so I can spend the rest of this post explaining how awesome my program is. This is my first time programming for a mobile device and my first real experience with Java. Unlike with C++, where I can comfortably create elaborate class hierarchies and argue the merits of Hungarian notation, I began this program with zero knowledge of Java and Android “best practices.”

So, when I was researching dialog creation for displaying landmark information, I researched best practices for dialogs. One COULD just instantiate a dialog class, and manually bind it to your application’s context, but my text implied it would be better to use the View class’s built-in callback function:

protected Dialog onCreateDialog(int id)

The function is called by the Android operating system when a particular dialog is requested, and returns the code needed to build the dialog:

protected Dialog onCreateDialog(int id) {
	    switch(id) {
	    case COFRIN_DIALOG:
	    	return mMarkers.getDialog("Cofrin Hall");
...
}

Pretty simple, right? Actually instantiating a dialog using this callback looks like this:

showDialog(_index);

where “_index” was the position in the list of landmarks the user touched. Couldn’t be simpler, right?

Wrong! I thought I was doing dialogs the “right” way, and at first blush this code seems fairly elegant. The only problem is that this callback method is wholly owned by our application’s main View class. And this class doesn’t have (direct) access to the same data that the MapView class does (as the MapView class owns the landmarks and points of interest.)

Had I started the project knowing what I know now, I would have disregarded the recommendations on Google’s Android site and in my text and just create a separate Dialog class. Another class could have had sole ownership of the data, and all of my Views (and the dialog class) could communicate through the Data class.

I covered most of the interesting bits in previous posts, namely the MapView code and the GPS code. However, I made my GPS code more robust by asking for the “best” location service that was available (and turned on!) rather than just assuming a GPS like in the demo code:

Criteria criteria = new Criteria();
 
    	criteria.setAccuracy(Criteria.ACCURACY_FINE);	//Pretty much means GPS.  "Fine" accuracy means stalker-approved proximity.
    	criteria.setAltitudeRequired(false);			//We don't need an altimeter
    	criteria.setBearingRequired(false);				//We don't really need to know which direction you're facing
    	criteria.setCostAllowed(true);					//Some cell companies charge for GPS use?!  Bastards.  There's a ring for them a few floors up from popes and usurers.
    	criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);	//All the examples had POWER_LOW, but I care more about it finding the GPS hardware than battery life
 
    	//Summon forth a location provider that matches our requirements!  (And is on!)
    	mProvider = mLocationManager.getBestProvider(criteria, true);

The “guided tour” part was actually fairly simple to implement. I have two parallel arrays of destinations and descriptions, and an index of how far you’ve progressed in the tour:

private static int mTourState = 0;
	private static String[] mTourStops = { "Cofrin Hall", "Todd Wehr Hall", "Boyle Hall", "Bemis International Center", "Mulva Library", "Campus Center" };
	private static String[] mTourCaptions = {
		"First stop is Cofrin Hall!",			//Cofrin
		"Now go to Todd Wehr Hall!",			//Todd
		"Now some meaningful and descriptive way of telling you to go to Boyle!",	//Boyle
		"Now go to the Bemis International Center!", //Bemis
		"Now go to the Mulva Library!", //Mulva
		"Now go to the Campus Center!"	//Campus Center
		};

I have a ProximityListener that fires when a user reaches a particular destination. They the advance to the next point on tour automagically. These worked reliably during testing wandering about campus, but the version 1.5 of the Android SDK seemed to be missing some features in the newer 2.0 and 2.1 incarnations.

	public ProximityHandler(TabbedMaps parent) {
		mParent = parent;
	}
 
	@Override
	public void onReceive(Context context, Intent intent) {
 
		//Will be true if they are ENTERING the target radius around the next building on the tour
		//But seems to only be present on Android version 2.0 and above; phone uses 1.5
		//Boolean entering = intent.getBooleanExtra("com.vromtr.tourproximity", false);
		Boolean entering = true;
 
		if (entering)
		{
			//Advance to the next part of the tour
			mParent.tourGoForward();
		}
 
	}
 
}

The rest of my code is about 50% comments by volume. It was definitely a learning experience for me – I got to learn a new SDK in a new language in a new IDE targeting a new platform. So, most of the comments were written for myself as I was learning. But, they should be helpful for whoever might inherit a similar project in the future.

Google Maps

Download

Google provides a MapView control, which is essentially a touch-sensitive Google Maps UI. Actually getting it working, however, was the source of much angst and frustration. A Google search will yield a number of equally frustrated novice programmers.

There are a number of prerequisites:

  1. You must generate a key to cryptographically sign your application. This proves that the application came from a specific developer. Unsigned applications will not run on actual hardware, although anyone can sign an application on their own.
  2. You have to get a Google Maps API key. This is generated from and unique to the key you sign your application with. Without this API key, the MapView will simply laugh at you and display gray tiles.
  3. You must ask for “Internet” and “Fine Location” permissions in your manifest. Without them, you will not have access to Google Maps (the maps and tiles are downloaded from the internet on-demand) or the user’s current location.

The Hello, MapView tutorial from Google’s site completely neglects to mention the need for #1 and #2, without which the application will not work. An API key for Android, along with instructions to sign your program, can be found here.

After your application is signed and talking to Google Maps, the rest of the code is fairly simple. The XML layout “main” looks like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
 
    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/myLocationText" android:text="Finding location..."></TextView>
    <com.google.android.maps.MapView
    	android:id="@+id/map_view"
    	android:layout_width="fill_parent"
    	android:layout_height="fill_parent"
    	android:enabled="true"
    	android:clickable="true"
    	android:apiKey="whargharbl - get your own"
    />
 
</LinearLayout>

This gives us a small TextView containing latitude and longitude from the previous example with the MapView control filling the rest of the screen.

Programming it is fairly simple. We derive our application’s main activity from MapActivity instead of Activity (MapActivity is itself derived from Activity). This greatly simplifies development, although I was not able to take this shortcut with my tour application proper – I needed it to cooperate with other activities.

public class MyMapActivity extends MapActivity {

Our MapActivity has two main components – a MapView representing the Google Maps interface the user sees and interacts with, and a MapController helper class for programmatically controlling the MapView.

private MapView mapView;
private MapController mapController;

We override the onCreate method like before and use it to configure our MapView and MapController:

 @Override
    public void onCreate(Bundle savedInstanceState) {
        //Call base method
    	super.onCreate(savedInstanceState);
 
    	//"Inflate" our layout
    	setContentView(R.layout.main);
 
    	//Get a handle to our MapView control; store it.
    	mapView = (MapView) findViewById(R.id.map_view);
 
    	//Get the MapView's associated controller.
    	//Believe it or not, used for controlling the MapView
    	mapController = mapView.getController();
 
    	//Set the zoom/display/visible controls of our MapView
    	mapView.setSatellite(true);		//False gets regular map tiles
    	mapView.setStreetView(true);
    	mapView.setBuiltInZoomControls(true);
    	mapView.displayZoomControls(true);
    	mapController.setZoom(17);

This is pretty self-explanatory – we get to configure what UI elements are available on the MapView, such as zoom in/zoom out controls. We also get to pick whether we want satellite imagery or regular, flat map tiles. We then use the MapController to set a sane starting zoom level.

Next, we place an Overlay on top of the MapView. Overlays are transparent UI elements that cover Views and other controls; in the context of the MapView control, they’re used to place pushpins and annotations over a map and to intercept touches. Overlays have z-order – you can place one on top of the other, and touch events filter top-down until something elects to handle it.

    	//Overlays are graphical layers drawn over the map control
    	//Add the built-in "You Are Here" overlay that puts a dot at your current location
    	List<Overlay> overlays = mapView.getOverlays();
    	MyLocationOverlay myLocationOverlay = new MyLocationOverlay(this, mapView);
    	overlays.add(myLocationOverlay);
 
    	//Turn on the compass and the You Are Here dot
    	myLocationOverlay.enableCompass();
    	myLocationOverlay.enableMyLocation();

I omitted the code for hooking up the GPS – it’s identical to the previous GPS example and included in the Workspace download at the top of this post.

GPS

Download

My Professional Android 2 Application Development text had two very nifty examples which are worth expounding upon: Talking to the Google Maps control, called a MapView, and talking to the GPS hardware. Talking to the GPS is simpler, so I’ll start there, with the source code in its scary entirety:

 
package com.vromtr.paad.ch8.whereami;
 
import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.widget.TextView;
 
public class WhereAmI extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        //Call base class onCreate
    	super.onCreate(savedInstanceState);
 
    	//"Inflate" our UI (such as it is) from our program's resource
    	setContentView(R.layout.main);
 
    	//Magic class for interacting with all the different ways
    	//a phone can stalk you.
    	LocationManager locationManager;
 
    	//We have to ask for the systemwide instance of the LocationManager class nicely.
    	//This is the name we use to ask for it.
    	String context = Context.LOCATION_SERVICE;
 
    	//We get the locationmanager by means of a Context
    	//I have no clue what a "context" is.  But it gets you stuff.
    	locationManager = (LocationManager)getSystemService(context);
 
    	//Name we use to ask to talk to the GPS
    	String provider = LocationManager.GPS_PROVIDER;
 
    	//Get last reported location from the GPS
    	//No guarantee that it was updated recently.
    	Location location = locationManager.getLastKnownLocation(provider);
 
    	//Our method
    	//Update the UI
    	updateWithNewLocation(location);
 
    	//Set up the listener to get location updates from the GPS
    	//The listener is triggered by polling the GPS, and the GPS proactively notifying our application of movements
    	int t = 5000;		//Poll the GPS every 5000 milliseconds
    	int distance = 1;	//Or update earlier if we've moved 1 meters
 
    	LocationListener myLocationListener = new LocationListener()
    	{
 
			public void onLocationChanged(Location location) {
				updateWithNewLocation(location);
 
			}
 
			public void onProviderDisabled(String provider) {
				// TODO Auto-generated method stub
 
			}
 
			public void onProviderEnabled(String provider) {
				// TODO Auto-generated method stub
 
			}
 
			public void onStatusChanged(String provider, int status,
					Bundle extras) {
				// TODO Auto-generated method stub
 
			}
 
    	};
 
    	//Hook up our event handler^W^W listener to receive updates
    	locationManager.requestLocationUpdates(provider, t, distance, myLocationListener);
 
 
    }
 
	private void updateWithNewLocation(Location location) {
 
		//We will fill this in with the latitude/longitude from "location"
		String latLongString;
 
		//Reference to our TextView (the only real component of our UI)
		TextView myLocationText;
 
		//Get a handle to our textview
		myLocationText = (TextView)findViewById(R.id.myLocationText);
 
		//If we have a location, build the string
		if (location != null)
		{
			double lat = location.getLatitude();
			double lng = location.getLongitude();
			latLongString = "Lat: " + lat + "\nLong: " + lng;
		}
		else
		{
			latLongString = "No location found.";
		}
 
		myLocationText.setText("Your current position is totally around: \n"+latLongString);
 
 
	}
}

Like practically every Java program, it starts with a package declaration and some includes. The includes aren’t really worth mentioning – if you try to use a class without including the correct package, Eclipse will underline the erroneous class, tell you what package it’s in, and offer to include it for you.

The application begins like normal: We derive a subclass from the Activity class, call the base class’s onCreate function for housekeeping, and “inflate” (display) our primary View (window).

public class WhereAmI extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        //Call base class onCreate
    	super.onCreate(savedInstanceState);
 
    	//"Inflate" our UI (such as it is) from our program's resource
    	setContentView(R.layout.main);

Following that, we create a LocationManager.

    	LocationManager locationManager;
      	String context = Context.LOCATION_SERVICE;
    	locationManager = (LocationManager)getSystemService(context);
    	String provider = LocationManager.GPS_PROVIDER;
    	Location location = locationManager.getLastKnownLocation(provider);

A LocationManager is a systemwide class that provides a layer of abstraction over all the possible different ways a phone can know where you are – GPS, cell tower triangulation, WiFi hotspot guessing, altimeter, divining rod, etc. A “context” is a string token you pass to the operating system to get a copy of this unique, system-wide class – we don’t get to instantiate a copy ourselves; it’s a singleton that belongs to the operating system, and we have to ask nicely to play with it via getSystemService.

Here, we explicitly specify the GPS with the provider string. My text suggests this is not best practices, but it serves for an example.

Finally, we get the provider’s last known location. However, the “last known location” is not guaranteed to have been set recently, or at all. So, we have to poll the GPS for current data.

This happens asynchronously, so we set up a special event handler called a LocationListener. It gets called when we get meaningful data back from the GPS.

LocationListener myLocationListener = new LocationListener()
    	{
 
			public void onLocationChanged(Location location) {
				updateWithNewLocation(location);
 
			}
        }

The updateWithNewLocation() function is pretty simple – it plasters the Location the GPS returns onto our UI. The following code finds our applications TextView control, builds a string from the new latitude and longitude, and places it in the TextView.

	private void updateWithNewLocation(Location location) {
 
		//We will fill this in with the latitude/longitude from "location"
		String latLongString;
 
		//Reference to our TextView (the only real component of our UI)
		TextView myLocationText;
 
		//Get a handle to our textview
		myLocationText = (TextView)findViewById(R.id.myLocationText);
 
		//If we have a location, build the string
		if (location != null)
		{
			double lat = location.getLatitude();
			double lng = location.getLongitude();
			latLongString = "Lat: " + lat + "\nLong: " + lng;
		}
		else
		{
			latLongString = "No location found.";
		}
 
		myLocationText.setText("Your current position is totally around: \n"+latLongString);
 
 
	}
}

Finally, we register our LocationListener event handler to receive updates from the GPS via our LocationManager:

    	int t = 5000;		//Poll the GPS every 5000 milliseconds
    	int distance = 1;	//Or update earlier if we've moved 1 meters
 
    	//Hook up our event handler^W^W listener to receive updates
    	locationManager.requestLocationUpdates(provider, t, distance, myLocationListener);

The layout isn’t interesting; you can look at it in the Workspace ZIP. Running it will yield a black TextView with GPS coordinates of your current position. (Or, if you’re running it within an emulator, whatever position you fed into the device.)

Hello, World!

Download

I started with the quintessential hello-world example. The text I used, Professional Android 2 Application Development by Reto Meier, pretty much uses the Google hello world and the SDK’s empty project default.http://senkaimon.ath.cx/wp-admin/post-new.php?preview=true

The entire source code is below:

package com.vromtr.paad.ch2.helloworld;
 
import android.app.Activity;
import android.os.Bundle;
 
public class HelloWorld extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

First, the package statement:

package com.vromtr.paad.ch2.helloworld;

Programs, and parts of programs, compile into packages. They’re like namespaces in C++ with the exception that a compiled package can potentially be linked to in strange ways by other programs, which is why pains must be taken to keep them unique.

Next, the import statements:

import android.app.Activity;
import android.os.Bundle;

Import statements are like C’s “#include” directives. They tell the compiler that you plan on making use of a specific package in your program. “Activity” in Android parlance can be analogous to either a “window” or an “application” in Windows land; it is a self-contained user interface element with associated code.

“Bundles” contain information for saving application state – your program can be killed at any time, so a program with stateful data needs to store and retrieve it using this structure.

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

This is Java-ese for void main(). We are creating a new HelloWorld class derived from the base Activity (window) class that comes with the SDK. Because the base class does nothing interesting, we want to override its onCreate() function to do something useful:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

@Override specifies that we’re replacing a function in the base Activity class – this is exactly like a virtual function in C++. The onCreate function is passed a Bundle; if our program had any stateful data, we would keep it within this Bundle so it looks like our application kept running after the user closed it, turned off the phone, or switched focus to another application.

We start the function by calling the base class’s implementation of onCreate. I’m sure this takes care of a lot of housekeeping for us.

The final line of the function is the most interesting:

        setContentView(R.layout.main);

setContentView sets the active View – analogous to a control or a sub-window. This line actually instantiates and displays our user interface. “R” is a class generated by the ADT toolkit; it’s how we reference Resources (get it?) like user interfaces (“layouts”), strings, graphics, and the like.

The source code for the layout looks like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
	<TextView android:text="I am placing text in this box!  And even more text!"
	 android:id="@+id/TextView01"
	 android:layout_width="wrap_content"
	 android:layout_height="wrap_content">
	 </TextView>
</LinearLayout>

The LinearLayout specifies how sub-views “flow” within your user interface. You don’t get to specify per-pixel locations and sizes out of deference to the plethora of screen sizes and orientations available on Android phones. Instead, you get to nest Layouts within Layouts within Layouts to try to get things to line up as you think they would, and hope that particular (valid) combination of Layouts doesn’t crash the IDE.

The TextView is exactly as it sounds – a “label” or a “textbox” control.

The XML layout is compiled into our applications resource file, which like Windows resource scripts are baked into our compiled application binary. As shown above, we access the main layout via R.layouts.main.

Lengthy exposition for a Hello World! program. Google’s version covers some more points on IDE configuration, and has pictures.

Configuring your IDE!

Presumably, one may want to put together an IDE suitable for running and debugging Android applications. Google actually does a fair job of documenting this, but there’s no reason why I can’t add to it.

  1. You need Eclipse. This is a free IDE used to write the Java code for our program.
  2. Next, you need the Android SDK. To install it, all you have to do is unzip it to a safe place on your C drive.
  3. Now, you need the ADT plugin for Eclipse. This is installed from within Eclipse by selecting Help, Install New Software and following the instructions on Google’s page.
  4. Finally, you’ll want to follow the rest of the instructions on Google’s Quick Start page. The end result is a working install of the Eclipse IDE for writing Java code, a copy of the Android SDK for all the necessary libraries, tools, and the Android virtual machine; and the ADT plugin to tie the Android SDK into Eclipse.

After you have a working development environment, you can open my projects by

  1. Downloading and extracting the ZIP file
  2. Specifying that folder as your “Workspace” when starting Eclipse. Workspaces are analogous to Visual Studio solutions.
Return top

About this project

I am creating a "guided tour" application for Android smart phones. With the magic of GPS, your phone will be able to introduce you to the sights and sounds on campus.