JSON Flattener

2012/01/21

I recently ran into the problem of needing to store a JSON object in a key, value store. I pasted a little utility class that does the job below. Here’s an example run against some nasty JSON,

ORIGINAL:

{ string: 'aString', integer: -1, nested: { x: 1, y: 2, z: 3}, moreNested: { a: [1,2,3], b: [4,5,6], c: [7,8,9]}, arrayFromHell: [{ innerArray: [{ innerInnerArray: [1,2,3]}]}]}

ENCODED:

{"arrayFromHell.0.innerArray.0.innerInnerArray.0":1,"arrayFromHell.0.innerArray.0.innerInnerArray.1":2,"arrayFromHell.0.innerArray.0.innerInnerArray.2":3,"nested.z":3,"nested.y":2,"nested.x":1,"integer":-1,"string":"aString","moreNested.b.0":4,"moreNested.b.1":5,"moreNested.b.2":6,"moreNested.c.0":7,"moreNested.c.1":8,"moreNested.c.2":9,"moreNested.a.0":1,"moreNested.a.1":2,"moreNested.a.2":3}

DECODED:

{"nested":{"z":3,"y":2,"x":1},"arrayFromHell":[{"innerArray":[{"innerInnerArray":[1,2,3]}]}],"integer":-1,"string":"aString","moreNested":{"b":[4,5,6],"c":[7,8,9],"a":[1,2,3]}}

As you can see, it simply looks at every leaf value (string, int) and derives the key path and stores the value there. For example,

{ x: { y: { z: 1 } } }

becomes,

{ x.y.z: 1 }

Arrays are a special case. Each value in an array becomes the path so far to the array appended with the array index. For example,

{ anArray: [1,2,3] }

becomes,

