Driving Directions in Android 0.9 SDK

Friday August 22, 2008 at 11:35 PM

Driving directions are going to be a big deal in Android, and showing them from applications is a must-have for several applications. For example, in my CompareEverywhere app, I’m showing directions from your current location to nearby stores.

For earlier SDKs, Nicolas Gramlich put together a tutorial to get access to the com.google.googlenav.DrivingDirection class and render an overlay onto a normal MapView. In the newest 0.9 SDK, this class isn’t public anymore, which means no more access to raw driving directions for developers. (There is an alternate method of getting raw data if you’re really interested.)

Nicolas also put together an awesome entry called AndNav for the Developer Challenge, but sadly it appears that real-time navigation systems go against the Google Maps Terms of Service which says “you may not use Google Maps with any applications capable of real time route guidance” (edited slightly). I’m sure this is an upstream restriction from Google’s data sources, as they want to protect their market for other in-vehicle navigation devices. Don’t worry, the iPhone suffers from this same restriction.

So how can Android applications show driving directions? It’s actually pretty simple, although not yet documented. If you launch a normal URL to the Google Maps website, Android will intercept and ask you if you really want to use the browser, or instead be directed over to the Android Maps app:


this.startActivity(new Intent(Intent.ACTION_VIEW,
	Uri.parse("http://maps.google.com/maps?f=d&saddr=37.4,-121.9
		&daddr=Bellevue, WA&hl=en")));

Just tap “Maps” option and your route will be calculated and shown on the device, screenshots below. You could easily use a LocationListener to keep track of your current latitude/longitude, and pass that as the starting address. Thanks to Daniel Switkin at Google for helping me find this gem.

Amarok 1.4 remote in Android using DCOP+Python

Wednesday August 20, 2008 at 10:04 PM

We’ve all seen the Apple app that lets you control iTunes remotely, and there totally needs to be something like this for Android. I’m an avid fan of Amarok, so I’ve whipped together a simple remote control that runs on Android. It only took about 3 hours tonight, and I’m releasing everything GPLv3 here. First some details on the architecture:

Amarok can easily be controlled via DCOP, including fetching currently playing information and album art. DCOP is a local, safe IPC architecture that usually comes already enabled with KDE applications. Just a reminder that DCOP is being phased out in KDE 4 with D-Bus taking its place. Speaking of the D-Bus future, there is an awesome standard called Media Player Remote Interface Specification (MPRIS) that is being put together by the folks at XMMS, VLC, Amarok, and others. It doesn’t seem to be in stable releases yet, but will be soon. Back to the present, I’m going to just focus on getting Amarok 1.4 working with older DCOP calls.

First, I built a Python bridge that offers a simple HTTP REST-like API that will help us relay pre-approved DCOP commands from Android to Amarok. The Python is pretty simple:

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import pydcop, re

port = 8484
allowed = ["status", "trackCurrentTime", "trackTotalTime", "album", "artist",
	"title", "next", "playPause", "prev", "volumeDown", "volumeUp",
	"coverImage", "seek"]
resafe = re.compile("[^A-Za-z0-9/]")

class AmarokHandler(BaseHTTPRequestHandler):
	def do_GET(self):

		# pull out action and simple variable
		safe = resafe.sub('', self.path)
		blank, action, var = tuple(safe.split('/'))

		# skip if action has not been approved
		if not action in allowed: return

		# check if image request
		if action == "coverImage":
			self.send_response(200)
			self.send_header('Content-type', 'image/jpeg')
			self.end_headers()

			cover = open((pydcop.DCOPMethod("amarok", "player", "coverImage"))())
			self.wfile.write(cover.read())
			cover.close()
			return

		# make dcop call over to amarok
		if len(var) > 0: reply = (pydcop.DCOPMethod("amarok", "player", action))(int(var))
		else: reply = (pydcop.DCOPMethod("amarok", "player", action))()

		# write back any dcop response
		self.send_response(200)
		self.send_header('Content-type', 'text/plain')
		self.end_headers()
		self.wfile.write(reply)

		return

try:
	server = HTTPServer(('', port), AmarokHandler)
	print 'started amarokremote server on port %s' % (port)
	server.serve_forever()
except KeyboardInterrupt:
	server.socket.close()

Essentially we are using a URL of the form http://ipaddress:port/command/variable. For example, in the Android emulator you could call http://10.0.2.2:8282/volumeUp/ to increase the volume.

Carefully note that we are screening the commands against an “allowed” list before running off to Amarok with them. We’re also scrubbing the incoming calls to prevent any buffer overflows. Usually we are just calling the Amarok DCOP method and returning any result. One exception is for coverImage requests, because Amarok just returns a local path. We help that image over the Python bridge by sending the local JPEG as the response.

On the Android side of things, we have a simple screen layout and are hooking up the various API calls to buttons. There’s also a background thread that keeps Android in-sync with what Amarok is currently playing. Finally, we’re using some simple preferences to store the server string and update interval. (Tap the menu button to change these settings.)

Here’s a tarball of the Eclipse project, along with the APK that’s ready to run on the new 0.9 SDK. Start the Python server above on your the computer running Amarok, change the IP address if needed, and you should be ready to go.

Also, a reminder that 10.0.2.2 is magic on the Android emulator because it points to the loopback adapter of the parent computer. So, if you’re running the emulator and Amarok on the same computer, this IP address will work perfectly.

Separating Lists with Headers in Android 0.9

Monday August 18, 2008 at 4:42 PM

Earlier today the latest Android 0.9 SDK was released, and it’s packed full of wonderful changes. As you play around, you might see ListViews split into sections using separating headers. (Example shown on the right is the browser settings list.)

There isn’t an easy way of creating these separated lists, so I’ve put together SeparatedListAdapter which does it quickly. To summarize, we’re creating a new BaseAdapter that can contain several other Adapters, each with their own section headers.

First let’s create some simple XML layouts to be used for our lists: first the header view, then two item views that we’ll use later for the individual lists. (Thanks to Romain Guy for helping me find existing styles to keep these XML layouts nice and tidy.)

<!-- list_header.xml -->
<TextView
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/list_header_title"
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:paddingTop="2dip"
	android:paddingBottom="2dip"
	android:paddingLeft="5dip"
	style="?android:attr/listSeparatorTextViewStyle" />

<!-- list_item.xml -->
<TextView
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/list_item_title"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	android:paddingTop="10dip"
	android:paddingBottom="10dip"
	android:paddingLeft="15dip"
	android:textAppearance="?android:attr/textAppearanceLarge"
	/>

<!-- list_complex.xml -->
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:orientation="vertical"
	android:paddingTop="10dip"
	android:paddingBottom="10dip"
	android:paddingLeft="15dip"
	>
	<TextView
		android:id="@+id/list_complex_title"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:textAppearance="?android:attr/textAppearanceLarge"
		/>
	<TextView
		android:id="@+id/list_complex_caption"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:textAppearance="?android:attr/textAppearanceSmall"
		/>
</LinearLayout>

Now let’s create the actual SeparatedListAdapter class which provides a single interface to multiple sections of other Adapters. After using addSection() to construct the child sections, you can easily use ListView.setAdapter() to present the now-separated list to users.

As for the Adapter internals, to correctly find the selected item among the child Adapters, we walk through subtracting from the original position until we find either a header (position = 0) or item in the current child Adapter (position < size).

Here’s the source for SeparatedListAdapter:

public class SeparatedListAdapter extends BaseAdapter {

	public final Map<String,Adapter> sections = new LinkedHashMap<String,Adapter>();
	public final ArrayAdapter<String> headers;
	public final static int TYPE_SECTION_HEADER = 0;

	public SeparatedListAdapter(Context context) {
		headers = new ArrayAdapter<String>(context, R.layout.list_header);
	}

	public void addSection(String section, Adapter adapter) {
		this.headers.add(section);
		this.sections.put(section, adapter);
	}

	public Object getItem(int position) {
		for(Object section : this.sections.keySet()) {
			Adapter adapter = sections.get(section);
			int size = adapter.getCount() + 1;

			// check if position inside this section
			if(position == 0) return section;
			if(position < size) return adapter.getItem(position - 1);

			// otherwise jump into next section
			position -= size;
		}
		return null;
	}

	public int getCount() {
		// total together all sections, plus one for each section header
		int total = 0;
		for(Adapter adapter : this.sections.values())
			total += adapter.getCount() + 1;
		return total;
	}

	public int getViewTypeCount() {
		// assume that headers count as one, then total all sections
		int total = 1;
		for(Adapter adapter : this.sections.values())
			total += adapter.getViewTypeCount();
		return total;
	}

	public int getItemViewType(int position) {
		int type = 1;
		for(Object section : this.sections.keySet()) {
			Adapter adapter = sections.get(section);
			int size = adapter.getCount() + 1;

			// check if position inside this section
			if(position == 0) return TYPE_SECTION_HEADER;
			if(position < size) return type + adapter.getItemViewType(position - 1);

			// otherwise jump into next section
			position -= size;
			type += adapter.getViewTypeCount();
		}
		return -1;
	}

	public boolean areAllItemsSelectable() {
		return false;
	}

	public boolean isEnabled(int position) {
		return (getItemViewType(position) != TYPE_SECTION_HEADER);
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		int sectionnum = 0;
		for(Object section : this.sections.keySet()) {
			Adapter adapter = sections.get(section);
			int size = adapter.getCount() + 1;

			// check if position inside this section
			if(position == 0) return headers.getView(sectionnum, convertView, parent);
			if(position < size) return adapter.getView(position - 1, convertView, parent);

			// otherwise jump into next section
			position -= size;
			sectionnum++;
		}
		return null;
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

}

As expected, it correctly prevents the section headers from being selected, and seamlessly stiches together the various Adapters.

This approach also uses convertView correctly as long as the child Adapters return getItemViewType() and getViewTypeCount() normally. No special changes are needed for an Adapter to become a child.

Now let’s use SeparatedListAdapter in some example code. We use the XML layouts defined earlier to create an ArrayAdapter and an advanced two-row SimpleAdapter, and then add both as sections to our SeparatedListAdapter.

public class ListSample extends Activity {

	public final static String ITEM_TITLE = "title";
	public final static String ITEM_CAPTION = "caption";

	public Map<String,?> createItem(String title, String caption) {
		Map<String,String> item = new HashMap<String,String>();
		item.put(ITEM_TITLE, title);
		item.put(ITEM_CAPTION, caption);
		return item;
	}

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

		List<Map<String,?>> security = new LinkedList<Map<String,?>>();
		security.add(createItem("Remember passwords", "Save usernames and passwords for Web sites"));
		security.add(createItem("Clear passwords", "Save usernames and passwords for Web sites"));
		security.add(createItem("Show security warnings", "Show warning if there is a problem with a site's security"));

		// create our list and custom adapter
		SeparatedListAdapter adapter = new SeparatedListAdapter(this);
		adapter.addSection("Array test", new ArrayAdapter<String>(this,
			R.layout.list_item, new String[] { "First item", "Item two" }));
		adapter.addSection("Security", new SimpleAdapter(this, security, R.layout.list_complex,
			new String[] { ITEM_TITLE, ITEM_CAPTION }, new int[] { R.id.list_complex_title, R.id.list_complex_caption }));

		ListView list = new ListView(this);
		list.setAdapter(adapter);
		this.setContentView(list);

	}

}

The resulting interface behaves just like the browser preferences list, and you could easily create other custom Adapters to insert into the various sections, such as including icons or checkboxes.

These section headers can really help separate out otherwise-cluttered activities. I used them several places in my CompareEverywhere application which lets you easily compare prices and read reviews for any product with a barcode.

Scan is now CompareEverywhere

Sunday August 10, 2008 at 7:28 AM

Over the last two months I’ve been rewriting Android Scan for the second phase of the Android Developer Challenge.  Just a quick heads up that Scan is now called CompareEverywhere for the second round.  CompareEverywhere is an Android app that helps you shop smarter using your phone.

Last Tuesday morning (August 5), I submitted my app for second round judging along with the 49 other teams.  It’s been a blast, and I can’t wait to share the app with everyone.

Scott at AndroidGuys put it best when he said “It’s a little ironic that the closer we get to milestones and the official launch, the quieter things are getting.”  I have the feeling that most teams were hesitant to speak out during the second round because of the NDA we signed with Google, and that only added to the silence.

That said, there’s a huge amount of excitement among the 50 teams.  Most of it is being directed towards final application polish and preparing our apps for public launch, but I’m sure you’ll see screenshots from various apps popping up.

Finally, to keep myself from going crazy while waiting for the juding process, I’m writing up some sweet tutorials which I’ll post when the new public SDK comes out. Stay tuned!

Watch my blog using Google Reader: Add to Google

Valid XHTML and CSS