Jeff Sharkey



Crossing things off lists in Android 0.9 SDK

A few months ago I wrote some code to let you “cross-off” things in a ListView. By wiping your finger left-to-right over an item it will add a strike-through effect to the text, and right-to-left would reverse the effect. Also, we’d like to store the crossed-off status in a backend database.

First, an overview of the problem. The ListView already captures clicks and long-touches for its items, and catches vertical scrolling and flinging to browse through the list. We’re interested in capturing any horizontal events while still letting normal touch events pass through to the ListView. The best way to handle this is by creating a wrapper View that will hold the ListView. Then we can use onInterceptTouchEvent() to watch for our cross-off actions, or otherwise ignore the touch events and let them trickle down to the ListView. (Thanks to Romain Guy for helping me find the correct way to capture these events.) Here’s the core of that capture code:

protected MotionEvent downStart = null;

public boolean onInterceptTouchEvent(MotionEvent event) {
	
	switch(event.getAction()) {
	case MotionEvent.ACTION_DOWN:
		// keep track of the starting down-event
		downStart = MotionEvent.obtain(event);
		break;
	case MotionEvent.ACTION_MOVE:
		// if moved horizontally more than slop*2, capture the event for ourselves
		float deltaX = event.getX() - downStart.getX();
		if(Math.abs(deltaX) > ViewConfiguration.getTouchSlop() * 2)
			return true;
		break;
	}
	
	// otherwise let the event slip through to children
	return false;
}

public boolean onTouchEvent(MotionEvent event) {
	
	// check if we crossed an item
	float targetWidth = this.getWidth() / 4;
	float deltaX = event.getX() - downStart.getX(),
		deltaY = event.getY() - downStart.getY();
	
	boolean movedAcross = (Math.abs(deltaX) > targetWidth);
	boolean steadyHand = (Math.abs(deltaX / deltaY) > 2);
	
	if(movedAcross && steadyHand) {
		boolean crossed = (deltaX > 0);
		
		// figure out which child view we crossed
		ListView list = (ListView)this.findViewById(android.R.id.list);
		int position = list.pointToPosition((int)downStart.getX(), (int)downStart.getY());
		
		// pass crossed event to any listeners
		for(OnCrossListener listener : listeners) {
			listener.onCross(position, crossed);
		}
		
		// and return true to consume this event
		return true;
	}

	return false;
}

Using this approach lets us capture these cross-off gestures while still letting the ListView behave normally. I’m using this technique in my CompareEverywhere application, but earlier today I wrote a quick TodoList app to show it in action.

There are two other cool things we’re doing in the process: using a ViewBinder to custom render the created time of each item, and a stateful drawable to handle the checkmarks shown on each item. The ViewBinder correctly sets the strike-through text effect based on the COL_CROSSED database column, and also shows a custom caption with a format similar to “4 hours ago” based on the COL_CREATED column.