{ anArray.0: 1, anArray.1: 2, anArray.2: 3 }
Of course this isn’t foolproof. If you have dots in your key names, it will break. If you use numbers as keys, it will break as it makes an assumption that a numbered key path element indicates an array. Here’s the source.
package org.test.flatjson;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class JsonFlattener {
	public static String encode(JSONObject jo) throws JSONException {
		String s = "{" + encode(null, jo) + "}";
		return s;

	}

	public static String encode(String json) throws JSONException {
		JSONObject jo = new JSONObject(json);
		return encode(jo);
	}

	private static String encode(String parent, Object val)
			throws JSONException {
		StringBuilder sb = new StringBuilder();
		if (val instanceof JSONObject) {
			JSONObject jo = (JSONObject) val;
			for (Iterator<String> i = jo.keys(); i.hasNext();) {
				String key = i.next();
				String hkey = (parent == null) ? key : parent + "." + key;
				Object jval = jo.get(key);
				String json = encode(hkey, jval);
				sb.append(json);
				if (i.hasNext()) {
					sb.append(",");
				}
			}
		} else if (val instanceof JSONArray) {
			JSONArray ja = (JSONArray) val;
			for (int i = 0; i < ja.length(); i++) {
				String hkey = (parent == null) ? "" + i : parent + "." + i;
				Object aval = ja.get(i);
				String json = encode(hkey, aval);
				sb.append(json);
				if (i < ja.length() - 1) {
					sb.append(",");
				}
			}
		} else if (val instanceof String) {
			sb.append("\"").append(parent).append("\"").append(":");
			String s = (String) val;
			sb.append(JSONObject.quote(s));
		} else if (val instanceof Integer) {
			sb.append("\"").append(parent).append("\"").append(":");
			Integer integer = (Integer) val;
			sb.append(integer);
		}

		return sb.toString();
	}

	public static String decode(String flatJson) throws JSONException {
		JSONObject encoded = new JSONObject(flatJson);
		return decodeToString(encoded);
	}

	public static String decodeToString(JSONObject encoded)
			throws JSONException {
		return decodeToObject(encoded).toString();
	}

	public static JSONObject decodeToObject(JSONObject encoded)
			throws JSONException {
		JSONObject decoded = new JSONObject();

		for (Iterator<String> i = encoded.keys(); i.hasNext();) {
			String hkey = i.next();
			String[] keys = hkey.split("\\.");

			Object json = decoded;

			for (int j = 0; j < keys.length; j++) {
				if (j == keys.length - 1) {
					Object val = encoded.get(hkey);
					if (json instanceof JSONObject) {
						JSONObject jo = (JSONObject)json;
						jo.put(keys[j], val);
					} else if (json instanceof JSONArray) {
						JSONArray ja = (JSONArray)json;
						int index = Integer.parseInt(keys[j]);
						ja.put(index, val);
					}
				} else {
					// we're NOT at a leaf key

					if (!isNumber(keys[j + 1])) {
						// next index is an object

						JSONObject joChild;

						if (json instanceof JSONObject) {
							// last index was an object
							// we're creating an object in an object
							JSONObject jo = (JSONObject)json;
							if (jo.has(keys[j])) {
								joChild = jo.getJSONObject(keys[j]);
							} else {
								joChild = new JSONObject();
								jo.put(keys[j], joChild);
							}
						} else if (json instanceof JSONArray) {
							// last index was an array
							// we're creating an object in an array
							JSONArray ja = (JSONArray)json;
							int index = Integer.parseInt(keys[j]);
							if (!ja.isNull(index)) {
								joChild = ja.getJSONObject(index);
							} else {
								joChild = new JSONObject();
								ja.put(index, joChild);
							}
						} else {
							throw new AssertionError("unhandled object type");
						}
						json = joChild;
					} else {
						// next index is an array element

						JSONArray jaChild;

						if (json instanceof JSONObject) {
							// last index was an object,
							// we're creating an array in an object
							JSONObject jo = (JSONObject)json;
							if (jo.has(keys[j])) {
								jaChild = jo.getJSONArray(keys[j]);
							} else {
								jaChild = new JSONArray();
								jo.put(keys[j], jaChild);
							}
						} else if (json instanceof JSONArray) {
							// last index was an array
							// we're creating an array in an array
							JSONArray ja = (JSONArray)json;
							int index = Integer.parseInt(keys[j + 1]);
							if (!ja.isNull(index)) {
								jaChild = ja.getJSONArray(index);
							} else {
								jaChild = new JSONArray();
								ja.put(index, jaChild);
							}
						} else {
							throw new AssertionError("unhandled object type");
						}
						json = jaChild;
					}
				}
			}
		}
		return decoded;
	}

	private static boolean isNumber(String s) {
		try {
			Integer.parseInt(s);
			return true;
		} catch (NumberFormatException e) {
			return false;
		}

	}
}

Android Bitmap Scaling

2011/01/27

Scaling images is a common task for many applications including those written for Android. The most obvious approach, as described on this page, won’t work in most cases. The problem as you would soon find out if you tried it is that your phone doesn’t have enough memory. Luckily we can get around that, but not easily.

The trick ends up being a two step process. First we decode the bitmap, setting the sampling level as high as we can while avoiding loss of quality, then we scale this smaller bitmap in memory down to the exact specified size. I’ve included this in a nicely packaged class BitmapScaler, included at the end of this post.The class itself looks quite long but this is only because it has the flexibility of scaling bitmaps from resources, assets, and files.

BitmapScaler preserves the aspect ratio. You may only pass a new width. The resulting height will be scaled equal to the amount the width was scaled.

For example,

BitmapScaler scaler = new BitmapScaler(getResources(), R.drawable.moorwen, newWidth);
imageView.setImageBitmap(scaler.getScaled());

BitmapScaler class follows,

class BitmapScaler {
	private static class Size {
		int sample;
		float scale;
	}

	private Bitmap scaled;

	BitmapScaler(Resources resources, int resId, int newWidth)
			throws IOException {
		Size size = getRoughSize(resources, resId, newWidth);
		roughScaleImage(resources, resId, size);
		scaleImage(newWidth);
	}

	BitmapScaler(File file, int newWidth) throws IOException {
		InputStream is = null;
		try {
			is = new FileInputStream(file);
			Size size = getRoughSize(is, newWidth);
			try {
				is = new FileInputStream(file);
				roughScaleImage(is, size);
				scaleImage(newWidth);
			} finally {
				is.close();
			}
		} finally {
			is.close();
		}
	}

