Showing posts with label How-to. Show all posts
Showing posts with label How-to. Show all posts

Friday, December 18, 2009

Back and other hard keys: three stories

Android 2.0 introduces new behavior and support for handling hard keys such as BACK and MENU, including some special features to support the virtual hard keys that are appearing on recent devices such as Droid.

This article will give you three stories on these changes: from the most simple to the gory details. Pick the one you prefer.

Story 1: Making things easier for developers

If you were to survey the base applications in the Android platform, you would notice a fairly common pattern: add a little bit of magic to intercept the BACK key and do something different. To do this right, the magic needs to look something like this:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
// do something on back.
return true;
}

return super.onKeyDown(keyCode, event);
}

How to intercept the BACK key in an Activity is also one of the common questions we see developers ask, so as of 2.0 we have a new little API to make this more simple and easier to discover and get right:

@Override
public void onBackPressed() {
// do something on back.
return;
}

If this is all you care about doing, and you're not worried about supporting versions of the platform before 2.0, then you can stop here. Otherwise, read on.

Story 2: Embracing long press

One of the fairly late addition to the Android platform was the use of long press on hard keys to perform alternative actions. In 1.0 this was long press on HOME for the recent apps switcher and long press on CALL for the voice dialer. In 1.1 we introduced long press on SEARCH for voice search, and 1.5 introduced long press on MENU to force the soft keyboard to be displayed as a backwards compatibility feature for applications that were not yet IME-aware.

(As an aside: long press on MENU was only intended for backwards compatibility, and thus has some perhaps surprising behavior in how strongly the soft keyboard stays up when it is used. This is not intended to be a standard way to access the soft keyboards, and all apps written today should have a more standard and visible way to bring up the IME if they need it.)

Unfortunately the evolution of this feature resulted in a less than optimal implementation: all of the long press detection was implemented in the client-side framework's default key handling code, using timed messages. This resulted in a lot of duplication of code and some behavior problems; since the actual event dispatching code had no concept of long presses and all timing for them was done on the main thread of the application, the application could be slow enough to not update within the long press timeout.

In Android 2.0 this all changes, with a real KeyEvent API and callback functions for long presses. These greatly simplify long press handling for applications, and allow them to interact correctly with the framework. For example: you can override Activity.onKeyLongPress() to supply your own action for a long press on one of the hard keys, overriding the default action provided by the framework.

Perhaps most significant for developers is a corresponding change in the semantics of the BACK key. Previously the default key handling executed the action for this key when it was pressed, unlike the other hard keys. In 2.0 the BACK key is now execute on key up. However, for existing apps, the framework will continue to execute the action on key down for compatibility reasons. To enable the new behavior in your app you must set android:targetSdkVersion in your manifest to 5 or greater.

Here is an example of code an Activity subclass can use to implement special actions for a long press and short press of the CALL key:

@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_CALL) {
// a long press of the call key.
// do our work, returning true to consume it. by
// returning true, the framework knows an action has
// been performed on the long press, so will set the
// canceled flag for the following up event.
return true;
}
return super.onKeyLongPress(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_CALL && event.isTracking()
&& !event.isCanceled()) {
// if the call key is being released, AND we are tracking
// it from an initial key down, AND it is not canceled,
// then handle it.
return true;
}
return super.onKeyUp(keyCode, event);
}

Note that the above code assumes we are implementing different behavior for a key that is normally processed by the framework. If you want to implement long presses for another key, you will also need to override onKeyDown to have the framework track it:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_0) {
// this tells the framework to start tracking for
// a long press and eventual key up. it will only
// do so if this is the first down (not a repeat).
event.startTracking();
return true;
}
return super.onKeyDown(keyCode, event);
}

Story 3: Making a mess with virtual keys

Now we come to the story of our original motivation for all of these changes: support for virtual hard keys, as seen on the Droid and other upcoming devices. Instead of physical buttons, these devices have a touch sensor that extends outside of the visible screen, creating an area for the "hard" keys to live as touch sensitive areas. The low-level input system looks for touches on the screen in this area, and turns these into "virtual" hard key events as appropriate.

To applications these basically look like real hard keys, though the generated events will have a new FLAG_VIRTUAL_HARD_KEY bit set to identify them. Regardless of that flag, in nearly all cases an application can handle these "hard" key events in the same way it has always done for real hard keys.

However, these keys introduce some wrinkles in user interaction. Most important is that the keys exist on the same surface as the rest of the user interface, and they can be easily pressed with the same kind of touches. This can become an issue, for example, when the virtual keys are along the bottom of the screen: a common gesture is to swipe up the screen for scrolling, and it can be very easy to accidentally touch a virtual key at the bottom when doing this.

The solution for this in 2.0 is to introduce a concept of a "canceled" key event. We've already seen this in the previous story, where handling a long press would cancel the following up event. In a similar way, moving from a virtual key press on to the screen will cause the virtual key to be canceled when it goes up.

In fact the previous code already takes care of this � by checking isCanceled() on the key up, canceled virtual keys and long presses will be ignored. There are also individual flags for these two cases, but they should rarely be used by applications and always with the understanding that in the future there may be more reasons for a key event to be canceled.

For existing application, where BACK key compatibility is turned on to execute the action on down, there is still the problem of accidentally detecting a back press when intending to perform a swipe. Though there is no solution for this except to update an application to specify it targets SDK version 5 or later, fortunately the back key is generally positioned on a far side of the virtual key area, so the user is much less likely to accidentally hit it than some of the other keys.

Writing an application that works well on pre-2.0 as well as 2.0 and later versions of the platform is also fairly easy for most common cases. For example, here is code that allows you to handle the back key in an activity correctly on all versions of the platform:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ECLAIR
&& keyCode == KeyEvent.KEYCODE_BACK
&& event.getRepeatCount() == 0) {
// Take care of calling this method on earlier versions of
// the platform where it doesn't exist.
onBackPressed();
}

return super.onKeyDown(keyCode, event);
}

@Override
public void onBackPressed() {
// This will be called either automatically for you on 2.0
// or later, or by the code above on earlier versions of the
// platform.
return;
}

For the hard core: correctly dispatching events

One final topic that is worth covering is how to correctly handle events in the raw dispatch functions such as onDispatchEvent() or onPreIme(). These require a little more care, since you can't rely on some of the help the framework provides when it calls the higher-level functions such as onKeyDown(). The code below shows how you can intercept the dispatching of the BACK key such that you correctly execute your action when it is release.

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getRepeatCount() == 0) {

// Tell the framework to start tracking this event.
getKeyDispatcherState().startTracking(event, this);
return true;

} else if (event.getAction() == KeyEvent.ACTION_UP) {
getKeyDispatcherState().handleUpEvent(event);
if (event.isTracking() && !event.isCanceled()) {

// DO BACK ACTION HERE
return true;

}
}
return super.dispatchKeyEvent(event);
} else {
return super.dispatchKeyEvent(event);
}
}

The call to getKeyDispatcherState() returns an object that is used to track the current key state in your window. It is generally available on the View class, and an Activity can use any of its views to retrieve the object if needed.

Wednesday, November 11, 2009

Integrating Application with Intents

Written in collaboration with Michael Burton, Mob.ly; Ivan Mitrovic, uLocate; and Josh Garnier, OpenTable.

OpenTable, uLocate, and Mob.ly worked together to create a great user experience on Android. We saw an opportunity to enable WHERE and GoodFood users to make reservations on OpenTable easily and seamlessly. This is a situation where everyone wins — OpenTable gets more traffic, WHERE and GoodFood gain functionality to make their applications stickier, and users benefit because they can make reservations with only a few taps of a finger. We were able to achieve this deep integration between our applications by using Android's Intent mechanism. Intents are perhaps one of Android's coolest, most unique, and under-appreciated features. Here's how we exploited them to compose a new user experience from parts each of us have.

Designing

One of the first steps is to design your Intent interface, or API. The main public Intent that OpenTable exposes is the RESERVE Intent, which lets you make a reservation at a speci?c restaurant and optionally specify the date, time, and party size.

Here's an example of how to make a reservation using the RESERVE Intent:

startActivity(new Intent("com.opentable.action.RESERVE",
Uri.parse("reserve://opentable.com/2947?partySize=3")));

Our objective was to make it simple and clear to the developer using the Intent. So how did we decide what it would look like?

First, we needed an Action. We considered using Intent.ACTION_VIEW, but decided this didn't map well to making a reservation, so we made up a new action. Following the conventions of the Android platform (roughly <package-name>.action.<action-name>), we chose "com.opentable.action.RESERVE". Actions really are just strings, so it's important to namespace them. Not all applications will need to de?ne their own actions. In fact, common actions such as Intent.ACTION_VIEW (aka "android.intent.action.VIEW") are often a better choice if you're not doing something unusual.

Next we needed to determine how data would be sent in our Intent. We decided to have the data encoded in a URI, although you might choose to receive your data as a collection of items in the Intent's data Bundle. We used a scheme of "reserve:" to be consistent with our action. We then put our domain authority and the restaurant ID into the URI path since it was required, and we shunted off all of the other, optional inputs to URI query parameters.

Exposing

Once we knew what we wanted the Intent to look like, we needed to register the Intent with the system so Android would know to start up the OpenTable application. This is done by inserting an Intent filter into the appropriate Activity declaration in AndroidManifest.xml:

<activity android:name=".activity.Splash" ... >
...
<intent-filter>
<action android:name="com.opentable.action.RESERVE"/>
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="reserve" android:host="opentable.com"/>
</intent-filter>
...
</activity>

In our case, we wanted users to see a brief OpenTable splash screen as we loaded up details about their restaurant selection, so we put the Intent Filter in the splash Activity de?nition. We set our category to be DEFAULT. This will ensure our application is launched without asking the user what application to use, as long as no other Activities also list themselves as default for this action.

Notice that things like the URI query parameter ("partySize" in our example) are not speci?ed by the Intent ?lter. This is why documentation is key when de?ning your Intents, which we'll talk about a bit later.

Processing

Now the only thing left to do was write the code to handle the intent.

    protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Uri uri;
final int restaurantId;
try {
uri = getIntent().getData();
restaurantId = Integer.parseInt( uri.getPathSegments().get(0));
} catch(Exception e) {
// Restaurant ID is required
Log.e(e);
startActivity( FindTable.start(FindTablePublic.this));
finish();
return;
}
final String partySize = uri.getQueryParameter("partySize");
...
}

Although this is not quite all the code, you get the idea. The hardest part here was the error handling. OpenTable wanted to be able to gracefully handle erroneous Intents that might be sent by partner applications, so if we have any problem parsing the restaurant ID, we pass the user off to another Activity where they can find the restaurant manually. It's important to verify the input just as you would in a desktop or web application to protect against injection attacks that might harm your app or your users.

Calling and Handling Uncertainty with Grace

Actually invoking the target application from within the requester is quite straight-forward, but there are a few cases we need to handle. What if OpenTable isn't installed? What if WHERE or GoodFood doesn't know the restaurant ID?



Restaurant ID knownRestaurant ID unknown
User has OpenTableCall OpenTable IntentDon't show reserve button
User doesn't have OpenTableCall Market IntentDon't show reserve button

You'll probably wish to work with your partner to decide exactly what to do if the user doesn't have the target application installed. In this case, we decided we would take the user to Android Market to download OpenTable if s/he wished to do so.

    public void showReserveButton() {

// setup the Intent to call OpenTable
Uri reserveUri = Uri.parse(String.format( "reserve://opentable.com/%s?refId=5449",
opentableId));
Intent opentableIntent = new Intent("com.opentable.action.RESERVE", reserveUri);

// setup the Intent to deep link into Android Market
Uri marketUri = Uri.parse("market://search?q=pname:com.opentable");
Intent marketIntent = new Intent(Intent.ACTION_VIEW).setData(marketUri);

opentableButton.setVisibility(opentableId > 0 ? View.VISIBLE : View.GONE);
opentableButton.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
PackageManager pm = getPackageManager();
startActivity(pm.queryIntentActivities(opentableIntent, 0).size() == 0 ?
opentableIntent : marketIntent);
}
});
}

In the case where the ID for the restaurant is unavailable, whether because they don't take reservations or they aren't part of the OpenTable network, we simply hide the reserve button.



Publishing the Intent Specification

Now that all the technical work is done, how can you get other developers to use your Intent-based API besides 1:1 outreach? The answer is simple: publish documentation on your website. This makes it more likely that other applications will link to your functionality and also makes your application available to a wider community than you might otherwise reach.

If there's an application that you'd like to tap into that doesn't have any published information, try contacting the developer. It's often in their best interest to encourage third parties to use their APIs, and if they already have an API sitting around, it might be simple to get you the documentation for it.

Summary

It's really just this simple. Now when any of us is in a new city or just around the neighborhood its easy to check which place is the new hot spot and immediately grab an available table. Its great to not need to find a restaurant in one application, launch OpenTable to see if there's a table, find out there isn't, launch the first application again, and on and on. We hope you'll find this write-up useful as you develop your own public intents and that you'll consider sharing them with the greater Android community.

Friday, October 23, 2009

UI framework changes in Android 1.6

Android 1.6 introduces numerous enhancements and bug fixes in the UI framework. Today, I'd like to highlight three two improvements in particular.

Optimized drawing

The UI toolkit introduced in Android 1.6 is aware of which views are opaque and can use this information to avoid drawing views that the user will not be able to see. Before Android 1.6, the UI toolkit would sometimes perform unnecessary operations by drawing a window background when it was obscured by a full-screen opaque view. A workaround was available to avoid this, but the technique was limited and required work on your part. With Android 1.6, the UI toolkit determines whether a view is opaque by simply querying the opacity of the background drawable. If you know that your view is going to be opaque but that information does not depend on the background drawable, you can simply override the method called isOpaque():

@Override
public boolean isOpaque() {
return true;
}

The value returned by isOpaque() does not have to be constant and can change at any time. For instance, the implementation of ListView in Android 1.6 indicates that a list is opaque only when the user is scrolling it.

Updated: Our apologies—we spoke to soon about isOpaque(). It will be available in a future update to the Android platform.

More flexible, more robust RelativeLayout

RelativeLayout is the most versatile layout offered by the Android UI toolkit and can be successfully used to reduce the number of views created by your applications. This layout used to suffer from various bugs and limitations, sometimes making it difficult to use without having some knowledge of its implementation. To make your life easier, Android 1.6 comes with a revamped RelativeLayout. This new implementation not only fixes all known bugs in RelativeLayout (let us know when you find new ones) but also addresses its major limitation: the fact that views had to be declared in a particular order. Consider the following XML layout:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="64dip"
android:padding="6dip">

<TextView
android:id="@+id/band"
android:layout_width="fill_parent"
android:layout_height="26dip"

android:layout_below="@+id/track"
android:layout_alignLeft="@id/track"
android:layout_alignParentBottom="true"

android:gravity="top"
android:text="The Airborne Toxic Event" />

<TextView
android:id="@id/track"
android:layout_marginLeft="6dip"
android:layout_width="fill_parent"
android:layout_height="26dip"