public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
	
	switch(view.getId()) {
	case android.R.id.content:
		// binding to parent container should set the crossed value
		ImageView icon = (ImageView)view.findViewById(android.R.id.icon);
		TextView text1 = (TextView)view.findViewById(android.R.id.text1),
			text2 = (TextView)view.findViewById(android.R.id.text2);
		
		// read crossed status and set text flags for strikethrough
		boolean crossed = Boolean.valueOf(cursor.getString(columnIndex));
		if(crossed) {
			icon.setImageState(new int[] { android.R.attr.state_checked }, true);
			text1.setPaintFlags(text1.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
			text2.setPaintFlags(text2.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
		} else {
			icon.setImageState(new int[] { }, true);
			text1.setPaintFlags(text1.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
			text2.setPaintFlags(text2.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
		}
		return true;
		
	case android.R.id.text2:
		// binding to second textview should format time nicely
		long created = cursor.getLong(columnIndex);
		long now = System.currentTimeMillis() / 1000;
		
		int minutes = (int)((now - created) / 60);
		String nice = view.getContext().getString(R.string.bind_minutes, minutes);
		if(minutes >= 60) {
			int hours = (minutes / 60);
			nice = view.getContext().getString(R.string.bind_hours, hours);
			if(hours >= 24) {
				int days = (hours / 24);
				nice = view.getContext().getString(R.string.bind_days, days);
			}
		}
		
		((TextView)view).setText(nice);
		
		return true;
	}
	
	// otherwise fall through to other binding methods
	return false;
	
}

And finally the code needed to connect the above ViewBinder to our SimpleCursorAdapter:

this.cross = (CrossView) this.findViewById(R.id.crossview);
this.list = (ListView) this.findViewById(android.R.id.list);

this.cross.addOnCrossListener(this);

// build adapter to show todo cursor
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.item_todo, cursor,
	new String[] { db.FIELD_LIST_TITLE, db.FIELD_LIST_CREATED, db.FIELD_LIST_CROSSED },
	new int[] { android.R.id.text1, android.R.id.text2, android.R.id.content });
adapter.setViewBinder(new CrossBinder());

list.setAdapter(adapter); 

And it really is that simple. The SimpleCursorAdapter shows our todo list, and the ViewBinder handles showing COL_CREATED correctly, and assigning the overall crossed-off state based on COL_CROSSED.

There is some additional code in our Activity to handle onCross() events and update the database and ListView as needed. Finally I threw in a context menu to handle deleting and crossing-off items on phones without a touchscreen, and a normal menu for adding new items.

The last cool thing is the stateful drawable that I’m using to automatically change the icon based on crossed-off status and also on selection:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
	<item android:state_selected="true" android:state_checked="true"
		android:drawable="@drawable/indicator_check_mark_dark_invert" />
	<item android:state_checked="true"
		android:drawable="@drawable/indicator_check_mark_dark" />

	<item android:state_selected="true" android:drawable="@drawable/ic_text_dot_c_invert" />
	<item android:drawable="@drawable/ic_text_dot_c" />
</selector>

The ImageView automatically works its way down that list until it finds a drawable that matches all the state requirements, which makes it super easy to handle darkening icons when an item is selected. We also used ImageView.setImageState() earlier in our ViewBinder to correctly set the checked state.

If you’re interested, here’s the full Eclipse project source under a GPLv3 license, and also an APK ready to be installed. And here’s some video of the app in action:



Quick database row editor in Android 0.9 SDK

For several Android projects I’ve needed a quick way of editing database rows without building an entire GUI. In the 0.9 SDK, we saw the introduction of the Preferences framework for storing simple application data, along with the PreferenceActivity family of classes for rapidly creating editable GUIs with almost zero effort. Wouldn’t it be awesome if we could edit database rows just as easily?

It is possible if you’re willing to do a little hacking. Essentially we’re providing a fake SharedPreferences up to a PreferenceActivity window. Instead of reading and saving from the application preferences, our SharedPreferences class will be using a specific database row for its storage. To make it all work, we then override the PreferenceActivity.getSharedPreferences() method to return our fake SharedPreferences instance.

Below is some example code of this approach being used in ConnectBot, an Android SSH client that I’ve been working on. First the host_prefs.xml file where we define the “preferences” that will be written to our database. Notice that I’m setting android:key to the database column names, which will make pairing the data up much easier later.

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
	<EditTextPreference
		android:key="nickname"
		android:title="Nickname"
		/>
	<ListPreference
		android:key="color"
		android:title="Color category"
		android:entries="@array/list_colors"
		android:entryValues="@array/list_colors"
		/>
	<CheckBoxPreference
		android:key="usekeys"
		android:title="Use SSH keys"
		/>
	<PreferenceCategory android:title="Connection settings">
		<EditTextPreference
			android:key="username"
			android:title="Username"
			/>
		<EditTextPreference
			android:key="hostname"
			android:title="Host"
			/>
		<EditTextPreference
			android:key="port"
			android:title="Port"
			/>
	</PreferenceCategory>
</PreferenceScreen>

Now let’s look at the code we’ll be using for our PreferenceActivity. Nothing too special except we are overriding the getSharedPreferences(), which is how the PreferenceActivity will get its data source. In this example we’re doing a quick hack by passing the exact _id into the onCreate() as Intent.EXTRA_TITLE. Another way to handle this would be to pass around ContentProvider URIs, which would help clean up this code in several places.

With that approach, the best way to create the SharedPreferences instance would be by passing the URI as the name string to the getSharedPreferences() call. We could easily set the name string our PreferenceActivity requests by calling getPreferenceManager().setSharedPreferencesName(uri.toString()).

public class HostEditor extends PreferenceActivity
	implements OnSharedPreferenceChangeListener {
	protected CursorPreferenceHack pref;

	@Override
	public SharedPreferences getSharedPreferences(String name, int mode) {
		Log.d(this.getClass().toString(), String.format("getSharedPreferences(name=%s)", name));
		return this.pref;
	}
	
	@Override
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		
		HostDatabase db = new HostDatabase(this);
		int id = this.getIntent().getIntExtra(Intent.EXTRA_TITLE, -1);
		
		// TODO: we could pass through a specific ContentProvider uri here
		//this.getPreferenceManager().setSharedPreferencesName(uri);
		
		this.pref = new CursorPreferenceHack(db.getWritableDatabase(), db.TABLE_HOSTS, id);
		this.pref.registerOnSharedPreferenceChangeListener(this);
		
		this.addPreferencesFromResource(R.xml.host_prefs);
		this.updateSummaries();
	}
	
	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
		// update values on changed preference
		this.updateSummaries();	
	}

	protected void updateSummaries() {
		// for all text preferences, set summary as current database value
		for(String key : this.pref.values.keySet()) {
			Preference pref = this.findPreference(key);
			if(pref == null) continue;
			if(pref instanceof CheckBoxPreference) continue;
			pref.setSummary(this.pref.getString(key, ""));
		}
	}
}