	BitmapScaler(AssetManager manager, String assetName, int newWidth)
			throws IOException {
		InputStream is = null;
		try {
			is = manager.open(assetName);
			Size size = getRoughSize(is, newWidth);
			try {
				is = manager.open(assetName);
				roughScaleImage(is, size);
				scaleImage(newWidth);
			} finally {
				is.close();
			}
		} finally {
			is.close();
		}
	}

	Bitmap getScaled() {
		return scaled;
	}

	private void scaleImage(int newWidth) {
		int width = scaled.getWidth();
		int height = scaled.getHeight();

		float scaleWidth = ((float) newWidth) / width;
		float ratio = ((float) scaled.getWidth()) / newWidth;
		int newHeight = (int) (height / ratio);
		float scaleHeight = ((float) newHeight) / height;

		Matrix matrix = new Matrix();
		matrix.postScale(scaleWidth, scaleHeight);

		scaled = Bitmap.createBitmap(scaled, 0, 0, width, height, matrix, true);
	}

	private void roughScaleImage(InputStream is, Size size) {
		Matrix matrix = new Matrix();
		matrix.postScale(size.scale, size.scale);

		BitmapFactory.Options scaledOpts = new BitmapFactory.Options();
		scaledOpts.inSampleSize = size.sample;
		scaled = BitmapFactory.decodeStream(is, null, scaledOpts);
	}

	private void roughScaleImage(Resources resources, int resId, Size size) {
		Matrix matrix = new Matrix();
		matrix.postScale(size.scale, size.scale);

		BitmapFactory.Options scaledOpts = new BitmapFactory.Options();
		scaledOpts.inSampleSize = size.sample;
		scaled = BitmapFactory.decodeResource(resources, resId, scaledOpts);
	}

	private Size getRoughSize(InputStream is, int newWidth) {
		BitmapFactory.Options o = new BitmapFactory.Options();
		o.inJustDecodeBounds = true;
		BitmapFactory.decodeStream(is, null, o);

		Size size = getRoughSize(o.outWidth, o.outHeight, newWidth);
		return size;
	}

	private Size getRoughSize(Resources resources, int resId, int newWidth) {
		BitmapFactory.Options o = new BitmapFactory.Options();
		o.inJustDecodeBounds = true;
		BitmapFactory.decodeResource(resources, resId, o);

		Size size = getRoughSize(o.outWidth, o.outHeight, newWidth);
		return size;
	}

	private Size getRoughSize(int outWidth, int outHeight, int newWidth) {
		Size size = new Size();
		size.scale = outWidth / newWidth;
		size.sample = 1;

		int width = outWidth;
		int height = outHeight;

		int newHeight = (int) (outHeight / size.scale);

		while (true) {
			if (width / 2 < newWidth || height / 2 < newHeight) {
				break;
			}
			width /= 2;
			height /= 2;
			size.sample *= 2;
		}
		return size;
	}
}

Or, try out the Eclipse test project.


Android Shortcuts

2011/01/23

Shortcuts are an often overlooked usability enhancer for your Android app.  They are a nice alternative to providing quick access to particular locations or views of data, where you would otherwise have “favorites” or “most recently used” function. I wanted to take a moment to share my implementation experience.

My use case: I am the author of the Clear Sky Droid (and the donate version) app which is available on the Android market. Being a tad esoteric, it’s probably not the best example, but I’ll try my best to explain the usability problem in abstract terms. Essentially, the user can define a list of favorites by searching for items. From the list of favorites, they can then select a detailed view of the item. The usability problem is that many users want direct access to the detailed view of a particular favorite – something that is normally 3 clicks away.

Shortcuts to the rescue. With this feature, the user can set up a desktop shortcut for direct access to the detailed view of a particular favorite. It’s now one click away.