android:layout_toRightOf="@+id/artwork"

android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="bottom"
android:text="Sometime Around Midnight" />

<ImageView
android:id="@id/artwork"
android:layout_width="56dip"
android:layout_height="56dip"
android:layout_gravity="center_vertical"

android:src="@drawable/artwork" />

</RelativeLayout>

This code builds a very simple layout—an image on the left with two lines of text stacked vertically. This XML layout is perfectly fine and contains no errors. Unfortunately, Android 1.5's RelativeLayout is incapable of rendering it correctly, as shown in the screenshot below.

The problem is that this layout uses forward references. For instance, the "band" TextView is positioned below the "track" TextView but "track" is declared after "band" and, in Android 1.5, RelativeLayout does not know how to handle this case. Now look at the exact same layout running on Android 1.6:

As you can see Android 1.6 is now better able to handle forward reference. The result on screen is exactly what you would expect when writing the layout.

Easier click listeners

Setting up a click listener on a button is very common task, but it requires quite a bit of boilerplate code:

findViewById(R.id.myButton).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Do stuff
}
});

One way to reduce the amount of boilerplate is to share a single click listener between several buttons. While this technique reduces the number of classes, it still requires a fair amount of code and it still requires giving each button an id in your XML layout file:

View.OnClickListener handler = View.OnClickListener() {
public void onClick(View v) {
switch (v.getId()) {
case R.id.myButton: // doStuff
break;
case R.id.myOtherButton: // doStuff
break;
}
}
}

findViewById(R.id.myButton).setOnClickListener(handler);
findViewById(R.id.myOtherButton).setOnClickListener(handler);

With Android 1.6, none of this is necessary. All you have to do is declare a public method in your Activity to handle the click (the method must have one View argument):

class MyActivity extends Activity {
public void myClickHandler(View target) {
// Do stuff
}
}

And then reference this method from your XML layout:

<Button android:onClick="myClickHandler" />

This new feature reduces both the amount of Java and XML you have to write, leaving you more time to concentrate on your application.

The Android team is committed to helping you write applications in the easiest and most efficient way possible. We hope you find these improvements useful and we're excited to see your applications on Android Market.

Thursday, October 8, 2009

Support for additional screen resolutions and densities in Android

You may have heard that one of the key changes introduced in Android 1.6 is support for new screen sizes. This is one of the things that has me very excited about Android 1.6 since it means Android will start becoming available on so many more devices. However, as a developer, I know this also means a bit of additional work. That's why we've spent quite a bit of time making it as easy as possible for you to update your apps to work on these new screen sizes.

To date, all Android devices (such as the T-Mobile G1 and Samsung I7500, among others) have had HVGA (320x480) screens. The essential change in Android 1.6 is that we've expanded support to include three different classes of screen sizes:

  • small: devices with a screen size smaller than the T-Mobile G1 or Samsung I7500, for example the recently announced HTC Tattoo
  • normal: devices with a screen size roughly the same as the G1 or I7500.
  • large: devices with a screen size larger than the G1 or I7500 (such as a tablet-style device.)

Any given device will fall into one of those three groups. As a developer, you can control if and how your app appears to devices in each group by using a few tools we've introduced in the Android framework APIs and SDK. The documentation at the developer site describes each of these tools in detail, but here they are in a nutshell:

  • new attributes in AndroidManifest for an application to specify what kind of screens it supports,
  • framework-level support for using image drawables/layouts correctly regardless of screen size,
  • a compatibility mode for existing applications, providing a pseudo-HVGA environment, and descriptions of compatible device resolutions and minimum diagonal sizes.

The documentation also provides a quick checklist and testing tips for developers to ensure their apps will run correctly on devices of any screen size.

Once you've upgraded your app using Android 1.6 SDK, you'll need to make sure your app is only available to users whose phones can properly run it. To help you with that, we've also added some new tools to Android Market.

Until the next time you upload a new version of your app to Android Market, we will assume that it works for normal-class screen sizes. This means users with normal-class and large-class screens will have access to these apps. Devices with "large" screens simply run these apps in a compatibility mode, which simulates an HVGA environment on the larger screen.

Devices with small-class screens, however, will only be shown apps which explicitly declare (via the AndroidManifest) that they will run properly on small screens. In our studies, we found that "squeezing" an app designed for a larger screen onto a smaller screen often produces a bad result. To prevent users with small screens from getting a bad impression of your app (and reviewing it negatively!), Android Market makes sure that they can't see it until you upload a new version that declares itself compatible.

We expect small-class screens, as well as devices with additional resolutions in Table 1 in the developer document to hit the market in time for the holiday season. Note that not all devices will be upgraded to Android 1.6 at the same time. There will be significant number of users still with Android 1.5 devices. To use the same apk to target Android 1.5 devices and Android 1.6 devices, build your apps using Android 1.5 SDK and test your apps on both Android 1.5 and 1.6 system images to make sure they continue to work well on both types of devices. If you want to target small-class devices like HTC Tattoo, please build your app using the Android 1.6 SDK. Note that if your application requires Android 1.6 features, but does not support a screen class, you need to set the appropriate attributes to false. To use optimized assets for normal-class, high density devices like WVGA, or for low density devices please use the Android 1.6 SDK.

Monday, October 5, 2009

Gestures on Android 1.6

Touch screens are a great way to interact with applications on mobile devices. With a touch screen, users can easily tap, drag, fling, or slide to quickly perform actions in their favorite applications. But it's not always that easy for developers. With Android, it's easy to recognize simple actions, like a swipe, but it's much more difficult to handle complicated gestures, which also require developers to write a lot of code. That's why we have decided to introduce a new gestures API in Android 1.6. This API, located in the new package android.gesture, lets you store, load, draw and recognize gestures. In this post I will show you how you can use the android.gesture API in your applications. Before going any further, you should download the source code of the examples.

Creating a gestures library

The Android 1.6 SDK comes with a new application pre-installed on the emulator, called Gestures Builder. You can use this application to create a set of pre-defined gestures for your own application. It also serves as an example of how to let the user define his own gestures in your applications. You can find the source code of Gestures Builders in the samples directory of Android 1.6. In our example we will use Gestures Builder to generate a set of gestures for us (make sure to create an AVD with an SD card image to use Gestures Builder.) The screenshot below shows what the application looks like after adding a few gestures:

As you can see, a gesture is always associated with a name. That name is very important because it identifies each gesture within your application. The names do not have to be unique. Actually it can be very useful to have several gestures with the same name to increase the precision of the recognition. Every time you add or edit a gesture in the Gestures Builder, a file is generated on the emulator's SD card, /sdcard/gestures. This file contains the description of all the gestures, and you will need to package it inside your application inside the resources directory, in /res/raw.

Loading the gestures library

Now that you have a set of pre-defined gestures, you must load it inside your application. This can be achieved in several ways but the easiest is to use the GestureLibraries class:

mLibrary = GestureLibraries.fromRawResource(this, R.raw.spells);
if (!mLibrary.load()) {
finish();
}

In this example, the gesture library is loaded from the file /res/raw/spells. You can easily load libraries from other sources, like the SD card, which is very important if you want your application to be able to save the library; a library loaded from a raw resource is read-only and cannot be modified. The following diagram shows the structure of a library:

Recognizing gestures

To start recognizing gestures in your application, all you have to do is add a GestureOverlayView to your XML layout:

<android.gesture.GestureOverlayView
android:id="@+id/gestures"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1.0" />

Notice that the GestureOverlayView is not part of the usual android.widget package. Therefore, you must use its fully qualified name. A gesture overlay acts as a simple drawing board on which the user can draw his gestures. You can tweak several visual properties, like the color and the width of the stroke used to draw gestures, and register various listeners to follow what the user is doing. The most commonly used listener is GestureOverlayView.OnGesturePerformedListener which fires whenever a user is done drawing a gesture:

GestureOverlayView gestures = (GestureOverlayView) findViewById(R.id.gestures);
gestures.addOnGesturePerformedListener(this);

When the listener fires, you can ask the GestureLibrary to try to recognize the gesture. In return, you will get a list of Prediction instances, each with a name - the same name you entered in the Gestures Builder - and a score. The list is sorted by descending scores; the higher the score, the more likely the associated gesture is the one the user intended to draw. The following code snippet demonstrates how to retrieve the name of the first prediction:

public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
ArrayList predictions = mLibrary.recognize(gesture);

// We want at least one prediction
if (predictions.size() > 0) {
Prediction prediction = predictions.get(0);
// We want at least some confidence in the result
if (prediction.score > 1.0) {
// Show the spell
Toast.makeText(this, prediction.name, Toast.LENGTH_SHORT).show();
}
}
}

In this example, the first prediction is taken into account only if it's score is greater than 1.0. The threshold you use is entirely up to you but know that scores lower than 1.0 are typically poor matches. And this is all the code you need to create a simple application that can recognize pre-defined gestures (see the source code of the project GesturesDemo):

Gestures overlay

In the example above, the GestureOverlayView was used as a normal view, embedded inside a LinearLayout. However, as its name suggests, it can also be used as an overlay on top of other views. This can be useful to recognize gestures in a game or just anywhere in the UI of an application. In the second example, called GesturesListDemo, we'll create an overlay on top of a list of contacts. We start again in Gestures Builder to create a new set of pre-defined gestures:

And here is what the XML layout looks like:

<android.gesture.GestureOverlayView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/gestures"
android:layout_width="fill_parent"
android:layout_height="fill_parent"

android:gestureStrokeType="multiple"
android:eventsInterceptionEnabled="true"
android:orientation="vertical">

<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />

</android.gesture.GestureOverlayView>

In this application, the gestures view is an overlay on top of a regular ListView. The overlay also specifies a few properties that we did not need before:

  • gestureStrokeType: indicates whether we want to recognize gestures made of a single stroke or multiple strokes. Since one of our gestures is the "+" symbol, we need multiple strokes
  • eventsInterceptionEnabled: when set to true, this property tells the overlay to steal the events from its children as soon as it knows the user is really drawing a gesture. This is useful when there's a scrollable view under the overlay, to avoid scrolling the underlying child as the user draws his gesture
  • orientation: indicates the scroll orientation of the views underneath. In this case the list scrolls vertically, which means that any horizontal gestures (like action_delete) can immediately be recognized as a gesture. Gestures that start with a vertical stroke must contain at least one horizontal component to be recognized. In other words, a simple vertical line cannot be recognized as a gesture since it would conflict with the list's scrolling.

The code used to load and set up the gestures library and overlay is exactly the same as before. The only difference is that we now check the name of the predictions to know what the user intended to do:

public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
ArrayList<Prediction> predictions = mLibrary.recognize(gesture);
if (predictions.size() > 0 && predictions.get(0).score > 1.0) {
String action = predictions.get(0).name;
if ("action_add".equals(action)) {
Toast.makeText(this, "Adding a contact", Toast.LENGTH_SHORT).show();
} else if ("action_delete".equals(action)) {
Toast.makeText(this, "Removing a contact", Toast.LENGTH_SHORT).show();
} else if ("action_refresh".equals(action)) {
Toast.makeText(this, "Reloading contacts", Toast.LENGTH_SHORT).show();
}
}
}

The user is now able to draw his gestures on top of the list without interfering with the scrolling:

The overlay even gives visual clues as to whether the gesture is considered valid for recognition. In the case of a vertical overlay, for instance, a single vertical stroke cannot be recognized as a gesture and is therefore drawn with a translucent color:

It's your turn

Adding support for gestures in your application is easy and can be a valuable addition. The gestures API does not even have to be used to recognize complex shapes; it will work equally well to recognize simple swipes. We are very excited by the possibilities the gestures API offers, and we're eager to see what cool applications the community will create with it.

Monday, September 28, 2009

Zipalign: an easy optimization

The Android 1.6 SDK includes a tool called zipalign that optimizes the way an application is packaged. Doing this enables Android to interact with your application more efficiently and thus has the potential to make your application and the overall system run faster. We strongly encourage you to use zipalign on both new and already published applications and to make the optimized version available�even if your application targets a previous version of Android. We'll get into more detail on what zipalign does, how to use it, and why you'll want to do so in the rest of this post.

In Android, data files stored in each application's apk are accessed by multiple processes: the installer reads the manifest to handle the permissions associated with that application; the Home application reads resources to get the application's name and icon; the system server reads resources for a variety of reasons (e.g. to display that application's notifications); and last but not least, the resource files are obviously used by the application itself.

The resource-handling code in Android can efficiently access resources when they're aligned on 4-byte boundaries by memory-mapping them. But for resources that are not aligned (i.e. when zipalign hasn't been run on an apk), it has to fall back to explicitly reading them�which is slower and consumes additional memory.

For an application developer like you, this fallback mechanism is very convenient. It provides a lot of flexibility by allowing for several different development methods, including those that don't include aligning resources as part of their normal flow.

Unfortunately, the situation is reversed for users�reading resources from unaligned apks is slow and takes a lot of memory. In the best case, the only visible result is that both the Home application and the unaligned application launch slower than they otherwise should. In the worst case, installing several applications with unaligned resources increases memory pressure, thus causing the system to thrash around by having to constantly start and kill processes. The user ends up with a slow device with a poor battery life.

Luckily, it's very easy to align the resources:

  • Using ADT:
    • ADT (starting with 0.9.3) will automatically align release application packages if the export wizard is used to create them. To use the wizard, right click the project and choose "Android Tools" > "Export Signed Application Package..." It can also be accessed from the first page of the AndroidManifest.xml editor.
  • Using Ant:
    • The Ant build script that targets Android 1.6 (API level 4) can align application packages. Targets for older versions of the Android platform are not aligned by the Ant build script and need to be manually aligned.
    • Debug packages built with Ant for Android 1.6 applications are aligned and signed by default.
    • Release packages are aligned automatically only if Ant has enough information to sign the packages, since aligning has to happen after signing. In order to be able to sign packages, and therefore to align them, Ant needs to know the location of the keystore and the name of the key in build.properties. The name of the properties are key.store and key.alias respectively. If those properties are present, the signing tool will prompt to enter the store/key passwords during the build, and the script will sign and then align the apk file. If the properties are missing, the release package will not be signed, and therefore will not get aligned either.
  • Manually:
    • In order to manually align a package, zipalign is in the tools folder of the Android 1.6 SDK. It can be used on application packages targeting any version of Android. It should be run after signing the apk file, using the following command:
      zipalign -v 4 source.apk destination.apk
  • Verifying alignment:
    • The following command verifies that a package is aligned:
      zipalign -c -v 4 application.apk

We encourage you manually run zipalign on your currently published applications and to make the newly aligned versions available to users. And don't forget to align any new applications going forward!

Wednesday, September 23, 2009

An introduction to Text-To-Speech in Android

We've introduced a new feature in version 1.6 of the Android platform: Text-To-Speech (TTS). Also known as "speech synthesis", TTS enables your Android device to "speak" text of different languages.

Before we explain how to use the TTS API itself, let's first review a few aspects of the engine that will be important to your TTS-enabled application. We will then show how to make your Android application talk and how to configure the way it speaks.