And finally the CursorPreferenceHack source. It’s pretty simple, and just wraps the SharedPreferences calls as needed when converting them over into Cursor calls. The resulting GUI is easy to change, just remember to set your android:key values to database column names so they can be resolved correctly:

So there you have it, this approach allows you to make rapid GUI interfaces to safely update your database-backed information. It’s definitely a hack for now, but it makes these GUI interfaces a snap. I think the Preferences framework itself might use databases for its normal storage, so they might generalize this family of classes in the future.



Driving Directions in Android 0.9 SDK

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

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

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.



Using ddrescue with an entire disk

Earlier this week we quickly ran ddrescue on a few 40GB disks, copying them to large .img files on another drive. We accidentally saved the entire disk /dev/hda instead of the individual partitions, and of course mount isn’t smart enough to read the partition tables embedded in the .img files.

One solution would be to dd the .img back to a physical disk and let the system read the partitions. After some digging around, some friends stumbled on using lomount from the Xen tools package:

losetup /dev/loop0 /path/to/backuphda.img
lomount -diskimage /dev/loop0 -partition 3 /mnt/yayitworks/

It would be nice if something like this was mentioned with the ddrescue documentation, because I’m sure plenty of people have run into this problem.



Thesis in Six Weeks

So the past 6 weeks of my life have been almost non-existent. They give advice to new graduate students saying you should start writing your thesis early, usually about 6 months before you graduate. Somehow I kept putting if off with excuses of “just one more test dataset” and “let’s make the code even faster.” Because of these excuses, I ended up with was the most intense 6 weeks of my life to date. When I wasn’t writing or studying for my oral exam, I was sleeping or eating. Even during classes, all I could think about was my thesis.

During this time, I jumped into my code just long enough to setup new datasets, then left it to run for a few days while I went back to writing. Thankfully I had access to two dual-processor Intel Xeon E5345 machines (sixteen 2.33GHz cores in total) with 16GB of RAM each. As I frantically ran different parameter settings, I was extremely thankful for all the time I had spent optimizing the codebase. In the end I probably ran those boxes for about 6 days straight, which ends up being about 3 months of single-CPU time.

Looking back, I think I saved a lot of time by carefully crafting everything I put into my thesis. This caused writing to be tedious and very slow, but it made for solid drafts. Over the 6 weeks I also saw the value of keeping a regular sleeping and eating schedule. You really need to pace yourself for the long haul–it’s not like a class project that you can skip a night of sleep to complete.

To keep from going completely insane during the process, I started getting serious about coffee. Until now I’ve only had a cup of the office coffee on occasion, but not on a regular basis. On the advice of a few friends, I picked up some beans, a grinder, and a French press, all for about $35. While writing my thesis, coffee turned into a sort of comforting ritual. Anyway, I digress. To summarize the things I took away, I learned that pacing yourself is key to large projects, and that an hourly cron’ed rsync of your thesis to seven servers on three different continents can really help you sleep soundly.

At some point I actually finished a solid second draft of my thesis, and then was faced with preparing and giving my thesis defense. From what I remember it went well, besides losing my voice for the rest of the night. My oral exam the next morning went well too. So at this point I’m pretty much a Master of Science, I just need to finish the one class I’m taking this semester. 🙂

If you’re interested in my thesis research, we put together a nice poster back in January for a conference. In short, we’re using artificial intelligence to design radio networks. If you’re really interested, you could watch my thesis defense or read my second thesis draft linked below.

I can’t believe it’s all over so quickly. I suppose that’s how academia works–steady progress over time with short bursts of intense activity around deadlines. Like I said earlier, this has been the most intense 6 weeks of my life to date. I wasn’t really overwhelmed at any point, and to some extent it was a blast. I wouldn’t mind doing it again sometime soon.



Android TabHost in the M5 SDK

So about a week ago I wrote up a quick example for TabHost. Then yesterday Google released a new version of the Android SDK which changed quite a bit. In the process, TabHost is no longer deprecated (yay!), but its interface has changed quite a bit, so I spent about an hour changing my earlier example to work.

Here is a quick run-down of what applies to TabHost: all of our id’s change to android:id’s, TabWidget’s width needs to fill_parent, and its height needs to be about 65px. The top padding of the content FrameLayout also needs to be about 65px.

The Java interface to TabHost also changed, letting us put both text and icons in the tabs:

setContentView(R.layout.tabs);