What exactly is a shortcut? It’s an Android desktop artifact. It is not part of your application. When clicked, it broadcasts an intent that you have defined. So you can see where this is going. You simply need to set up your application to respond appropriately to whatever intent is configured into the shortcut.

First we will register an activity into the Android “create shortcuts” home screen menu. Create a new activity, or add the functionality to an existing activity. Respond to the create shortcut intent,

<intent-filter>
    <action android:name="android.intent.action.CREATE_SHORTCUT" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

You can look at CSDroid’s AndroidManifest.xml to see this in context.

This is all it takes to make your application show up when the user long presses on the home screen and select “shortcuts”.

You must now make your application handle the intent. In CSDroid, look at the startService() method in the class TabwidgetActivity, find the line where it’s checking if the intent’s action is “android.intent.action.CREATE_SHORTCUT”. This means the user has long pressed the home screen and selected your app.

Gather any information necessary to customize the shortcut to access to a particular aspect of your application.  For CSDroid, we open a dialog that lets the user select a favorite item. Looking at the code in TabwidgetActivity mentioned above, you can see that a dialog  is shown when the CREATE_SHORTCUT intent is received.  The dialog builder class is FaveShortcutDialogBuilder.

FaveShortcutDialogBuilder just gets the favorite item then calls back to TabwidgetActivity‘s saveShortcut() method. This is where we request creation of the desktop shortcut. Here are the steps,

  1. Create an intent that targets your application, setting action, data, extras, etc accordingly to perform the specific action requested when the user clicks the shortcut
  2. Wrap that intent in another intent that will be sent back to the Android “create shortcut” activity
  3. Set this wrapping intent as the result of your activity, and finish

Here’s the code from CSDroid,

void saveShortcut(Site site) {
    Intent shortcutIntent = new Intent(this, TabWidgetActivity.class);
    shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    shortcutIntent.putExtra("org.jtb.csdroid.site.id", site.getId());

    Intent intent = new Intent();
    intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
    intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, site.getName());
    intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(this, R.drawable.icon));
    intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
    setResult(RESULT_OK, intent);
    finish();
}

In this case, if CSDroid sees a site ID in the intent’s extras, it will perform the shortcut action for that site ID.

I should not that in the context of CSDroid the code could be organized better. A better pattern would separate the main activity from the short cut creation, and shortcut handling activities. So you might have MainActivity, CreateShortcutActivity, and HandleShortcutActivity.

One last gotcha … my first idea was to auto-create the shortcut from the app. In other words, circumvent the steps where the user must long-press the home screen, select shortcuts, select my app, then select a favorite – all from within the application. CSDroid already has an activity that lists the user’s favorites. I wanted to simply add a “add shortcut” option right there. Well, you can’t do that. You cannot programmatically create shortcuts. Only the user can do that.

CSDroid is open source. Visit the CSDroid Google code project page to find the source code, etc.

And finally, all due credit to Attilla Danko, the owner of ClearDarkSky.com, the data source for this application.


Gracefully Handle When Location is Unavailable (Android)

2010/12/24

This seemed like a useful pattern so I thought I’d document it here, as it seems quite common. The scenario is that you have an application that provides some location-based service or information, but needs to gracefully handle the situation when the location cannot be obtained.

Ther are various reason why the location might not be obtainable. The most common is that the user disabled one or all location services requested by your app. Just because you request location permissions and the user accepts doesn’t mean that they actually have those services enabled. And worse, when you request a location in this situation, you don’t get any sort of indicator that it has been disabled. Your location changed callback is just never called.

My application grabs earthquake data from USGS and provides information to the user with respect to their proximity to the quake. If the app can’t get the user’s location, it should functionally normally, minuses providing distances to quake epicenters. For reference, the app is called QuakeAlert! and you can find it on the market, and the source on Google code.

QuakeAlert! has a service that runs periodically and alerts the user of any new earthquakes that match their criteria. When the service runs, it should get the location if possible, process the data from USGS, and potentially alert the user. The problem is in how Android location callbacks operate. The service can request a location update, but there’s no guarantee it will ever be called (and it won’t if the user has disabled the location service).  Here is the solution,