Languages and resources

About the TTS resources

The TTS engine that ships with the Android platform supports a number of languages: English, French, German, Italian and Spanish. Also, depending on which side of the Atlantic you are on, American and British accents for English are both supported.

The TTS engine needs to know which language to speak, as a word like "Paris", for example, is pronounced differently in French and English. So the voice and dictionary are language-specific resources that need to be loaded before the engine can start to speak.

Although all Android-powered devices that support the TTS functionality ship with the engine, some devices have limited storage and may lack the language-specific resource files. If a user wants to install those resources, the TTS API enables an application to query the platform for the availability of language files and can initiate their download and installation. So upon creating your activity, a good first step is to check for the presence of the TTS resources with the corresponding intent:

Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, MY_DATA_CHECK_CODE);

A successful check will be marked by a CHECK_VOICE_DATA_PASS result code, indicating this device is ready to speak, after the creation of our android.speech.tts.TextToSpeech object. If not, we need to let the user know to install the data that's required for the device to become a multi-lingual talking machine! Downloading and installing the data is accomplished by firing off the ACTION_INSTALL_TTS_DATA intent, which will take the user to Android Market, and will let her/him initiate the download. Installation of the data will happen automatically once the download completes. Here is an example of what your implementation of onActivityResult() would look like:

private TextToSpeech mTts;
protected void onActivityResult(
int requestCode, int resultCode, Intent data) {
if (requestCode == MY_DATA_CHECK_CODE) {
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
// success, create the TTS instance
mTts = new TextToSpeech(this, this);
} else {
// missing data, install it
Intent installIntent = new Intent();
installIntent.setAction(
TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
}
}
}

In the constructor of the TextToSpeech instance we pass a reference to the Context to be used (here the current Activity), and to an OnInitListener (here our Activity as well). This listener enables our application to be notified when the Text-To-Speech engine is fully loaded, so we can start configuring it and using it.

Languages and Locale

At Google I/O, we showed an example of TTS where it was used to speak the result of a translation from and to one of the 5 languages the Android TTS engine currently supports. Loading a language is as simple as calling for instance:

mTts.setLanguage(Locale.US);

to load and set the language to English, as spoken in the country "US". A locale is the preferred way to specify a language because it accounts for the fact that the same language can vary from one country to another. To query whether a specific Locale is supported, you can use isLanguageAvailable(), which returns the level of support for the given Locale. For instance the calls:

mTts.isLanguageAvailable(Locale.UK))
mTts.isLanguageAvailable(Locale.FRANCE))
mTts.isLanguageAvailable(new Locale("spa", "ESP")))

will return TextToSpeech.LANG_COUNTRY_AVAILABLE to indicate that the language AND country as described by the Locale parameter are supported (and the data is correctly installed). But the calls:

mTts.isLanguageAvailable(Locale.CANADA_FRENCH))
mTts.isLanguageAvailable(new Locale("spa"))

will return TextToSpeech.LANG_AVAILABLE. In the first example, French is supported, but not the given country. And in the second, only the language was specified for the Locale, so that's what the match was made on.

Also note that besides the ACTION_CHECK_TTS_DATA intent to check the availability of the TTS data, you can also use isLanguageAvailable() once you have created your TextToSpeech instance, which will return TextToSpeech.LANG_MISSING_DATA if the required resources are not installed for the queried language.

Making the engine speak an Italian string while the engine is set to the French language will produce some pretty interesting results, but it will not exactly be something your user would understand So try to match the language of your application's content and the language that you loaded in your TextToSpeech instance. Also if you are using Locale.getDefault() to query the current Locale, make sure that at least the default language is supported.

Making your application speak

Now that our TextToSpeech instance is properly initialized and configured, we can start to make your application speak. The simplest way to do so is to use the speak() method. Let's iterate on the following example to make a talking alarm clock:

String myText1 = "Did you sleep well?";
String myText2 = "I hope so, because it's time to wake up.";
mTts.speak(myText1, TextToSpeech.QUEUE_FLUSH, null);
mTts.speak(myText2, TextToSpeech.QUEUE_ADD, null);

The TTS engine manages a global queue of all the entries to synthesize, which are also known as "utterances". Each TextToSpeech instance can manage its own queue in order to control which utterance will interrupt the current one and which one is simply queued. Here the first speak() request would interrupt whatever was currently being synthesized: the queue is flushed and the new utterance is queued, which places it at the head of the queue. The second utterance is queued and will be played after myText1 has completed.

Using optional parameters to change the playback stream type

On Android, each audio stream that is played is associated with one stream type, as defined in android.media.AudioManager. For a talking alarm clock, we would like our text to be played on the AudioManager.STREAM_ALARM stream type so that it respects the alarm settings the user has chosen on the device. The last parameter of the speak() method allows you to pass to the TTS engine optional parameters, specified as key/value pairs in a HashMap. Let's use that mechanism to change the stream type of our utterances:

HashMap<String, String> myHashAlarm = new HashMap();
myHashAlarm.put(TextToSpeech.Engine.KEY_PARAM_STREAM,
String.valueOf(AudioManager.STREAM_ALARM));
mTts.speak(myText1, TextToSpeech.QUEUE_FLUSH, myHashAlarm);
mTts.speak(myText2, TextToSpeech.QUEUE_ADD, myHashAlarm);

Using optional parameters for playback completion callbacks

Note that speak() calls are asynchronous, so they will return well before the text is done being synthesized and played by Android, regardless of the use of QUEUE_FLUSH or QUEUE_ADD. But you might need to know when a particular utterance is done playing. For instance you might want to start playing an annoying music after myText2 has finished synthesizing (remember, we're trying to wake up the user). We will again use an optional parameter, this time to tag our utterance as one we want to identify. We also need to make sure our activity implements the TextToSpeech.OnUtteranceCompletedListener interface:

mTts.setOnUtteranceCompletedListener(this);
myHashAlarm.put(TextToSpeech.Engine.KEY_PARAM_STREAM,
String.valueOf(AudioManager.STREAM_ALARM));
mTts.speak(myText1, TextToSpeech.QUEUE_FLUSH, myHashAlarm);
myHashAlarm.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,
"end of wakeup message ID");
// myHashAlarm now contains two optional parameters
mTts.speak(myText2, TextToSpeech.QUEUE_ADD, myHashAlarm);

And the Activity gets notified of the completion in the implementation of the listener:

public void onUtteranceCompleted(String uttId) {
if (uttId == "end of wakeup message ID") {
playAnnoyingMusic();
}
}

File rendering and playback

While the speak() method is used to make Android speak the text right away, there are cases where you would want the result of the synthesis to be recorded in an audio file instead. This would be the case if, for instance, there is text your application will speak often; you could avoid the synthesis CPU-overhead by rendering only once to a file, and then playing back that audio file whenever needed. Just like for speak(), you can use an optional utterance identifier to be notified on the completion of the synthesis to the file:

HashMap<String, String> myHashRender = new HashMap();
String wakeUpText = "Are you up yet?";
String destFileName = "/sdcard/myAppCache/wakeUp.wav";
myHashRender.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, wakeUpText);
mTts.synthesizeToFile(wakuUpText, myHashRender, destFileName);

Once you are notified of the synthesis completion, you can play the output file just like any other audio resource with android.media.MediaPlayer.

But the TextToSpeech class offers other ways of associating audio resources with speech. So at this point we have a WAV file that contains the result of the synthesis of "Wake up" in the previously selected language. We can tell our TTS instance to associate the contents of the string "Wake up" with an audio resource, which can be accessed through its path, or through the package it's in, and its resource ID, using one of the two addSpeech() methods:

mTts.addSpeech(wakeUpText, destFileName);

This way any call to speak() for the same string content as wakeUpText will result in the playback of destFileName. If the file is missing, then speak will behave as if the audio file wasn't there, and will synthesize and play the given string. But you can also take advantage of that feature to provide an option to the user to customize how "Wake up" sounds, by recording their own version if they choose to. Regardless of where that audio file comes from, you can still use the same line in your Activity code to ask repeatedly "Are you up yet?":

mTts.speak(wakeUpText, TextToSpeech.QUEUE_ADD, myHashAlarm);

When not in use...

The text-to-speech functionality relies on a dedicated service shared across all applications that use that feature. When you are done using TTS, be a good citizen and tell it "you won't be needing its services anymore" by calling mTts.shutdown(), in your Activity onDestroy() method for instance.

Conclusion

Android now talks, and so can your apps. Remember that in order for synthesized speech to be intelligible, you need to match the language you select to that of the text to synthesize. Text-to-speech can help you push your app in new directions. Whether you use TTS to help users with disabilities, to enable the use of your application while looking away from the screen, or simply to make it cool, we hope you'll enjoy this new feature.

Thursday, September 17, 2009

Introducing Quick Search Box for Android

One of the new features we're really proud of in the Android 1.6 release is Quick Search Box for Android. This is our new system-wide search framework, which makes it possible for users to quickly and easily find what they're looking for, both on their devices and on the web. It suggests content on your device as you type, like apps, contacts, browser history, and music. It also offers results from the web search suggestions, local business listings, and other info from Google, such as stock quotes, weather, and flight status. All of this is available right from the home screen, by tapping on Quick Search Box (QSB).

What we're most excited about with this new feature is the ability for you, the developers, to leverage the QSB framework to provide quicker and easier access to the content inside your apps. Your apps can provide search suggestions that will surface to users in QSB alongside other search results and suggestions. This makes it possible for users to access your application's content from outside your application�for example, from the home screen.

The code fragments below are related to a new demo app for Android 1.6 called Searchable Dictionary.


The story before now: searching within your app

In previous releases, we already provided a mechanism for you to expose search and search suggestions in your app as described in the docs for SearchManager. This mechanism has not changed and requires the following two things in your AndroidManifest.xml:

1) In your <activity>, an intent filter, and a reference to a searchable.xml file (described below):

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

<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable" />

2) A content provider that can provide search suggestions according to the URIs and column formats specified by the Search Suggestions section of the SearchManager docs:

<!-- Provides search suggestions for words and their definitions. -->
<provider android:name="DictionaryProvider"
android:authorities="dictionary"
android:syncable="false" />

In the searchable.xml file, you specify a few things about how you want the search system to present search for your app, including the authority of the content provider that provides suggestions for the user as they type. Here's an example of the searchable.xml of an Android app that provides search suggestions within its own activities:

<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/search_label"
android:searchSuggestAuthority="dictionary"
android:searchSuggestIntentAction="android.intent.action.VIEW">
</searchable>

Note that the android:searchSuggestAuthority attribute refers to the authority of the content provider we declared in AndroidManifest.xml.

For more details on this, see the Searchability Metadata section of the SearchManager docs.

Including your app in Quick Search Box

In Android 1.6, we added a new attribute to the metadata for searchables: android:includeInGlobalSearch. By specifying this as "true" in your searchable.xml, you allow QSB to pick up your search suggestion content provider and include its suggestions along with the rest (if the user enables your suggestions from the system search settings).

You should also specify a string value for android:searchSettingsDescription, which describes to users what sorts of suggestions your app provides in the system settings for search.

<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/search_label"
android:searchSettingsDescription="@string/settings_description"
android:includeInGlobalSearch="true"
android:searchSuggestAuthority="dictionary"
android:searchSuggestIntentAction="android.intent.action.VIEW">
</searchable>

These new attributes are supported only in Android 1.6 and later.

What to expect

The first and most important thing to note is that when a user installs an app with a suggestion provider that participates in QSB, this new app will not be enabled for QSB by default. The user can choose to enable particular suggestion sources from the system settings for search (by going to "Search" > "Searchable items" in settings).

You should consider how to handle this in your app. Perhaps show a notice that instructs the user to visit system settings and enable your app's suggestions.

Once the user enables your searchable item, the app's suggestions will have a chance to show up in QSB, most likely under the "more results" section to begin with. As your app's suggestions are chosen more frequently, they can move up in the list.

Shortcuts

One of our objectives with QSB is to make it faster for users to access the things they access most often. One way we've done this is by 'shortcutting' some of the previously chosen search suggestions, so they will be shown immediately as the user starts typing, instead of waiting to query the content providers. Suggestions from your app may be chosen as shortcuts when the user clicks on them.

For dynamic suggestions that may wish to change their content (or become invalid) in the future, you can provide a 'shortcut id'. This tells QSB to query your suggestion provider for up-to-date content for a suggestion after it has been displayed. For more details on how to manage shortcuts, see the Shortcuts section within the SearchManager docs.


QSB provides a really cool way to make your app's content quicker to access by users. To help you get your app started with it, we've created a demo app which simply provides access to a small dictionary of words in QSB—it's called Searchable Dictionary, and we encourage you to check it out.

Wednesday, May 6, 2009

Painless threading

Whenever you first start an Android application, a thread called "main" is automatically created. The main thread, also called the UI thread, is very important because it is in charge of dispatching the events to the appropriate widgets and this includes the drawing events. It is also the thread you interact with Android widgets on. For instance, if you touch the a button on screen, the UI thread dispatches the touch event to the widget which in turn sets its pressed state and posts an invalidate request to the event queue. The UI thread dequeues the request and notifies the widget to redraw itself.

This single thread model can yield poor performance in Android applications that do not consider the implications. Since everything happens on a single thread performing long operations, like network access or database queries, on this thread will block the whole user interface. No event can be dispatched, including drawing events, while the long operation is underway. From the user's perspective, the application appears hung. Even worse, if the UI thread is blocked for more than a few seconds (about 5 seconds currently) the user is presented with the infamous "application not responding" (ANR) dialog.

If you want to see how bad this can look, write a simple application with a button that invokes Thread.sleep(2000) in its OnClickListener. The button will remain in its pressed state for about 2 seconds before going back to its normal state. When this happens, it is very easy for the user to perceive the application as slow.

Now that you know you must avoid lengthy operations on the UI thread, you will probably use extra threads (background or worker threads) to perform these operations, and rightly so. Let's take the example of a click listener downloading an image over the network and displaying it in an ImageView:

public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork();
mImageView.setImageBitmap(b);
}
}).start();
}

At first, this code seems to be a good solution to your problem, as it does not block the UI thread. Unfortunately, it violates the single thread model: the Android UI toolkit is not thread-safe and must always be manipulated on the UI thread. In this piece of code, the ImageView is manipulated on a worker thread, which can cause really weird problems. Tracking down and fixing such bugs can be difficult and time-consuming.

Android offers several ways to access the UI thread from other threads. You may already be familiar with some of them but here is a comprehensive list:

Any of these classes and methods could be used to correct our previous code example:

public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap b = loadImageFromNetwork();
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(b);
}
});
}
}).start();
}

Unfortunately, these classes and methods also tend to make your code more complicated and more difficult to read. It becomes even worse when your implement complex operations that require frequent UI updates. To remedy this problem, Android 1.5 offers a new utility class, called AsyncTask, that simplifies the creation of long-running tasks that need to communicate with the user interface.

AsyncTask is also available for Android 1.0 and 1.1 under the name UserTask. It offers the exact same API and all you have to do is copy its source code in your application.

