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.



Scan is now CompareEverywhere

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!



News coverage of Android Scan

Over the past few weeks Scan has been showing up in various news articles all over the web, and earlier today my local newspaper and news station covered it. Google has kept all of us busy with new internal SDK releases, and I haven’t had much time to keep up with my E-mail lately. Below are some of the news highlights I’ve pulled out. Oh yeah, and I made the Digg front-page, so that’s one lifelong goal to check off my list. :)

About a week ago Michael Becker from Montana State University interviewed me. I ended up with an article on the montana.edu front page, and then today in our local newspaper:

And earlier today I also interviewed with our local NBC affiliate KTVM. Ripped it wonderfully using my HDHomeRun and MythTV so you can watch it here:

Cringe with me as they mispronounce my name several times and then get the contest details slightly wrong. Oh well. :) I’m not really one for media attention, but this is hopefully getting the word out about Scan and even Android in general. I’ve got a few packed weeks in front of me, but I’m sure it will turn out great. Sadly because of the NDA that Google made us sign for the SDK, I’m not sure when I’ll be able to post a new list of features.

(Photo above by Kelly Gorham.)



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.



Winner of Google Developer Challenge

So earlier today I sat down at lunch with my Nokia N810 and connected to some nearby Wifi, only to find an E-mail congratulating me on being one of the 50 winners of the Google Android Developer Challenge. Mine was only one of 1,700 total entries, so I’m pretty stoked. :)

I talked briefly on this blog about my project back in April after I first submitted Scan. If you haven’t seen Scan, there is a quick 3-minute video showing it in action with real barcodes.

Because I’m geeky, I recorded the voicemail I received later in the day from Stephanie at Google:

I already registered for Google I/O in about a month, but word is that the winners get free admission. Not sure how that will work out since I already paid, but it’s all good. :)



Android Scan

So today is the deadline for the Android Developer Challenge, and I just finished up my submission. It’s a sweet tool that can show you pricing and metadata for anything with a barcode. Here’s a few of the features that make it stand out:

It’s called Android Scan and I’ve put up some documentation and a quick video that shows it in action with an external camera and real-world barcodes.

Once the developer challenge is over in mid-May I’ll be releasing both the Android and Python server source code under a GPLv3 open-source license. Send me an E-mail if you have any ideas or comments. I’m pumped about getting an actual Android device later this year.

« Previous PageNext Page »