UpdateService- this is an intent service that does the real work, whatever that may be. In the case of QuakeAlert! it fetches the data from USGS and processes it, sends notifications, and updates the user interface (if it’s running).

LocationService- this is a regular (non-intent) service that does the following,

  1. Registers for location updates
  2. Schedules a TimerTask for execution (say a few minutes in the future)

If the service gets an “on location changed” event before the time is up, it cancels the timer. If the timer runs first, it cancels the “on location changed” registration. In both cases, it calls the UpdateService to do the work.

UpdateService gets the location by calling getLastKnownLocation(). If there was a location obtained through the running of LocationService then it will get that location and use it. The last known location may have also been obtained by a different application at some earlier time getting location updates. That’s okay to, we know we’ve tried our best to get the most accurate location we can at the time.

The implementation of LocationService in QuakeAlert! is reusable. Just pass it a timeout (in milliseconds) and a broadcast intent to send when either the location in obtained or when the timer executes. For example,

// create broadcast intent that will ultimately start your
// intent service
Intent broadcastIntent = new Intent(...);

Intent locationIntent = new Intent(context, LocationService.class);
locationIntent.putExtra("timeout", 1000 * 60 * 2); // 2 minutes
locationIntent.putExtra("broadcastIntent", broadcastIntent);
context.startService(locationIntent);

Why pass a broadcast intent, instead of an intent that would start the update service directly? Since UpdateService is an intent service, we must obtain a wake lock before it is run, and the pattern for doing that is to receive an intent (in a receiver), grab the lock there, start the service from the reciever’s handler, then release the lock in the intent service when the work is done.

Note that this same pattern applied to a a foreground activity is much more straighforward: 1) register for location updates 2) open a cancelable progress dialog. If we get an “on location changed” event, dismiss the dialog, and start the UpdateService. If the user cancels, dismiss the dialog, and start the UpdateService. In both cases remember to remove thelocation listener.


3D Model Viewer for Android

2010/12/07

I just published a new application on the Android Market. It’s called ModelView and is a 3D model viewer. You can download it onto 1.6+ devices. It supports loading of standard .OFF and .OBJ format files, allows rotation and zoom (via multi-touch, on 2.0+ devices), and comes packaged with many interesting 3D models. You can also view your own .OBJ / .OFF files by placing them on the SD card.

This started out as a sample OpenGL project, with me learning how to define simple 3D objects with vertices and triangles. From there I started to think about how I could define the model’s data some other way than statically in the code. Then I started looking for standard ways to do that. Then getting lazy and wanting to take advantage of models defined by others.  I ended up learning quite a bit about OpenGL and 3D graphics. Now it’s time to get a book and put it all in it’s place. The app is really just a learning exercise and serves no real purposes, but it’s fun to play with so I posted on the market so other people can take a look. The code is available on Google code, and hopefully someone else can learn from this.


Netflix on Android

2010/11/20

Like many Android users I’m dismayed at the lack of a Netflix application. I loathe that I have to go grab my iPod when I want Netflix on the go. Apparently there’s a good reason for this, however, as a naive mid-level Android developer I’m at a loss trying to understand what that reason is. In a recent blog entry, Greg Peters from Netflix said,

“The hurdle has been the lack of a generic and complete platform security and content protection mechanism available for Android. The same security issues that have led to piracy concerns on the Android platform have made it difficult for us to secure a common Digital Rights Management (DRM) system on these devices.”

What does copy protection have to do with a streaming app? You authenticate the user and then stream. Nothing is every written to disk (except maybe a buffer) so there’s nothing to copy. The stream itself must be protected, so the point of comprise would need to be the memory space in the app itself, where the unencrypted stream is present. In Android, you’d need to have a rooted phone and a native library or app to access this. Sure, it’s relatively easy to root Android phones, but it’s easy to root an iPhone as well.

Also, what piracy concerns? It used to be the case that apps were pirated left and right, but that’s not true anymore. Android did away with its copy protection scheme and now uses a license management service which is built right into the Android SDK. Regardless, what do pirated apps have to do with digital content protection?