The goal of AsyncTask is to take care of thread management for you. Our previous example can easily be rewritten with AsyncTask:

public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask {
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}

protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}

As you can see, AsyncTask must be used by subclassing it. It is also very important to remember that an AsyncTask instance has to be created on the UI thread and can be executed only once. You can read the AsyncTask documentation for a full understanding on how to use this class, but here is a quick overview of how it works:

In addition to the official documentation, you can read several complex examples in the source code of Shelves (ShelvesActivity.java and AddBookActivity.java) and Photostream (LoginActivity.java, PhotostreamActivity.java and ViewPhotoActivity.java). I highly recommend reading the source code of Shelves to see how to persist tasks across configuration changes and how to cancel them properly when the activity is destroyed.

Regardless of whether or not you use AsyncTask, always remember these two rules about the single thread model: do not block the UI thread and make sure the Android UI toolkit is only accessed on the UI thread. AsyncTask just makes it easier to do both of these things.

If you want to learn more cool techniques, come join us at Google I/O. Members of the Android team will be there to give a series of in-depth technical sessions and answer all your questions.

Monday, May 4, 2009

Drawable mutations

Android's drawables are extremely useful to easily build applications. A Drawable is a pluggable drawing container that is usually associated with a View. For instance, a BitmapDrawable is used to display images, a ShapeDrawable to draw shapes and gradients, etc. You can even combine them to create complex renderings.

Drawables allow you to easily customize the rendering of the widgets without subclassing them. As a matter of fact, they are so convenient that most of the default Android apps and widgets are built using drawables; there are about 700 drawables used in the core Android framework. Because drawables are used so extensively throughout the system, Android optimizes them when they are loaded from resources. For instance, every time you create a Button, a new drawable is loaded from the framework resources (android.R.drawable.btn_default). This means all buttons across all the apps use a different drawable instance as their background. However, all these drawables share a common state, called the "constant state." The content of this state varies according to the type of drawable you are using, but it usually contains all the properties that can be defined by a resource. In the case of a button, the constant state contains a bitmap image. This way, all buttons across all applications share the same bitmap, which saves a lot of memory.

The following diagram shows what entities are created when you assign the same image resource as the background of two different views. As you can see, two drawables are created but they both share the same constant state, hence the same bitmap:

This state sharing feature is great to avoid wasting memory but it can cause problems when you try to modify the properties of a drawable. Imagine an application with a list of books. Each book has a star next to its name, totally opaque when the user marks the book as a favorite, and translucent when the book is not a favorite. To achieve this effect, you would probably write the following code in your list adapter's getView() method:

Book book = ...;
TextView listItem = ...;

listItem.setText(book.getTitle());

Drawable star = context.getResources().getDrawable(R.drawable.star);
if (book.isFavorite()) {
star.setAlpha(255); // opaque
} else {
star.setAlpha(70); // translucent
}

Unfortunately, this piece of code yields a rather strange result, all the drawables have the same opacity:

This result is explained by the constant state. Even though we are getting a new drawable instance for each list item, the constant state remains the same and, in the case of BitmapDrawable, the opacity is part of the constant state. Thus, changing the opacity of one drawable instance changes the opacity of all the other instances. Even worse, working around this issue was not easy with Android 1.0 and 1.1.

Android 1.5 offers a very way to solve this issue with a the new mutate() method. When you invoke this method on a drawable, the constant state of the drawable is duplicated to allow you to change any property without affecting other drawables. Note that bitmaps are still shared, even after mutating a drawable. The diagram below shows what happens when you invoke mutate() on a drawable:

Let's update our previous piece of code to make use of mutate():

Drawable star = context.getResources().getDrawable(R.drawable.star);
if (book.isFavorite()) {
star.mutate().setAlpha(255); // opaque
} else {
star. mutate().setAlpha(70); // translucent
}

For convenience, mutate() returns the drawable itself, which allows to chain method calls. It does not however create a new drawable instance. With this new piece of code, our application now behaves correctly:

If you want to learn more cool techniques, come join us at Google I/O. Members of the Android team will be there to give a series of in-depth technical sessions and answer all your questions.

Tuesday, April 28, 2009

Backward compatibility for Android applications

Android 1.5 introduced a number of new features that application developers can take advantage of, like virtual input devices and speech recognition. As a developer, you need to be aware of backward compatibility issues on older devices�do you want to allow your application to run on all devices, or just those running newer software? In some cases it will be useful to employ the newer APIs on devices that support them, while continuing to support older devices.

If the use of a new API is integral to the program�perhaps you need to record video�you should add a manifest entry to ensure your app won't be installed on older devices. For example, if you require APIs added in 1.5, you would specify 3 as the minimum SDK version:

  <manifest>
...
<uses-sdk android:minSdkVersion="3" />
...
</manifest>

If you want to add a useful but non-essential feature, such as popping up an on-screen keyboard even when a hardware keyboard is available, you can write your program in a way that allows it to use the newer features without failing on older devices.

Using reflection

Suppose there's a simple new call you want to use, like android.os.Debug.dumpHprofData(String filename). The android.os.Debug class has existed since the first SDK, but the method is new in 1.5. If you try to call it directly, your app will fail to run on older devices.

The simplest way to call the method is through reflection. This requires doing a one-time lookup and caching the result in a Method object. Using the method is a matter of calling Method.invoke and un-boxing the result. Consider the following:

public class Reflect {
private static Method mDebug_dumpHprofData;

static {
initCompatibility();
};

private static void initCompatibility() {
try {
mDebug_dumpHprofData = Debug.class.getMethod(
"dumpHprofData", new Class[] { String.class } );
/* success, this is a newer device */
} catch (NoSuchMethodException nsme) {
/* failure, must be older device */
}
}

private static void dumpHprofData(String fileName) throws IOException {
try {
mDebug_dumpHprofData.invoke(null, fileName);
} catch (InvocationTargetException ite) {
/* unpack original exception when possible */
Throwable cause = ite.getCause();
if (cause instanceof IOException) {
throw (IOException) cause;
} else if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
/* unexpected checked exception; wrap and re-throw */
throw new RuntimeException(ite);
}
} catch (IllegalAccessException ie) {
System.err.println("unexpected " + ie);
}
}

public void fiddle() {
if (mDebug_dumpHprofData != null) {
/* feature is supported */
try {
dumpHprofData("/sdcard/dump.hprof");
} catch (IOException ie) {
System.err.println("dump failed!");
}
} else {
/* feature not supported, do something else */
System.out.println("dump not supported");
}
}
}

This uses a static initializer to call initCompatibility, which does the method lookup. If that succeeds, it uses a private method with the same semantics as the original (arguments, return value, checked exceptions) to do the call. The return value (if it had one) and exception are unpacked and returned in a way that mimics the original. The fiddle method demonstrates how the application logic would choose to call the new API or do something different based on the presence of the new method.

For each additional method you want to call, you would add an additional private Method field, field initializer, and call wrapper to the class.

This approach becomes a bit more complex when the method is declared in a previously undefined class. It's also much slower to call Method.invoke() than it is to call the method directly. These issues can be mitigated by using a wrapper class.

Using a wrapper class

The idea is to create a class that wraps all of the new APIs exposed by a new or existing class. Each method in the wrapper class just calls through to the corresponding real method and returns the same result.

If the target class and method exist, you get the same behavior you would get by calling the class directly, with a small amount of overhead from the additional method call. If the target class or method doesn't exist, the initialization of the wrapper class fails, and your application knows that it should avoid using the newer calls.

Suppose this new class were added:

public class NewClass {
private static int mDiv = 1;

private int mMult;

public static void setGlobalDiv(int div) {
mDiv = div;
}

public NewClass(int mult) {
mMult = mult;
}

public int doStuff(int val) {
return (val * mMult) / mDiv;
}
}

We would create a wrapper class for it:

class WrapNewClass {
private NewClass mInstance;

/* class initialization fails when this throws an exception */
static {
try {
Class.forName("NewClass");
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}

/* calling here forces class initialization */
public static void checkAvailable() {}

public static void setGlobalDiv(int div) {
NewClass.setGlobalDiv(div);
}

public WrapNewClass(int mult) {
mInstance = new NewClass(mult);
}

public int doStuff(int val) {
return mInstance.doStuff(val);
}
}

This has one method for each constructor and method in the original, plus a static initializer that tests for the presence of the new class. If the new class isn't available, initialization of WrapNewClass fails, ensuring that the wrapper class can't be used inadvertently. The checkAvailable method is used as a simple way to force class initialization. We use it like this:

public class MyApp {
private static boolean mNewClassAvailable;

/* establish whether the "new" class is available to us */
static {
try {
WrapNewClass.checkAvailable();
mNewClassAvailable = true;
} catch (Throwable t) {
mNewClassAvailable = false;
}
}

public void diddle() {
if (mNewClassAvailable) {
WrapNewClass.setGlobalDiv(4);
WrapNewClass wnc = new WrapNewClass(40);
System.out.println("newer API is available - " + wnc.doStuff(10));
} else {
System.out.println("newer API not available");
}
}
}

If the call to checkAvailable succeeds, we know the new class is part of the system. If it fails, we know the class isn't there, and adjust our expectations accordingly. It should be noted that the call to checkAvailable will fail before it even starts if the bytecode verifier decides that it doesn't want to accept a class that has references to a nonexistent class. The way this code is structured, the end result is the same whether the exception comes from the verifier or from the call to Class.forName.

When wrapping an existing class that now has new methods, you only need to put the new methods in the wrapper class. Invoke the old methods directly. The static initializer in WrapNewClass would be augmented to do a one-time check with reflection.

Testing is key

You must test your application on every version of the Android framework that is expected to support it. By definition, the behavior of your application will be different on each. Remember the mantra: if you haven't tried it, it doesn't work.

You can test for backward compatibility by running your application in an emulator from an older SDK, but as of the 1.5 release there's a better way. The SDK allows you to specify "Android Virtual Devices" with different API levels. Once you create the AVDs, you can test your application with old and new versions of the system, perhaps running them side-by-side to see the differences. More information about emulator AVDs can be found in the SDK documentation and from emulator -help-virtual-device.


Learn about Android 1.5 and more at Google I/O. Members of the Android team will be there to give a series of in-depth technical sessions and to field your toughest questions.

Friday, April 24, 2009

Live folders

Live folders have been introduced in Android 1.5 and let you display any source of data on the Home screen without forcing the user to launch an application. A live folder is simply a real-time view of a ContentProvider. As such, a live folder can be used to display all your contacts, your bookmarks, your email, your playlists, an RSS feed, etc. The possibilities are endless! Android 1.5 ships with a few stock live folders to display your contacts. For instance, the screenshot below shows the content of the live folders that displays all my contacts with a phone number:

If a contacts sync happens in the background while I'm browsing this live folder, I will see the change happen in real-time. Live folders are not only useful but it's also very easy to modify your application to make it provider a live folder. In this article, I will show you how to add a live folder to the Shelves application. You can download its source code and modify it by following my instructions to better understand how live folders work.

To give the user the option to create a new live folder, you first need to create a new activity with an intent filter who action is android.intent.action.CREATE_LIVE_FOLDER. To do so, simply open AndroidManifest.xml and add something similar to this:

<activity
android:name=".activity.BookShelfLiveFolder"
android:label="BookShelf">
<intent-filter>
<action android:name="android.intent.action.CREATE_LIVE_FOLDER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

The label and icon of this activity are what the user will see on the Home screen when choosing a live folder to create:

Since you just need an intent filter, it is possible, and sometimes advised, to reuse an existing activity. In the case of Shelves, we will create a new activity, org.curiouscreature.android.shelves.activity.BookShelfLiveFolder. The role of this activity is to send an Intent result to Home containing the description of the live folder: its name, icon, display mode and content URI. The content URI is very important as it describes what ContentProvider will be used to populate the live folder. The code of the activity is very simple as you can see here:

public class BookShelfLiveFolder extends Activity {
public static final Uri CONTENT_URI = Uri.parse("content://shelves/live_folders/books");

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

final Intent intent = getIntent();
final String action = intent.getAction();

if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) {
setResult(RESULT_OK, createLiveFolder(this, CONTENT_URI,
"Books", R.drawable.ic_live_folder));
} else {
setResult(RESULT_CANCELED);
}

finish();
}

private static Intent createLiveFolder(Context context, Uri uri, String name, int icon) {
final Intent intent = new Intent();

intent.setData(uri);
intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, name);
intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON,
Intent.ShortcutIconResource.fromContext(context, icon));
intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE, LiveFolders.DISPLAY_MODE_LIST);

return intent;
}
}

This activity, when invoked with theACTION_CREATE_LIVE_FOLDER intent, returns an intent with a URI, content://shelves/live_folders/books, and three extras to describe the live folder. There are other extras and constants you can use and you should refer to the documentation of android.provider.LiveFolders for more details. When Home receives this intent, a new live folder is created on the user's desktop, with the name and icon you provided. Then, when the user clicks on the live folder to open it, Home queries the content provider referenced by the provided URI.

Live folders' content providers must obey specific naming rules. The Cursor returned by the query() method must have at least two columns named LiveFolders._ID and LiveFolders.NAME. The first one is the unique identifier of each item in the live folder and the second one is the name of the item. There are other column names you can use to specify an icon, a description, the intent to associate with the item (fired when the user clicks that item), etc. Again, refer to the documentation of android.provider.LiveFolders for more details.

In our example, all we need to do is modify the existing provider in Shelves called org.curiouscreature.android.shelves.provider.BooksProvider. First, we need to modify the URI_MATCHER to recognize our content://shelves/live_folders/books content URI:

private static final int LIVE_FOLDER_BOOKS = 4;
// ...
URI_MATCHER.addURI(AUTHORITY, "live_folders/books", LIVE_FOLDER_BOOKS);

Then we need to create a new projection map for the cursor. A projection map can be used to "rename" columns. In our case, we will replace BooksStore.Book._ID, BooksStore.Book.TITLE and BooksStore.Book.AUTHORS with LiveFolders._ID, LiveFolders.TITLE and LiveFolders.DESCRIPTION:

private static final HashMap LIVE_FOLDER_PROJECTION_MAP;
static {
LIVE_FOLDER_PROJECTION_MAP = new HashMap();
LIVE_FOLDER_PROJECTION_MAP.put(LiveFolders._ID, BooksStore.Book._ID +
" AS " + LiveFolders._ID);
LIVE_FOLDER_PROJECTION_MAP.put(LiveFolders.NAME, BooksStore.Book.TITLE +
" AS " + LiveFolders.NAME);
LIVE_FOLDER_PROJECTION_MAP.put(LiveFolders.DESCRIPTION, BooksStore.Book.AUTHORS +
" AS " + LiveFolders.DESCRIPTION);
}

Because we are providing a title and a description for each row, Home will automatically display each item of the live folder with two lines of text. Finally, we implement the query() method by supplying our projection map to the SQL query builder:

public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {

SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

switch (URI_MATCHER.match(uri)) {
// ...
case LIVE_FOLDER_BOOKS:
qb.setTables("books");
qb.setProjectionMap(LIVE_FOLDER_PROJECTION_MAP);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}

SQLiteDatabase db = mOpenHelper.getReadableDatabase();
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, BooksStore.Book.DEFAULT_SORT_ORDER);
c.setNotificationUri(getContext().getContentResolver(), uri);

return c;
}

You can now compile and deploy the application, go to the Home screen and try to add a live folder. I added a books live folder to my Home screen and when I open it, I can see the list of all of my books, with their titles and authors, and all it took was a few lines of code:

The live folders API is extremely simple and relies only on intents and content URI. If you want to see more examples of live folders implementation, you can read the source code of the Contacts application and of the Contacts provider.

You can also download the result of our exercise, the modified version of Shelves with live folders support.


Learn about Android 1.5 and more at Google I/O. Members of the Android team will be there to give a series of in-depth technical sessions and to field your toughest questions.

Labels

'hungry' (1) "O" (1) (press (1) [N8/C7/C6/E7]apps (140) [N8/C7/C6/E7]games (169) $10 Off (1) $15 Rush Tickets (1) $50 Million (1) 1110V (1) 12 Days of Christmas (1) 12 seconds (1) 148apps (1) 1490LMT (1) 1950s. vintage advertising (1) 1955 (1) 2008 (1) 2009 Season (1) 2011 (1) 24/7 Wall Street (1) 3-D (1) 30th Anniversary (1) 319 Bowery (1) 3d checkers (1) 3D Compass Plus (1) 3G iPhone (30) 40cozy (1) 4445 Bash (1) 4ft12m (1) 4th of July (1) 50s (2) 5Inch (1) 6Volt (1) 70s (1) 7200mAh (1) 802.11n (1) 80s (4) 8Ounce (1) A Delicate Balance (1) A Long and Winding Road (1) A R Gurney (2) A Raisin in the Sun (1) A Rural Tragedy (1) A Stripper's History (1) A1185 (2) Aaron Copland (1) Aaron Tveit (1) ABC (5) About Last Night (1) about.com (1) academy awards (1) Academy of Music (1) Access Copyright (2) Accessories (12) Accordian Orchestra (1) ACE Awards (1) Ace Tennis (1) Aceh Recipes (3) acquire (1) acquisition (1) acquisitions (7) across (1) Acrylic (1) action (214) actor scholarship (1) ad sales (9) ad:edit (3) Adam Feldman (1) Adam Monley (1) address labels (1) Adjustable (1) Administration (1) AdMob (1) adobe (3) Adriana Lima (1) Adults (1) advance (1) adventure (247) advertisers (5) Advertising (17) affortable clothing (1) afp (1) Africa (1) ageekspot (1) Agnes De Mille (1) aim (1) Airport (1) Al Hirschfeld Theatre (1) Alan Ziter (1) alarm (2) Alberta (1) alert (1) Algerian Recipes (14) Ali (1) Alice Ripley (1) Alina Vacariu (1) all iphone wallpapers (2) all natural (1) all weird news (1) alliphonewallpapers (4) Allison Schulnik (1) AllLocking (1) allrecipes (1) almond bark (1) almond toffee (1) Almonds (1) alphabet blocks (1) AluminumSteel (1) Alvin Epstein (1) Amato Opera (1) amazon (3) AMC (1) america (1) American Symphony Orchestras (1) American Voices New Play Institute (1) Amherst (1) Amy Freed (1) Amy J. Carle (1) Anatomy (1) Ancestral Voices (1) anchor free (1) anchovies porridge (1) Ancient (1) Anders Cato (1) Andres (1) Android (126) Android 1.5 (15) Android 1.6 (10) Android 2.0 (3) Android 2.1 (2) Android 2.2 (2) Android 2.3 (1) Android 2.3.3 (1) Android 3.0 (2) Android Central (1) Android Developer Challenge (19) Android Developer Phone (2) Android Market (8) Angela Lansbury (1) angelina jolie (1) Angels in America (1) Animal Crackers (1) Anna Kournikova (1) Anna Russell (1) Anne Gottleb (1) Anne Undeland (1) Annie Get Your Gun (2) anniversaries (6) Anniversary (1) Announcements (39) announces (1) Antec (1) Anthony Amato (1) Antiques (1) Anton Kuerti (1) aposTouch (1) app craver (3) app shopper (1) app store (16) Appetizer (2) Appetizers (148) apple (515) apple apple iphone school (2) apple case (1) apple cider (1) apple crisp (1) apple insider (2) Apple iPhone Developer Conference (1) apple muffins (1) apple orchards (1) apple picking (1) apple tell (3) appleinsider (1) apples (2) application (277) Applications (201) applique shirts (1) Applying (1) appointed (1) appointment (23) appointments (11) appoints (1) Apps (26) apps for samsung wave (3) appstore (6) appulous (1) appventcalendar (1) aquacalendar.sisx (1) archives (1) Area Stage (1) arena (1) Arena Stage (10) around me (1) ars technica (6) Art (5) Art Basel Miami Beach (1) art collector (1) art direction (12) Art Miami (1) art of the iphone (1) Arthur Fiedler (1) Articles (28) artistic statistics (2) artists (2) arts (6) Arts amendment (1) Arts America (3) arts and crafts (3) Arts Boston (2) arts funding (2) Arts Grants (1) Arts Journal (2) Arts Journal Poll (1) Artstix (1) artwork (3) ashlees boutique (2) ashley simpson (1) ashley tisdale (1) ASME (2) association publishing (1) associtations. Canadian Press (1) AT T (2) Atlanta (2) Atlanta Performs (1) Atlantic Canada (1) Atlantic Journalism Awards (1) Atlantic Magazines Association (1) ATT (26) ATXmATX (1) ATXmATXITX (1) audience (2) audience development (2) audiences (2) audio (20) Audra Blazer (1) August Wilson (1) August: Osage County (1) Austin (1) AusTIX (1) autism (1) Automatic (1) autumn (1) Auxiliary (1) Auxin (1) Avatar (1) Avril Lavigne (1) Awakening (1) awards (23) awards. Western Magazine Awards (1) Awl (1) Ayaan (1) ayam kalasan singapore (1) b-to-b (1) B.C. (1) b2b (2) babes (2) babies (7) babo botanicals (2) baby (3) baby announcements (1) baby care (1) baby clothes (1) baby clothing (4) baby gifts (6) baby items (1) baby names (1) baby products (1) baby shower gift (1) baby showers (4) baby skin care (1) baby toys (1) baby wash (1) back to school (2) back to school lunch box (1) back to school promo (1) background iphone apps (2) Bad Dates (2) Bada (1) Bada Games (1) Bahraini Recipes (10) baked goods (1) baking (2) Baklava (10) Baliwick Repertory (1) ballet trocadero Mass MoCA (3) ballot question (1) Baltimore (1) Banana (3) banana crapes recipe (1) banners (1) Bar Refaeli (1) Barack and Michelle (1) Barack Obama (1) Barbecue (40) barefoot books (10) barefoot in portland (1) bark (1) Barrington Stage (7) Barrington Stage Company (6) Barrymore Theatre (1) Batteries (2) battery (10) BBC (2) beamme (1) bean bags (1) Bean Curd (2) Beatles Love (1) Beckett Estate (1) Beef (9) Beef Recipe (11) beer (1) beer glasses (1) beerbutton (1) Belasco Theatre (1) Belkin (4) Bench (1) Benefit (1) bento boxes (1) bento lunch box (1) Berkshire (2) Berkshire Beat (1) Berkshire Eagle (1) Berkshire Fine Arts (10) Berkshire on Stage (2) Berkshire Theaters (1) Berkshire Theatre Festival (15) Berkshire Theatre Openings (1) Berkshires (7) Berlin Metro (1) bestt free iphone games (2) beta (1) Betsy Dorfman (1) bible (2) Biblica (1) bibs (3) big top (1) Bill Irwin (1) Bill's Casino (1) Billion (1) billion dollar movies (1) Billy Holiday (1) bing (1) birds (1) birthday (1) birthday parties (1) birthdays (1) Biscuit (1) bivinteractive (1) bizjournals (1) Black (5) black eyed peas (1) black friday (1) black pepper (1) black pepper beef saos fried chicken (1) black pepper chicken cook (1) black pepper sauce seafood (1) Blackberry (39) Blackout (1) Blades (1) Blithe Spirit (1) Blockbuster (2) blocks (1) bloggers (1) Blogging (10) blogs (5) blorge (4) Blue Man Group (1) Bluegrass (1) bluetooth (8) Bluray (2) Bob Dylan (1) Bob Marley (1) Bob Merrill (1) Bog of Cats (1) bolt (1) bon cherry (1) Bon Jovi (1) bonjour family (1) books (5) Bosch (1) Bostix (1) Boston (6) Boston Ballet (1) Boston Center for the Arts (2) Boston MA (1) Boston Pops (2) Boston Symphony Orchestra (2) Boston theatre scene (1) Boston.com (1) boutique (1) boutique clothing (1) box.net (1) boy clothing (1) Brad Steele (1) Brand (1) Brandeis trustees (1) Brandeis University (4) branding (5) Braodway (1) bread (1) bread making (1) Bread recipes (4) Breakfast (2) breakfast for dinner (1) breakfast menu (1) breakfast recipes (1) Brian Dennehy (3) brian hogan (1) Brief (1) Britain (1) British artist (1) Broadway (17) Broadway discount (2) Broadway Discounts (1) Broadway League (1) Broadway revival (2) Broadway show (1) Broccoli (3) broken sculpture (1) Brooklyn (1) Brooklyn Museum (1) brought (1) brownies n butterflies (5) browser (19) Browsing (1) Bruce Jordan (1) Bruce Springsteen (1) BSO (4) BTF PLAYS (1) bubur gurih (1) bubur kacang ijo (1) bubur sukabumi (1) Bucheel (1) Buchel (2) budget (1) budget eating (1) budget recipes (1) burp cloths (2) business (1) business apps (1) business cards (1) Business Info (11) business insider (2) business week (1) business wire (1) busy (1) butterflies (1) Butterfly (1) butterfly mobiles (2) butterfly orb (3) butterfly wings (1) Buxton (1) c (1) C-R Productions (1) Cabaret (2) Cabaret Grimm (1) Cabbage (3) Cable (1) cadiwompus (2) Cake Traditional Fermentation (1) Calabarock (1) calculator (2) Caleb Hiliadis (1) Calling (2) Calvin Gentry (1) Camelot (1) camera (12) Canada Post (2) Canada Council (1) Canada Magazine Fund (1) Canada Periodical Fund (5) Canadian Geographic (1) Canadian Heritage (2) Canadian Journalism Foundation (1) Canadian Online Publishing Awards (1) Canadian Writers Group (2) Cancellation (1) canning (1) cardigans (1) Caretaker (1) Carla Gugino (2) Carnegie (1) Carnegie Hall (2) Carnival (1) Carolann Patterson (1) Carole Feuerman (1) Carole King (1) Caroline or Change (1) Carousel (1) Carrot (2) carrot juice (1) carrot puree (1) Carter (1) cartoon wallpapers (1) cartoons (1) Cashew nut (1) cats (2) cbc (2) cbs (1) cbs4 (1) CD (2) celebrities (1) Cell Phone (1) Cell Phone blocker (1) Cell Phone News (31) Center (1) CenterHTPC (1) Chad Allen (3) Challenge (1) Chandra Wilson (1) Change (1) channel web (1) Charger (1) Charity (1) Charles Giuliano (3) Charles Playhouse (1) Charles Randolph-Wright (1) Charlie Ergen (1) charlie's soap (1) charlotte con mahlsdorf (1) Charlotte St. Martin (1) chat (56) chat rooms (1) cheap iphone (1) Cheese (1) Cherish the Ladies (1) Cheryl Tweedy (2) chess with friends (1) Chicago (4) Chicago Musical (2) Chicken (35) Chicken Recipe (33) chicken tomato sauce (1) Chickory (1) childhood (34) children (17) children's shows (2) childrens art (1) Childrens books (2) childrens clothing (2) childrens cooking (1) childrens toys (1) chili dipping chicken (1) chili sauce fried chicken (1) Chinese Food (3) chinese worker (1) chocolate (5) Chris Anderson (1) chris pirillo (1) Chris Thile (1) Christine Ebersole (1) christmas (6) Christmas Carol (1) Christmas Desserts (30) christmas eve (1) Christmas Mains (8) Christmas Show (1) Christmas Sides (12) christmas specials (1) Christmas trees (1) Christopher (1) Chronicle (1) cio (1) Circle of theatres (2) Circulation (15) Cirebon Recipes (1) Cirque bug show (1) Cirque du Soleil (6) Citroen Osee (1) City of Pittsfield (1) Civilization (1) Clark Art Institute (2) classical music (2) CLB Media (1) cleaning (1) Cleveland (2) Clock (1) clocks (1) closes (1) closures (8) clothes (2) cnet (10) cnn money (1) Code Day (4) coffee (2) Cohoes (1) Cohoes Music Hall NY (1) cold (1) Colin Lane (1) Collection (1) collective bargaining (1) Collectors (1) Colonial Theatre (4) Colonial Theatre Pittsfield (6) color splash (1) colorful ecosystem (1) colour (1) CoMA (1) comfort food (3) comics (1) Commonwealth Opera Northampton (1) company (1) compatible (1) competitions (1) completes (1) components (1) Computer (2) Computer and Accessories (34) computer world (1) computers (1) Concepts (1) concert halls (1) Conde Nast (2) Cond� Nast (1) Conference Shakespeare Theatre Association (3) conferences (2) congress (1) Connect (1) Connecticut (1) consolidation (1) consumer reports (1) consumerist (1) Consumers (1) contemporary art (2) content-sharing (1) contests (2) contract-free (4) controlling diet (1) controversy (1) Cookbook (1) cooked meat (1) cookies (6) cooking (4) cooking for kids (7) Cool Stuff (1) cool tricks (4) Copley Square (1) copy paste (1) copyright (5) Coriander (1) coriander salad (1) Corn (3) cost of cable satellite (1) costs (1) costumes (2) coupons (3) courtesans (1) Couscous (2) covers (6) cowgirl chocolates (4) Crab (2) craft fairs (1) craft finds (1) crafts (5) crape (1) crayon wallets (1) crazy mike apps (1) Creamy Carrot and Orange Soup (1) create (1) creative clusters (1) creativity (2) Criss Angel Believe (1) crochet hats (1) crocheting (1) Crowns (1) crunch deal (1) crunch gear (1) csas (1) CSME (3) csn office furniture (1) csn stores (2) CT Ovo (1) Cucumber (5) Cucumber Recipe (1) cucumber salad (1) cucumber salmon salad (1) Cucumber with Chili Shrimp Paste (1) cultofmac (1) Cultural Alliance (1) cultural magazines (1) cultural nonprofits (1) Cultural Workforce Forum (1) culture (3) cupcakes (1) Curry (2) custom clothing (1) custom painting (1) custom publishing (1) customer service (1) cydia (2) dailytech (73) dali decals (2) Dame Edna tickets (1) Damien Hirst (1) Damn Yankees (1) dance (2) danica patrick (1) Daniel (1) Daniel Radcliffe (1) Danielle Lloyd (1) Dashboard (1) data (3) David A. Ross (1) David Adkins (1) David Alan Anderson (1) David Beditz (1) David Bryan (1) David Finkle (1) David Mamet (1) David Morse (1) David Rabe (1) David Shapira (1) Dayton (1) deal or no deal (1) deals (2) death (1) debuts (1) decade (1) deception (1) declaration (1) decorating (1) Delay (1) deluxe designs (2) demographics (1) denise milani (1) Dennis Hopper (1) departures (5) design (17) designer (1) designer fabrics (1) Desire Under Elms (1) Desire Under the Elms (2) Desmond Nani Reese (1) Dessert (7) Desserts (106) desserts on the cheap (1) Developer Days (1) Developer Labs (3) Developer profiles (4) developers (2) Developmental (1) deviant art (1) Diagnosed (1) Diane Paulus (1) dictionary (5) Did You Know 3.0 (1) diet recipe (1) digital (23) dinner recipes (1) direct mail (2) Dirty Dancing (1) discount seats (1) Discount tickets (26) discounted tickets (3) discounts (1) Disease (1) disease outbreaks (1) Dish (2) Dish Network (2) dishonest Ticketmaster (1) disney (6) distribution (1) do it yourself (1) documentaries (1) documentary filmmaker (1) dogs (1) DollHouse (1) Donal McCann (1) Donald Strachey (1) Donizetti (1) Doug McLenna (1) Doug Wright (1) doughnut muffins (1) downeast basics (1) downgrading (1) download music hutch (1) downloads (3) Downtown (1) dragon ball z (1) drawing (1) DREAM Act (1) dress up (1) drinking (1) drivetrain (1) dropbox (2) Duracell (1) dv2000 (1) dv2200 (1) dv6000 (1) dv6100 (1) DVD (1) DVDs (1) e entertainment (1) e-book (12) e-books (3) e-media (1) e-readers (2) Ear (1) East Berlin (1) East Haddam (2) East Java Recipes (4) easter (1) Easter candy (1) easy cheezy (1) easy cooking porridge (1) ebay (2) Ebb (1) eco friendly baby (1) Economy (1) econsultancy (1) ECTACO (1) Edition (2) editorial (10) Edmonton (1) education (1) Edward Albee (1) Egg Recipe (5) Eggplant (1) Eggs (5) Egyptian Recipes (68) Einstein (1) El Bosco (1) Elayne P. Bernstein Theatre (1) Elisha Cuthbert (2) Elizabeth Aspenlieder (3) Elyse Sommer (1) email (1) Employing Hope (1) emulator (1) en travesti (1) endowment (2) Eneloop (1) Engines (1) English (1) Entertaining Mr. Sloane (1) entertainment (3) environmental art (1) Equinux (1) Equus (2) Eric Hill (2) erichegwer (1) Ericsson (41) Estragon (1) Etch a Sketch Lite (1) etiquette (1) etsy (19) etsy shops (2) Etty Hillesum (1) Eugene Ionesco (1) Eugene O'Neil (1) European (1) eva longoria (1) eva mendes (1) events (13) Everything (1) Examiner (4) excessive commercials (1) Exit the King (1) expenses (2) experimental film (1) explorer (10) extend (1) Extended (1) Extreme Shepherding (1) eye tricks (1) fabric (1) facebook (3) fact checking (2) fairies (1) fairy house (1) fairy wings (1) Faith (1) Faith Healer (1) Falafel (26) Fall (11) fall clothing (1) family (2) Fandango (1) Faneuil Hall Marketplace (1) Faraday Cage (1) farm baby (1) farmers markets (1) farming (1) farms (1) Fascism (2) Fascist (1) fashion (6) fashion shoot (1) Fashion Show Mall (1) fatboy slim (1) fcc (1) felt (1) felt food (1) fergie (1) ferrari (1) festival (1) feta (1) Fettucine (4) fido (1) fierce mobile content (1) File (1) filipino food recipes (32) film (1) finalists (1) finance (5) FinancePLRcom (1) Financial (4) Fine Art Shipping (1) FINISH (1) Fiona Shaw (1) FIPP (1) firefly confections (2) Firmware (2) firmwares for samsung wave (1) Fish (6) Fish Balls (1) fish dive (1) Fish or Seafood Recipe (21) fish paste dipping (1) FisherPrice (1) flash (16) flashing (1) Flashing Method (1) flashing samsung wave (1) Flasing Tutorial (1) Flea (1) flower backpack (1) flowers (1) flu (1) fm radio (2) fm transmitter (2) fonts (3) football (1) Force (2) forcing (1) format Nokia 6600 (1) fortune magazine (1) Forty Magnolias (1) foxconn (1) Fragmented Orchestra (1) Frame (1) France (2) Franchelle Stewart Dorn (1) Francis X. Curley (1) Francisco (1) Frank Galati (1) Frank Theater (1) fre iphone video recorder (1) Free Beer Glasses Wallpaper (1) Free Ebook (1) free iphone (499) free iPhone 3GS (166) free iphone 4 (15) free iphone applications (12) free iphone apps (154) free iphone coding class (1) free iphone developer university (2) free iphone dock (1) free iphone games (26) free iphone kindle (2) free iphone porn (3) free iphone ringtones (7) free iphone skin (1) Free iPhone Synthesizer (1) Free iPhone tethering (5) Free iPhone Theme (1) free iphone unlock (2) free iphone video recorder (1) free iphone wallpapers (59) free ipod touch (2) free ipod touch apps (3) free mobile video (2) Free Nokia Unlock Codes (1) free phone calls (1) Free Preview Weekend (1) free satellite radio (1) free shipping (2) free sms (3) Free Stuff Online (1) free tv (3) free voice guidance (1) freeappalert (1) freebies (1) freedom (1) freelancers (6) freezer installer (1) Freida Pinto (1) Friction (1) friends (2) Front (1) full house (1) fun with magazines (1) funding (7) fundraising (1) fussy britches (1) future (2) futureshop (1) Fuzzies (1) Gabe Askew (1) Gail Burns (2) Gail Nelson (1) Gail Sez (1) Gala (1) gallery (1) Gallery 51 (1) Galt MacDermot (1) game salad (1) Games (549) games radar (1) Gaming (1) Garden (1) Garden of Earthly Delights (1) gardening (2) Garmin (2) Garmin n�vifone (2) Gary Sinese (1) Gay (1) gearlog (1) gecko (1) geek (1) geek sugar (1) Gendai Games (1) General (13) Gennady Rozhdestvensky (1) genome (1) Geoffrey Rush (1) George Bailey (1) George Hotz (2) Gerald Schoenfeld Theatre (1) Gestures (1) ggiphone (1) Ghosts (1) gift giving (2) gift guide (1) gift sets (1) gifts (5) gigaom (1) Gigotron (1) Gilbert and George (1) Gilbert and Sullivan (1) gilded age (1) Ginger (1) gingerbread house (1) girls (3) Girls Gone Weill (1) Gisele Bundchen (1) giveaway (13) giveaway winner (17) giveaway winners (1) giveaways (98) giveawys (1) giveways (1) gizards fried rice (1) gizmag (2) gizmodo (3) glee gum (4) global (1) Globe and Mail (1) glow iPod (1) go graham go (1) goats (1) Goeff Edgars (1) Goggle (1) Golden Globe (1) Goldstar (1) gonzo (1) good gravy (1) good gravy designs (1) Goodman Theatre (4) Goodspeed Musicals (2) Goodspeed Opera (2) google (14) Google Android (12) google app (1) google books (1) google earth (1) Google I/O (4) google latitude (1) google maps (1) google voice (3) gourds (1) GP952 (1) gps (3) GPS Nokia N9 (1) GPS Nokia N95 (5) Gr?vMe (1) grants (1) Graphic Mania (1) graphics (1) Great (1) Great Quesadilla (1) Greater Washington (1) Greater Washington DC (1) Green Beans (1) green cucumber (1) green pepper sauce chicken (1) Greylock Arts (1) Grilled Quesadillas (1) Grilled Salmon On Naan Bread with Lemon Yogurt (2) Grilling method (1) Grizzly Bear (2) Grocery (1) groundhog day (1) Grouper (1) growing up (4) growth (1) Growth (1) growth charts (1) guacamole (2) guest blogger (3) Guidelines (3) gummies (1) Guthrie Theatre (7) Guys and Dolls (1) gveaways (1) gyanin (1) Hair (1) hair accessories (3) hair clips (1) Hairspray (1) half price tickets (20) halftix (1) halloween (8) halloween 2010 (1) halloween apparel (1) halloween candy (1) halloween recipes (3) handmade (7) handmade toys (3) Happy Days (2) Happy Merry Jolly (1) Harold Pinter (1) Harris Burdick (1) Hartford (1) Hartford CT (1) harvest (2) hate tourists (1) have2p (1) Hawaiian Marketplace (1) Hayden Panettiere (1) HazelMail (1) HD (1) Headset (2) Health (3) Health and Fitness Software (12) health care (1) Healthy (1) healthy snacks (1) healthy cooking (1) healthy eating (6) Healthy Living (22) healthy recipes (2) Heart (1) Heather Robison and Hamish Linklater (1) Heather Woodbury (1) Help for Haiti (2) here films (1) hide and seek (1) Hieronymus Bosch (1) HighLine Ballroom (1) hindi movies (1) hiphone (1) Hirsi (1) History (1) Hitchens (1) hitler (1) hiya luv (1) Hmmm (1) Hmmm... (5) Holiday (5) holiday baking (1) holiday decor (1) holiday fairs (1) holiday feature (2) holiday gift guide (3) holiday gifts (3) Holiday music (2) holiday shopping (1) holidays (8) Holistic (1) Holmes (1) Holocaust (1) Holzer (1) home media magazine (1) home school teacher (1) HomeOffice (1) homeschooling (2) homoerotic (1) horses (1) hospital (1) Hot and Spicy Chicory Recipe (1) hot chili sauce (1) hot cocoa (1) hot fried rice (1) Hot Tix (1) Hotels (32) hotspots (1) Hours (1) hours watching ads (1) hours watching tv (1) Houston (1) how stuff works (1) how to (1) how to cook porridge (1) how to lose weight (1) How-to (22) HP (3) HTC (51) Huawei (14) Hubble (1) Hubbub (1) huffington post (2) Hugh Jackman (1) Hugo Bass (1) hulu (2) Hummus (32) Hundred (1) Hunter Center (1) Hunter Thompson (1) hup (1) hutch mp3 (1) hutch player (1) I Drink the Air Before Me (1) i4u (3) i8910HD/5800/N97/Mini/X6 (435) iad (1) iafrica (1) Ian McKellan (1) Ibsen (1) ice cream (2) ICFC318 (1) iGirl (1) ihound (1) ijiggles (1) illegal (1) illumina (1) illustration (5) ilounge (2) image processing (1) images (1) Imax (1) immigration (1) Imperial Theatre (1) Impressionism (1) In The Heights (1) Inauguration Quartet (1) InCarCables (1) included (1) Included (1) income tax (1) Incredibles (1) independence (1) independent (1) Indexes (1) India (14) Indiaaposs (1) Indian Recipe (3) Indigo (1) Indonesian Food (32) industry associations (4) indy bookstores (2) indy mags (3) infections (1) Inflation (1) info world (2) ingredient of pasta lasagna (1) Inherent Vice (1) innovation (2) Innovators (1) Input (1) Input methods (2) Insect (1) Inside (1) INsight Venture Partners (1) inspiration (1) Inspiron (1) installation (1) installous (1) intel (1) Intents (2) international editions (1) Internet (19) internet news (1) internet radio iphone (1) internships (3) into mobile (2) investigative journalism (2) invitations (1) io2010 (2) Ion blog (1) iPad (10) ipad sdk (1) iphone (519) iphone 21 (1) iphone 3.0 (10) iphone 3g (6) iphone 3g speeds (1) iphone 3gs (7) iphone 3gs problems (3) iphone 3gs video (1) iphone 4 antenna (1) iphone 4 jaialbreak (1) iphone 4 mock (1) iphone 4 problems (1) iphone 4 reactions (1) iphone 4 reception (1) iphone 4g (1) iphone ads (4) iphone alley (2) iphone app demo (1) iphone app review (2) iphone apps (4) iphone apps for parents (1) iphone battery life (1) iphone browser (2) iphone business (1) iphone buzz (1) iphone calendar (1) iphone camera (3) iphone canada (2) iphone carriers (1) iphone class (1) iphone commercial (1) iphone concepts (1) iphone contest (4) iphone contract (1) iphone costume (1) iphone daily (1) iphone data usage (1) iphone delay (1) iphone dev team (4) iphone developer (5) iphone download blog (1) iphone exclusive (1) iphone explode (1) iphone fail (1) iphone fake (1) iphone faq (1) iphone firmware (3) iphone footprint (1) iphone freak (3) iphone games (2) iphone girls (1) iphone gui (2) iphone hack (11) iphone hacks (6) iphone hardware upgrade (1) iphone hosting (1) iphone icons (1) iphone in canada (6) iphone japan (3) iphone joystick (1) iphone kindle (1) iphone language (1) iphone launch (2) iphone leak (3) iphone legal (1) iphone marketing (1) iphone memory (1) iphone mms (5) iphone mod (1) iphone modem (2) iphone monitor (1) iphone movies (1) iphone music apps (2) iphone nano (1) iphone os (11) iphone os 4.0 (1) iphone overheat (1) iphone patent (1) iphone platform (1) iphone predictions (1) iphone problems (2) iphone programming (2) iphone prototype (1) iphone psd (3) iphone radio (2) iphone rumour (7) iphone sales (2) iphone scam (1) iphone sdk (3) iphone security (4) iphone seo (1) iphone sms (1) iphone storage (1) iphone study (1) iphone suicide (1) iphone tethering (2) iphone theme for samsung wave (1) iphone time lapse test (1) iphone tips (1) iphone tracking (1) iphone traffic (1) iphone tv (3) iphone unlock (2) iphone video conferencing (1) iphone wallpapers (2) iphone warranty (1) iphone world (2) iphone worm (2) iphones talk (1) ipod touch (1) ipod touch firmware (1) Ipodmp3 (1) ipodnn (1) ipodtouchfans (1) iporn (2) Iraqi Recipes (14) Islam (2) Islamic (2) iSmashPhone (2) isteam (1) it world (1) It's a Wonderful Life (1) Italian Food (11) italkphone (1) itbusiness (1) iTRAVL (1) itunes (6) iTunes Store (5) itv (1) itwire (1) Itzhak Perlman (1) iZel (1) J Tormey (1) J.S. Bach mandolin (1) Jack Cutmore-Scott (2) Jacob's Pillow Dance (1) jailbreak (14) James and Kim Taylor (1) James Barry (2) James Cameron (1) James Michael Curley (1) James Taylor (2) Jane Hudson (1) Jane Jacobs prize (1) Japan (1) Japanese phones (5) jason chen (1) java (426) Java Apps For Samsung Wave (3) Jay Goode (1) Jaybirds (1) Jayne Atkinson (1) Jcobs Piillow (1) Jean Shepherd (1) Jeffery Self (1) Jehane Noujaim (1) Jehuda Reinharz (2) Jen Davis (1) Jenn Gambatese (1) jenna jameson (1) Jennifer Ellison (1) jennifer lopez (1) Jenny (1) Jepara Recipes (1) Jeremy Irons (1) Jerry Springer (1) Jerry Christakos (1) Jersey Boys (1) jessica alba (2) Jessica Biel (1) jessica simpson (1) jewelry (1) Ji Lee (1) Jim Charles (1) jkontherun (1) Joan Allen (1) Jobathan Epstein (1) Joe Hewitt (1) Joe Thompson (2) Joe Turner's Come and Gone (1) John Barrett (1) John Carmack (1) John Glover (1) John Goodman (1) John Rando (2) John Williams (1) joint ventures (1) Jones (1) joose box (2) joost (2) Jordanian Recipes (124) Joseph Jeffries (1) Joshua Bell (1) Joshua Dean (1) journalism (3) journalismdegree (1) journalist (1) Joyce Theatre (1) Jujamcyn Theatres (1) jukebox musicals (1) Julian Kuerti (2) Juliane Hiam (1) Julianne Boyd (3) July 4th (1) jvc (1) KA (1) Kander (1) kansas city (1) Karen Zacarias (1) Kate Maguire (3) katharine hepburn (1) Katie Johnson Cabaret (1) Katori Hall (1) Katrina Kaif (1) Katy Hill (1) Keeley Hazell (1) Keira Naughton (1) Keith Lockhart (1) Kevin Earley (1) Kevin Duda (1) kevin rose (1) keyboard (8) keylock (13) Kidder Smith (1) kids (10) kids activities (1) kids clothing (2) kids recipes (13) kids room (1) kindergarten (1) Kindle (1) Kingdom (1) Kirk Lynn (1) Kitchens (8) Knickerbocker (1) Knighthood (1) knit hats (1) Know Your Mobile (4) Knowliz (1) Kofta (16) Kooza (2) kristen kruek (1) Kuwaiti Recipes (10) kvj bible audiobook (1) Kweekies (1) LA Stage Allliance (1) la times (1) labels (1) labor day (1) labour-management dispute (2) Lake George Opera (2) Lake Shore Limited (1) Lamb (3) Lamb Recipe (3) Lampung Recipes (1) Language (1) Laptop (4) Larry (1) Larry Murray (10) Las Vegas (2) lasagna (2) latest (1) launches (28) Lauren Worsham (1) le Monde (1) league (1) Leap Year (1) learning toys (1) LEATHER (1) Lebanese Recipes (276) LED Sheep (1) Lee Breuer (1) legal (4) Lenox (4) Lenox MA (1) Les Liaisons Dangereuses (1) LesLiaisons Dangereuses (1) Lets golf 2 (1) Lettuce (3) Lever (1) LG (69) Lie Cheat Steal Fake It (1) life hacker (3) Lifetime (1) light (2) Liion (1) lindsey lohan (1) line extensions (2) Links (1) Lion King (3) Lisa Kron (1) literacy (2) literary journalism (2) Literature (1) Lithium (1) Lithiumion (1) Little Mermaid (3) little princess pea (4) liver fried rice (1) Living (1) Liz Canner (1) local food (1) Local Stations (1) locationbased (1) locker gnome (1) lockout (1) London (2) Long Island (1) Looped (3) Looped Broadway (1) Lorraine Hansberry (1) Los Angeles (1) lose weight article (1) lose weight seminar (1) Lost (1) love (1) loving shop (5) Lowell (1) Lucia di Lammermoor (1) luggage (1) Lumens (1) luna and larrys organic coconut bliss (2) Lunchbox (1) Lunchtime Theatre (1) lux dlx (1) Lyceum Theatre (1) Lynn Harrell (1) Lyric Stage (1) Lyric Stage Company (1) MA (1) MA Ovo (1) Ma561ga (2) Ma561lla (2) mabels labels (2) Mabou Mines (1) mac and cheese (1) mac daddy world (1) mac daily news (1) Mac Haydn Theatre (1) mac mega site (1) mac rumors (2) mac user (1) mac world (1) macapper (1) Macbook (2) Maccarone (1) Maclean's (8) macmost (1) Macsimum News (1) macworld (2) Mad Men (1) Madama Butterfly (1) made in the usa (1) Magawards (1) magazine business (1) magazine industry (2) magazine profiles (1) Magazine Publishers of America (1) magazines (1) Magazines Canada (8) magens bay designs (4) MagNet (2) MagsBC (2) Mahaiwe Performing Arts Center (3) Mail (1) mailing rates (1) Main Course (37) Main Dishes (312) Main Squeeze Orchestra (1) Mainboards (1) Majestic Theatre (1) make money (3) make stable money (1) make use of (1) make your own kits (1) Malay Food (2) Maluku Recipes (2) Mamma Mia (1) management (3) Manitoba magazines (2) manolo blahnik (1) mapquest (1) Marceau (1) Marcel (1) Marco Brambilla (1) Margaret Gibson (1) Margot Kidder (1) Marilyn Abrams (1) Marisa Jara (1) Marissa Miller (1) Mark Favermann (1) market watch (2) Marketing (1) marketing 101 (1) marketing pilgrim (1) Marsha Mason (1) Martha Clarke (1) Martin Lawrence (1) Mary Poppins (2) masakan itali (1) mashable (6) Mass Moca (5) Mass Moca and Jacobs Pillow (1) Mass MoCA Film Series (1) Massachusetts (1) match.com (1) Matt Wade (1) Matte (1) Matthew Lombardo (1) Maude Mitchell (1) Maureen McGovern (1) Maverick Arts (1) May Poppins (1) Mayor (1) mcafee (1) mccain (1) MCLA (1) Measure for Measure (1) meat sauce (1) Media (3) media planners (1) Media Post Group (1) Meego (1) megaapp (1) megafart (1) megan fox (1) Melbourne Australia (1) Meltdown (1) memory (8) Men Fake Foreplay (1) Meredith Corporation (1) Merrimack Repertory Theatre (1) Mesothelioma (1) messenger (5) Met Player (1) metrics (1) Metropolitan Opera (2) mexican (2) mexican food (2) Micahael Greif (1) Michael Arden (1) michael jackson (1) Michael Patrick Thornton (1) Michael Rush (2) Michelle Candice (1) mickey mouse (1) Micro (1) Microsoft (10) microsoft office (1) Midnight (1) MidTower (1) Mike Dugan (1) million (1) mime (1) Minetta Lane (1) mini shopper clutch (1) Minneapolis (2) mint (2) Miranda Hope Shea (1) miranda im (1) Miranda Kerr (1) Miscellaneous (72) ML03B (1) MLM Films (1) mobile (3) mobile appy (1) Mobile Browsers (1) mobile crunch (1) mobile devices (1) mobile entertainment (2) mobile flash (2) Mobile games (5) Mobile Internet (2) Mobile operators (39) Mobile phone Tips (1) Mobile Phone Tricks (1) mobile phones tricks (1) mobile wire (1) mobiles (1) moconews (1) Mohawk Theatre (1) Molly Smith (2) mom's group (1) Momix (1) Monica Bellucci (1) Month (1) Moonwalking (1) Moroccan Recipes (194) mossberg (1) Most Expensive (26) Most Expensive Foods (14) most wanted app (1) Motally (2) motherhood (3) mothering (1) mothers (3) motion sensitive ad (1) Motorola (96) Motorola Unlock (1) Motorola Unlock code (1) Mount (1) Mountain View (1) Mouse King (1) Movie Gallery (1) movies (1) MP3 (1) MPA (1) MS500BLK (1) muffins (3) Mullins Center (1) Multi;oader (1) Multilingual (1) multimedia (1) multitasking (1) Munich (1) Murray (1) Museum (1) Mushroom (2) music (37) music video (2) Musical (2) Muslims (1) my artsy baby (2) MYOPENPC (1) MySpace (1) Mystere (1) N8/C7/C6/E7 (330) Naan Bread (1) Naional Summit on Arts Journalism (1) names (1) Nancy Coyne (1) naptime (1) Nashville (1) nasi goreng ati (1) nasi goreng kampung (1) nasi goreng lezat (1) nasi goreng panas (1) nasi sambal goreng (1) natasha thomas (1) Nathan Lane (1) National Endowment Arts (3) national film board of canada (1) National Magazine Awards (1) National Post (1) National Summit Arts Journalism (1) natural baby (1) natural gum (1) Navigation (1) navigation app (1) Navigator (1) nbc (2) nbc bay area (1) NDK (6) NDrive (1) NDrive Germany (1) NDrive Italy (1) NDrive Poland (1) NDrive Portugal (1) NEA (3) Nearly (1) NEH (1) neowin (2) nes (1) Netflix (3) New York City (1) new england (1) new hampshire (6) new iphone (5) New Jersey (1) New Nokia (3) New Orleans (1) New Rep (1) New Victory Theatre. String ensemble (1) New Year (1) New York (1) New YOrk City Ballet (2) New York Drama Critics Circle (1) New York Times (1) newborn skin care (1) Newly (1) News (13) newsoxy (2) newspapers (7) newsstand (3) newsstands (1) Nexflix (1) Next Issue Media (1) Next to Normal (3) Nexus One (2) nic (1) nice porridge (2) Nicholas (1) Nicholas Martin (1) Nicholas Nickleby (1) Nick Cordero (1) nicole richie (1) Nicole Scherzinger (1) nielson survey (1) Night Cries (1) Nikki Sanderson (1) Nimbuzz (2) no contracts (1) no credit card (1) No on 1 (1) Noel Coward (1) nokia (440) Nokia 5230 (1) Nokia 5530 (496) Nokia 5800 (499) Nokia C2-01 (1) Nokia C3 (2) Nokia C7 (1) Nokia E5 (1) Nokia E63 (12) Nokia E7 (5) Nokia E71 (1) Nokia E90 (2) Nokia N8 (53) Nokia N9 (1) Nokia N900 (4) Nokia N95 8GB (2) Nokia N96 (1) Nokia N97 (495) Nokia Siemens Networks (25) Nokia WP7 (1) Nokia X2-01 (1) Nokia X6 (495) Nokia X7-00 (2) Noodles (5) Nora (1) North Adams (8) North Adams Transcript (1) North Strip (1) North Sumatra (3) northeast (2) Northern (1) Not enough memory (1) Notebook (1) Notebook/laptop (3) notecards (5) NOW (1) NTT DoCoMo (1) nude (1) nursery (2) nursery art (5) Nutcracker (1) ny times (1) obama (3) obituary (4) Obopay (1) octopus (1) Of Mice and Men (1) office (7) Ohio (1) Oklahoma (1) Olympia Dukakis (1) OMDC (2) OMMA (1) On the Other Hand Death (1) onesies (2) online (8) ootunes (1) open mic (1) Open source (1) OpenGL ES (2) Opera (11) opera mini (2) Optimization (10) orabelle baby (3) Orange (16) orange puree (1) organic bath products (1) organic coconut bliss (1) organic foods (2) Organization (1) organizers (1) Orgasm Inc (1) Oriental Recipe (4) Original Cast Recording (1) original painting (1) Orion Society (1) oscar (1) oscars (1) Other mobile phone brands (37) Other Recipes (43) others (4) Out at Arena (1) Ovo (2) Ovo review (1) PA3534U1BRS (1) Pablo Schreiber (2) pac man (1) packaging (1) Packs (1) paddington bear (1) Pagagninni (1) Pageant (1) paid iphone apps (7) painting (1) paintings (1) Palestinian Recipes (116) Palm (1) Pam McKinnon (1) Pamela Anderson (1) Pamela Kurstin (1) pandora (1) Pangea Day (2) Pantech (6) PAPA Center (1) paper (3) paper collage (1) paper goods (5) paper products (2) paperwhites (1) Paprika (1) parenting (1) Paris 1890 Unlaced (1) paris hilton (1) parties (1) partnerships (1) Party (1) party planning (1) Pasta (13) pasta fagioli soup (1) pasta lasagna (1) Pasta Recipe (19) Pastries (20) patent infringement (2) patterns (1) Patti LuPone (1) Pavilion (1) Pay What You Can (1) pay-for-use (3) Paypal (1) pc magazine (2) pc world (4) PCEverything (1) pcmag (2) pcworld (4) PDA (1) PDA / Pocket PC (47) PDQ Bach (1) peach crisp (1) peaches (1) peeps (1) Penne (7) Pennsylvania arts cultural tax (1) pepsi (1) Performance Lab (1) performances (1) performing arts (2) Peter Gil-Sheridan (1) Peter Pan (1) Peter Schaeffer (1) pets (1) PetSafe (1) Phantom of the Opera (1) Pharos (1) Philadelphia Cltural Alliance (1) Philadelphia Cultural Alliance (1) Philadelphia Orchestra (2) Philip LaPointe (1) Philip Sneed (1) Philips (8) Phone (1) Phone App (25) Phone cell (9) Phone cell;Bluetooth (1) Phone Schematics and Service (2) Phone Theme (7) PhoneEthernetCoaxial (1) phones review (1) photo (16) photographs (2) photography (5) photos (1) photoshop (1) PIB (1) Pierre Boulez (1) pillow pets (1) Pineapple (1) pink (1) Pinocchio (1) Pinterland (1) Pipes (1) piracy (1) pitch engine (1) Pittsburgh Arts Council (1) Pittsfield (2) PivotPlug (1) Pizza (1) Platinum (1) play (1) play.com (1) player (24) playing with fiber (2) PMB (1) Poached Salmon - Green Been with Cheesy Dills Sauce (1) Pocket (1) pocket gamer (8) Pocket Mime (1) policy (1) pom wonderful (1) pomegranate juice (1) ponche (1) pop culture (1) Popcorn (1) PopSci (2) porn star (1) Portable (2) Portfolio (1) Portion (1) position (1) Potatoes (3) Power (2) power lines (1) powerful (1) Prairie Home Companion (1) Prawn (1) PreCharged (1) preschool (1) presents (1) President (1) President Broadway (1) President Obama (2) press conference (1) press freedom (1) pricing (1) primakow (1) print-to-web (6) printing (4) privacy (1) Privacy Policy (1) Private Lives (1) Prize (1) Proactiv (1) problems (1) product reviews (1) production (2) professional development (13) programming (2) projections (1) promo codes (1) promotion (11) promotions (16) proseo (1) Protection (1) Protector (1) ProtectorDual (1) Protectors (1) Protest (1) Protests (1) prweb (2) Psychiatric (1) Psychiatry (1) ptmoney (1) Public (1) Public Theatre (1) Publick Theatre (2) publishers (2) publishing (1) publishing models (2) Pudding (1) pumpkin chocolate chip cookies (1) pumpkin soup (1) pumpkins (4) puppies (1) Purplera1n (1) pussycat dolls (1) puzzle (161) Pwn2Own (1) pwnage (2) Qatar Recipes (8) Qatayef (4) Quebecor (2) Quebecor Media (1) Queer as Folk (1) quesadillas (1) quick online tips (1) Quick Search Box (1) quicken (1) quickpwn (2) quilting (1) quilts (2) quiz (1) quote (43) R (1) Racine's (1) racing (21) Radio (1) Radio City (1) Ralph Fiennes (1) Ramadan Desserts (12) Ramadan Recipes (46) Randy Harrison (6) Rangers (2) rapid (1) raspberry (1) raspberry cream cheese heart tarts (1) Rattle (1) Reaching (1) reader-written content (1) Reader's Digest (1) readership (2) reading (5) readwriteweb (1) real business (1) reasons (1) Rechargeable (1) recipe to lose weight (1) recipes (13) Recovery Act (1) Red Bull (1) Red Chamber (1) redesigns (7) redeyechicago (1) redmond pie (1) reference books (1) relaunches (5) release (5) release) (1) religion (1) Remembering (1) Remote (1) remote car start (1) remove app (1) Rental (1) replace (1) Replacement (1) research (17) resep mushroom (1) resep pasta (1) Resolution (1) Resources (1) restaurant food (1) Restaurants (14) retro (2) reveals (1) Reviews (8) Rice (12) rice porridge (1) rice recipe (2) Richard Box (1) Richard Griffiths (1) Richard Kornberg (1) Richard Ooms (1) Richie DuPont (1) Rickrolled (1) rights (3) rim (1) ringtone (6) Ringtones (1) RN873 (1) RNC (1) Rob Melrose (1) Rob Ruggiero (4) Robert Belushi (1) Robert Falls (1) Robert Frost (1) Robertson case (3) Rodgers and Hammerstein (1) Roger Rees (1) rogers (2) Rogers Consumer Publishing (1) Rolling Stone (1) room decor (1) Rose Art Museum (4) roselyn sanchez (1) Roundabout Theatre (1) rpg (66) rujak manis (1) rujak pedas (1) rujak pengantin (1) rujak ulek (1) Rupert Everett (1) Rush PR News (1) Rushdie (2) RV02BW (1) Ryan Lammer (1) S60v3 (111) s8500XXJID (1) s8500XXJK1 (1) safari (2) Salad (52) salaries (1) Sally Wingert (1) Salman (2) Salmon (4) salmon yogurt (1) Salsa Fresca (1) Salted Fish (1) Saltimbanco (1) Sam Worthington (1) sambel ayam goreng (1) sambel goreng ayam (1) sambel terasi (1) Sample code (1) samsung (119) Samsung Omnia HD (21) Samsung Wave Apps (4) Samsung Wave Free Apps And Games (1) Samsung Wave Games (1) Samsung wave theme (3) Samuel Beckett (3) San Diego Theatre League (1) Sandisk (3) Sandwiches (10) sandys baking memories (3) santa claus (2) Sanyo (2) sara jean underwood (1) Sarah Taylor (1) Saratoga (1) Sasha Anawalt (1) Sassy (1) Satanic (2) satwaves (2) Sauce - Relish - Dressing (12) sauce for fried chicken (1) Saudi Recipes (64) Save Me (1) saving money (4) Scale (1) scare machine (1) Scarlett Johansson (1) school (2) science (2) scones (1) Scopes Trial (1) scott tissue (1) scrapbooking (2) screensaver (29) SDK updates (19) second (1) Secondstage (1) Secret (1) security (9) sepia (1) September (1) Serena (1) Series (3) Serrano (1) services (1) Sesame (1) sew-fantastic (2) sewing (2) sexy iphone app (1) sexy iphone wallpaper (29) Shaker Hymn (1) Shakespeare (2) Shakespeare and Company (7) Shakespeare Company (3) Shakespeare Theatre Association of America (1) Sharp (1) Shawarma (6) Shazam (3) Shear Madness Boston (1) Sheath (1) Sherlock (1) shipments (1) shipping (1) shoes (1) shooting (97) shop feature (1) shopping (2) shopping handmade (1) Shorts (1) shotgun house (1) shout me loud (1) show closings (1) showcase (1) Showcase Mall (1) shows (2) Shrek the Musical (1) Shrimp (2) Shubert Theatre (1) siblings (1) sickness (1) Side Dish (4) sidelines (1) Silly (1) silly bands (1) SilverStone (1) SILVERSTONE (1) sim card (1) sim-free iphone 3gs (1) simon blog (1) simple fried rice (1) simple making porridge (1) simple salmon yogurt (1) simple sauce chicken (1) single copies (5) sirius buzz (1) sirius radio (4) Sirloin (1) sisters (3) skincare (1) skype (5) slacker radio (1) slash gear (1) Slate (1) sling box (1) sling player (2) slumdog millionaire (1) SMART (1) smart canuck (1) smart house (1) smart phones (1) Smarter (1) smartphone (4) smartphones (1) Smithereens (1) smoking (1) smoothies (1) sms chat 3rd (1) sms chat download (1) sms messenger (1) Snack (5) Snafu (1) Snapper (2) Snapshots (1) snaptell (1) snow (3) snowday (1) snowman (1) snowstorms (1) snuggie (1) soap (1) soccer (1) Social (2) social gaming (1) social media (3) social networking (1) Sofia Vergara (1) softpedia (5) Software (25) Solid Sound Festival Tickets (1) Solo Recipes (1) Solution (1) song (1) Sonim (2) sonos (1) Sony Ericsson (90) Sony Ericsson Satio (26) sorry (1) Sounds (1) Soup (5) South Kalimantan (1) south park (1) South Sulawesi (1) Southwest Night (1) SPAC (1) Spaghetti (4) SpeakEasy Stage (2) Special (1) special effects (1) special interest publications (1) Spectacular (1) Speech Input (1) speeddate (1) Spelling Bee (1) Spike Jones (1) Spinach (5) Spirituality (1) sponsors (1) Sport (1) sports (65) sports theatre (1) spotify (3) Spring (3) Sprint (2) squarespace (1) Squid (5) St. Ann's Warehouse (1) STAA (2) Stage (1) Stage West (1) stagehands (2) Stainless (1) Standard (3) stanford (5) starbucks (1) started (1) stay at home moms (1) Steak Recipe (3) Steel (2) Stephen (2) Stephen Petronio Dance (1) Steppenwolf (5) Stereo (1) Sterling and Francine Clark Art Institute (1) steve jobs (3) Steven Wright (1) still (1) stimulus bill (1) Stir-fry (8) Stock (2) Stockbridge (2) stocking stuffers (2) Story (1) Stratford Shakespeare Festival (1) strawberries (1) stream games (1) stream videos (1) Street (1) strike (3) student class 2009 (1) subscriptions (4) subscriptons (1) summer (8) Summer 2009 (1) sun (1) Superhero (1) superman (1) Supply (1) Surefire (1) Surge (3) SURGE (1) Survival of Serena (1) Susan Sarandon (1) sustainable (1) swap (1) sweaters (1) sweet chili dipping (1) sweet grass farm (1) sweet orange soup (1) Sweet Potatoes (2) symantec (1) symbian (321) symbian music player (1) symbian^3 (341) Symphony Hall (1) Symphony Orchestra (1) sync iphone (1) synthtopia (2) Syrian Recipes (150) System (1) T Mobile (11) T-Mobile (8) T. Rowe Price Group (1) tables (1) tablets (4) Tag Sale (1) Tagines (32) Take Me Out (1) Takeovers (2) Tallulah Bankhead (2) Tanglewood (3) tap tap revenge (4) tarts (1) Tate (1) Taylor (1) Taylors North Adams (1) tea (1) tech crunch (3) tech dirt (1) tech flash (1) tech mixer (1) tech radar (1) tech worlds (1) techburgh (1) techeblog (1) Technical (2) Technical Requirements (1) technology (7) TED Conference (2) Teehan+lax (1) Tehra Dark Warrior (1) Tel Aviv (1) television (2) television watching (1) Tenderloin (1) Tennessee (1) tent (1) terminal (1) terrible twos (1) Terry Teachout (1) tethering (1) Texas (1) text messaging (1) Text-to-Speech (1) texting (1) tg daily (1) Thaindian News (1) thank you cards (1) thanksgiving (1) The Acting Company Romeo and Juliet (4) the actor (1) The Bach Project (1) The Colonial Theatre (1) the grand horizontals (1) the guardian (1) The Intelligent Homosexual�s Guide to Capitalism and Socialism with a Key to the Scriptures (1) The Intelligent Homosexual�s Guide to Capitalism and Socialism with Key to the Scriptures (1) the iphone blog (12) The Ladies Man (1) The Lt Dan Band (1) The Metropolitan Opera (2) The Mikado (1) The Performanace Lab (1) The Producers (2) the register (1) The Salon (1) the standard (1) the story tree (1) The Tempest (2) The Waypoint (1) The Wrestling Patient (1) the www blog (1) theater (2) Theater Collective (1) Theater Development Fund (1) Theaters (1) Theatre (3) Theatre Bay Area (1) theatre dance music (3) theatre district (1) Theatre Etiquette (1) theatre music dance (1) theatres and concert halls (1) TheatreWorks (1) Theme / Wallpapers (10) Themes (66) theremin (1) Theresa Reebeck (2) third (1) third-party iphone applications (1) This Wonderful Life (1) Thomas (1) Thomas Pynchon (1) Thosiba (1) Three (1) Ticket Agents (1) ticket brokers (1) ticket buying iPhone (1) ticket discounts (3) ticket prices (1) ticket sales (1) ticket scalpers (1) Ticketmaster (2) Ticketplace (1) Ticketron (1) tickets (4) tila tequila (2) Tim O'Brien (1) Time Out New York (1) times of india (1) Tina Landau (1) Tina Packer (3) Tips (13) Tix 4 Tonight (1) Tix Bay Area (1) TKTS (3) TL2EW6 (1) tmc net (1) To Kill a Mockingbird (1) toddler clothing (1) toddlers (3) toffee (1) Tofu (1) Tom Coburn (1) Tom Morris (1) Tomato (4) tomtom iphone app (1) Tony Kushner (2) Tony Kushner premiere (1) Tony Kushner quotes (1) Tony Simotes (1) Toonwarz (1) top 10 (1) top iphone apps (2) top iphone news (2) Topics (3) Toronto (1) Toronto Life (1) toronto star (1) torrent freak (4) torrents (2) Tortilla (1) Toshiba (5) touch (464) touch arcade (1) touchterm (1) toward (1) Tower (1) Toxic Avenger the Musical (1) Toxie (1) toys (4) Tracey Moffatt (1) tractor (1) Tracy Jan (1) trade (2) Traffic (1) train timetable (1) Training Ground for Democracy (1) transactions (2) Transcontinental (5) Transcontinental Media (1) Translator (1) Transparent (1) transvestite (1) trash talk (1) travel diary (1) traveling with kids (2) treats (2) Trent (1) Treo | Centro (14) trick or treat (1) trips (2) trocks (2) truffles (1) Trusted (1) trusted reviews (1) trustees (1) tuaw (2) tubestick (1) tunecast (1) Tunisian Recipes (2) Tupelo Press (1) Turkey (1) Tutorials (12) tuttles barn (1) tutu (1) tutus (2) tv (2) tv.com (1) Tweet (1) tweet baby designs (2) tweetdeck (1) Twilight (1) twilight movie (1) Twitpay (1) twitter (7) Two Keys (1) two little tots (1) Two Weeks (1) TwoDisc (2) Typeapos (1) UAE Recipes (12) ubergizmo (1) Uiq (6) Uiq3 (32) ultrasn0w (2) Underground Atlanta (1) union (2) Union Square (1) unions (1) United (1) Universal (1) universe (1) unlimited data (1) Unlock code for mobile phones (1) unlocked iphone (1) Updates (1) upgrading samsung (1) Ursula Mayes (1) US Senate (1) USA (1) usa today (2) USC (1) User Interface (17) ustream (1) utorrent (2) vacations (1) valentines day (7) valentines day recipes (1) Valerie Harper (3) vandalism (1) vanessa hudgens (1) Vanessa Redgrave (1) vator (1) vcard (1) Veckatimest (1) Vegetable Recipe (9) vegetarian (1) Ventfort Hall (1) Venture (1) verisign (1) Verizon (20) Verses (2) Version (1) Vertu (1) Veterans (1) Victims (1) video (11) videoegg (1) Videos (152) Vietnam Plays (1) Vince Gatton (2) Vincent Delaney (1) vintage (2) vintage clothing (1) vintage living (1) vintage pearl (1) violin (1) viral video (1) Virgin Mobile (1) viruses (1) vision (1) visual (1) visual arts (2) Vivian Matalon (1) Vladimir (1) vlingo (1) Vodafone (4) voicemail (1) voip (1) Vonage (1) vote (2) Waiting for Godot (5) Waiting for Godot Opera (1) wal-mart (1) Wales (1) wall decals (1) wall decor (1) wall street journal (6) Wallet (1) wallpaper (1) Wallpapers (3) Wallpapers for samsung wave (1) wallswitch (5) WalMart (2) WalMart Economy (1) War on Terror (2) Washington (2) Washington DC (1) washington post (2) wayback (1) weather (1) web (3) web and print (45) web burning (1) web date info (1) web resources depot (1) wedding apps (1) wedding invites (1) weekends (1) weelicious (1) Weigh (1) West Java (3) West Kalimantan (1) West Nusa Tenggara (1) West Sumatra (2) Western Magazine Awards (2) Weston Playhouse (1) wett giggles (1) What May Fall (1) WhatTheFont (1) White (2) White House (1) white iphone (1) white noise (1) whole grain (1) Why Buy Used Cars (1) Widget (26) Widgets (43) wifi (25) Wilco Mass MoCA Tickets (1) William Coe Bigelow (1) William Finn (1) William Gibson (1) Williamstown Theatre (2) Williamstown Theatre Festival (3) windows 98 (1) Windows Mobile (13) windows mobile 7 theme (2) Windows phone 7 theme (2) winners (3) winnie the pooh (1) Winter (2) wired (6) wirless and mobile news (1) wirless week (1) Wolfenstein (1) Women of Will (1) women's clothing (1) wool (2) word of mouth (1) Word on the Street (1) World (2) world view (23) World Wide Developer Conference (5) worldaposs (1) Writer 1272 (1) writers (2) Writers' Union (1) writing (1) WTEN (2) WWDC (5) X284G (1) xbiznewswire (1) xltblog (1) xm radio (4) xmas (1) XPERIA (43) xscale (1) Yahoo (6) Yasmnina Reza (1) year in review (1) yellowsnow (1) Yo Yo Ma (1) yoga (1) yogurt (2) Yogyakarta Recipes (3) You Tube (1) youmail (1) Young Frankenstein (1) youtube (4) yowza (1) zdnet (4) Zeitgeist (1) zoss designs (3) Zumanity (1) ZumoDrive (1) Zurin Villlanueva (1)