TabHost tabs = (TabHost)this.findViewById(R.id.tabs);
tabs.setup();

TabHost.TabSpec one = tabs.newTabSpec(“one”);
one.setContent(R.id.content1);
one.setIndicator(“labelone”, this.getResources().getDrawable(R.drawable.gohome));
tabs.addTab(one);

TabHost.TabSpec two = tabs.newTabSpec(“two”);
two.setContent(R.id.content2);
two.setIndicator(“labeltwo”);
tabs.addTab(two);

tabs.setCurrentTab(0);

And here’s a peek at what the resulting output looks like:

The one nice feature is that the tabs are now clickable instead of just keypad-only.  That’s about it, so have fun with TabHost in the new SDK. 🙂 The home icon above came from the Tango Icon Library.



Using Android TabHost

Update: the code below needs some slight changes to work with the new M5 SDK.

So over the past few weeks I’ve jumped into Google’s Android platform. It’s a blast and very well designed, but there are still some rough edges. One of those rough spots is getting a tab or paging control to work. The API documentation talks about a TabHost widget, but it has been marked as deprecated. (Already!? The API was just formed a few months ago, lol.) It speaks of a phantom “views/Tabs2.java” example which never shipped. There is also talk about a PageTurner widget, but no examples exist for that either.

Because I needed a tab-like control badly, I brute forced my way through getting a TabHost working. Hopefully this will save other developers some time. Remember that the TabHost widget is marked as deprecated in this version of the API. However, someone mentioned that TabHost might be making a comeback. In either case, here is a quick example of TabHost in action:

First let’s create an XML layout for our example, just a simple LinearLayout with a TabHost widget inside. It’s important to notice that the TabHost must contain both a TabWidget and a FrameLayout with specific id’s in order to work.

<?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”
>

<TabHost
id=”@+id/tabs”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>

<TabWidget
id=”@android:id/tabs”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
/>

<FrameLayout
id=”@android:id/tabcontent”
android:layout_width=”fill_parent”
android:layout_height=”200px”
android:paddingTop=”30px”
>

<LinearLayout
id=”@+id/content1″
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:background=”#ff99ccff”
>

<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”tab item uno :)”
/>

</LinearLayout>

<LinearLayout
id=”@+id/content2″
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:background=”#ffffcc99″
>

<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”tab item dos :/”
/>

<Button
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”tabhost needs”
/>

<Button
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”to be upgraded ;)”
/>

</LinearLayout>

</FrameLayout>

</TabHost>

</LinearLayout>

Inside the FrameLayout we can put our tab contents, which in this case are two LinearLayouts with different contents. These, of course, could be pulled by id and filled with dynamic content as needed. If I remember correctly, I think it was important that some sort of tab-content container needed to actually exist as a sub-widget under the FrameLayout in order for the tabs to work.

Next, let’s jump over to the Java code and build up the tab example.

setContentView(R.layout.tabs);

TabHost tabs = (TabHost)this.findViewById(R.id.tabs);
tabs.setup();

tabs.addTab(“one”, R.id.content1, “labelone”);
tabs.addTab(“two”, R.id.content2, “labeltwo”);

Nothing too fancy, just pull the TabHost, initialize it, and let it know about the tabs we have. Now let’s give the example a spin on the emulator:

This is the first real look we’ve had at the TabHost widget, and it looks okay. Mouse clicking doesn’t switch tabs, so you need to use the keypad’s left/right arrow keys to navigate. The up/down keys work as expected on a tab with buttons, so not much to complain about. 🙂



Laser Graffiti on Buildings

Over a weekend back in August I wrote a C++ program that allows people to draw graffiti on any surface. The person draws using a normal red laser pointer, and a MiniDV camera then detects the red dot. The C++ program maps the laser dot from camera coordinates into OpenGL screen coordinates using a simple homography. The computer projects its screen image back onto the surface, offering a persistent view of what the user has drawn.

The system is self-calibrating on startup–it uses four green squares to generate the homography. They disappear once calibration is complete. We then use OpenGL textures to render a chalk-like pen, following the laser pointer when it’s turned on. The system can also detect crude gestures to change ink color or clear the screen.

Using a typical VGA projector, we filled the side of a seven story building at night. Above are some pictures of things we drew. Because the system recognized gestures, we could easily draw using the laser pointer from a few blocks away from the actual equipment. We used a Panasonic MiniDV camcorder connected through firewire to a Intel Core2 2.4GHz box running Gentoo Linux. The computer was connected to an Epson 2400-lumens projector through a VGA cable and pointed towards the building.

Future work includes moving to an ffmpeg-based image processing solution, instead of using pipes to pass raw image data. Gestures need work so they are detected more reliably. Some effort should also be put into the laser pointer detection, as its thresholds can vary widely under different lighting conditions.

« Previous PageNext Page »