Content holders in general would do well to understand the purpose of DRM in 2010. It’s purpose should NOT be to prevent people from copying copy protected digital media. Why not? Because that’s impossible. The world contains an infinite supply of bored 15 year olds with nothing but time on their hands. They will beat whatever scheme you put in place. You can waste innumerable costly hours of  highly paid staff software developers coming up with a new methodology to have it broken in hours.

So what should be the purpose of DRM? To make stealing content annoying. To make it, for 99.9% of the schlubs out there, easier and more cost effective to pay for the content than it is to steal it … and write off your losses for that 0.1% of people that have way more time than money and will find a way to beat the DRM regardless.

Content holders should understand that any content (music, movies, bookes) is a bitorrent download away, and one need not be highly technical to grab it. Go to a site. Search. Click. Wait 10 minutes. That’s it. Make it easier and more cost effective than that to purchase content. If you don’t, people will steal it.

Coming back to Netflix and Android … a Netflix app doesn’t need to provide uber-safe device dependent hardware-level DRM. If it simply avoids writing the video file to a persistent store that would be enough.

Netflix tells us we can expect an app in 2011,

But I’m happy to announce we’ll launch select Android devices that will instantly stream from Netflix early next year.

That means we’ll see a Netflix app for the premiere devices / carriers – like whatever the latest Motorla Droid will be at the time. I don’t hold much hope for my poor Nexus One, relegated to a second class carrier like T-Mobile.


Iterative Tree Traversal

2010/11/16

While studying for an interview, I some how got it into my head that I should understand the iterative algorithms for the three standard tree traversal methods (pre-, post-, in-). While the recursive algorithms for these are straightforward and intuitive, the iterative ones are most certainly not. Worse, googling on this topic turned up a lot of incomplete or just plain wrong solutions.

With some painstaking work I believe I came up with some nearly as simple as possible solutions. Some solutions I found required parent pointers, some required visit markers. These require neither of these.

After each algorithm, I include a walkthrough of the algorithm based on this sample tree from the Wikipedia tree traversal page.

Pre-order,

void preOrderIterative(Node n) {
	Stack<Node> s = new new Stack<Node>();
	s.push(n);

	while (!s.empty()) {
		n = s.pop();
		n.visit();

		if (n.right != null) {
			s.push(n.right);
		}
		if (n.left != null) {
			s.push(n.left);
		}
	}
}

Each line of the example shows the stack at the beginning of the iteration, the set of visited nodes so far after the iteration, and the stack at the end of the iteration.

1: s={}, visited={F}, s={B,G}
2: s={G}, visited={F,B}, s={A,D,G}
3: s={D,G}, visited={F,B,A}, s={D,G}
4: s={G}, visited={F,B,A,D}, s={C,E,G}
5: s={E,G}, visited={F,B,A,D,C}, s={E,G}
6: s={G}, visited={F,B,A,D,C,E}, s={G}
7: s={}, visited={F,B,A,D,C,E,G}, s={I}
8: s={}, visited={F,B,A,D,C,E,G,I}, s={H}
9: s={}, visited={F,B,A,D,C,E,G,I,H}, s={}

Post-order,

This one uses two stacks. It consumes the input stack and builds the output stack. At the end of the method, it pops each node from the output stack and visits it.

void postOrderIterative(Node n) {
	Stack<Node> s = new new Stack<Node>();
	Stack<Node> o = new new Stack<Node>();
	s.push(n);

	while (!s.empty()) {
		n = s.pop();
		o.push(n);

		if (n.left != null) {
			s.push(n.left);
		if (n.right != null) {
			s.push(n.right);
		}
	}
	while (!o.empty()) {
		o.pop().visit();
	}
}

Each line of the example below shows state of the input stack and output stack after the iteration.

1: out={F}, in={G,B}
2: out={G,F}, in={I,B}
3: out={I,G,F}, in={H,B}
4: out={H,I,G,F}, in={B}
5: out={B,H,I,G,F}, in={D,A}
6: out={D,B,H,I,G,F}, in={E,C,A}
7: out={E,D,B,H,I,G,F}, in={C,A}
8: out={C,E,D,B,H,I,G,F}, in={A}
9: out={A,C,E,D,B,H,I,G,F}, in={}

In-order,

void inOrderIterative(Node n) {
	Stack<Node> s = new new Stack<Node>();
	s.push(n);

	while (!s.empty()) {
		while (n.left != null) {
			n = n.left;
			s.push(n);
		}
		n = s.pop();
		n.visit();
		if (n.right != null) {
			s.push(n.right);
		}
	}
}

Each line of the example shows the stack at the beginning of the iteration, the set of visited nodes so far after the iteration, and the stack at the end of the iteration.

1: s={A,B,F}, v={A}, s={B,F}
2: s={B,F}, v={A,B}, s={D,F}
3: s={C,D,F}, v={A,B,C}, s={D,F}
4: s={D,F}, v={A,B,C,D}, s={E,F}
5: s={E,F}, v={A,B,C,D,E}, s={F}
6: s={F}, v={A,B,C,D,E,F}, s={G}
7: s={G}, v={A,B,C,D,E,F,G}, s={I}
8: s={H,I}, v={A,B,C,D,E,F,G,H}, s={I}
9: s={I}, v={A,B,C,D,E,F,G,H,I}, s={}

Android Themes: an Adventure

2010/10/25

I am the author of a simple Android app widget , “Uptime Widget” that shows the uptime, and max uptime of the user’s device. As widgets are rendered on the home screen their readability greatly varies depending on the user’s wall paper. I got a lot of request to provide “lighter” or “darker” versions of widget. This prompted me to investigate Android’s theming abilities.

My first attempt didn’t use themes or styles at all. I integrated the simple color picker dialog from the Android API demos, with the intention of having an app widget configuration activity that allowed the user to pick their background and text color, at the least. I found at the hard way the RemoteViews, the view class used to build app widgets doesn’t allow programmatic setting of the background color.  Back to the drawing board.

My next attempt was to use Android’s theme / style facilities in full. Essentially, define several themes and allow the user to pick one in a configuration activity. This page has an excellent example and sample code. I was quite frustrated before finding this, as there aren’t really any good examples of how to make dynamic references to styles, versus statically declaring their use in the layout. That’s critical if you are to have >1 theme for your application. Unfortunately, you can’t use this method for app widgets. I don’t understand why at this point, another limitation in RemoteViews. I filed an issue against the AOSP. I won’t hold my breath.

The only option left is to statically declare style usage in the app widget’s layout. To get multiple, dynamic themes, the app widget must build the RemoteViews object based on a dynamically picked layout ID. That means having one layout per theme (and in my case, additionally, per orientation). Uptime Widget has 8 very similar layouts. A terrible solution by all regards. Modifying the UI or adding a theme is a tedious error-prone endeavor. But it works. You can take a look at the source over at the Google code project if you are interested.

All in all, I found Android style / theme mechanism lacking. First. the method of referencing a theme is awkward. You must first declare a reference attribute,

<attr name="Caption" format="reference"/>

then, create various styles that map to the reference,

<style name="Dark.Caption">
  <item name="android:textColor">@android:color/black</item>
</style>
<style name="Light.Caption">
  <item name="android:textColor">@android:color/white</item>
</style>

Now, map that reference to real styles within a theme,

<style name="DarkTheme">
  <item name="Caption" value="@style/Dark.Caption"/>
</style>
<style name="LightTheme">
  <item name="Caption" value="@style/Light.Caption"/>
</style>

Finally, use the attribute reference in your layout,

<TextView style="?Caption" .../>

and set the theme using the android:theme attribute to either “DarkTheme” or “LightTheme” at either the application or activity level in your manifest. What’s missing? Selectors. For example, if you apply a theme with android:textColor=”…”, it applies to every element in the layout. The only way to get around this is to call out the specific style (or reference to a style, as shown above) in each view element. We need to be able to select on the element type and  ID like,

<item name="android:textColor" selector="@+id/topLayout/*.TextView">
  @android:color/black
</item>

This styles only TextViews under the element with the topLayout. There would need to be some advanced syntax, for example, to select elements recursively. CSS is not simple, and it would not be simple to mimic it either, but without this Android styles are sorely lacking.


2010/01/08

In a previous entry, I talked about AT&T’s plan to make microcell (a.k.a. femotcell) technology available to their subscribers. From their CES presentation, it looks like a company named MagicJack has beat them to it. You may recognize the MagicJack name from those low-budget late night commercials. They have a product that plugs into your USB port, and provides a standard RJ45 jack to make VoIP calls, for $20 / month, free for the first year. Despite the cheesy commercials, it’s a decent product, so says Consumer Reports.

Now their are releasing something similar, but for GSM mobile networks. Plug in their USB device and install some software, and you have a mini cell tower in your house. Their product will route your GSM calls through VoIP bypassing your carrier’s network. It will cost the same price as the original MagicJack. Don’t ask me how they are allowed to do this. Isn’t it illegal to co-opt any frequency of the radio spectrum for your own purposes?

What prevents me from connecting to an arbitrary MagicJack femotcell without my knowledge? Can it be restricted to particular GSM devices? If I own the MagicJack, I don’t want to be clogging my network with my neighbor’s GSM traffic. If my neighbor has one, I don’t want my calls going through their unsecured network, not to mention that the quality, and availability of the network is completely out of my control and unsupported. Can you imagine the support nightmare that would make for carriers when they can no longer tell if a user is connected to their network or an arbitrary unregulated femtocell somewhere?

It will be interesting to see how this plays out. The idea that anyone with an internet connection and a computer can create a cell tower is intriguing to say the least.


AT&T’s 3g Microcell Scam

2009/12/24

AT&T is beta testing what they call a 3g Microcell in select markets. In a nutshell, this is a mini, personal cell tower for your home. It bridges a local 3g router with your existing broadband connection. When available in your market, the microcell is available to AT&T wireless customer at an additional fee. It can blanket 5,00 square feet with a strong signal (probably under optimal conditions, not in a city). It is locked to your phones only. You must have a 3g phone. Engadget has a more detailed write up.

Let me make sure you understand. For the privilege of having a usable wireless signal in / around your home, you can pay AT&T an additional charge. This is on top of the $80+ you are already paying for your wireless plan. Along with that, AT&T also gets to use your pipe, not theirs, to provide you the signal. The pipe you are already paying for to them (via AT&T DSL) or some other broadband provider, on top of the $80, on top of the charge for the Microcell.

Depending on the additional microcell charge, this is somewhere between a bad insult and rape. I expect to have a usable wireless phone service for $80 / month. I’m certainly open to working outside the box to make it better, but I shouldn’t have to pay an additional charge. If anything, AT&T should provide a kickback to microcell users, as it is using the microcell host’s broadband pipe and not further clogging AT&T’s. It shouldn’t come as a surprise that microcell users would be connected to their microcell more than any other cell “tower”, possibly with the exception of their employer’s locale (and it makes sense for businesses to have microcells of their own anyway). That means that the microcell is diverting large percentage of traffic that would otherwise be filling up AT&T’s pipes.

Here’s how AT&T should make use of microcell technology. In areas where many users report spotty service, AT&T should contact customers and offer them free microcells. The microcell host should get some discount just for using the microcell for their own purposes. Additionally, if the user agrees, they can share some configurable portion of their broadband and make it available through the microcell for surrounding customers. The microcell host should get a discount proportional to how much service they provide, proportional to the load they take off AT&T’s pipes. AT&T should make larger-scale microcells available to businesses (millicells?).

At even a small monthly discount, AT&T would have more volunteers than they would know what to do with. In densely populated areas, this would be a godsend for them.

This idea does complicate deployment to some degree. The microcell needs to monitor and throttle bandwidth and connections, and reject connections when the limit is reached. For discounts, it needs to track and upload usage back to AT&T. These are not hard problems by any means. I also wonder how broadband providers would feel about AT&T offloading traffic onto their networks without providing any compensation.