加载中...

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 specific 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 define 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 definition. 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 specified by the Intent filter. This is why documentation is key when defining 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.

ADC 2 Round 2 Voting Open

The results from ADC 2 Round 1 are now tabulated and verified. With the top 200 applications identified, it's time to begin the final round judging. Be sure to download the ADC 2 judging application, or update your existing application, and help us select the final winners!

For the final round, both users and a Google-selected panel of industry judges will provide votes to determine the final winners. Prizes will be distributed to the top 3 entrants in each of the 10 categories, and the top 3 overall entrants will receive additional prizes. Please see our reference page for full challenge information.

Your vote is critical! We will keep voting open until we have received sufficient votes for all of the applications. We encourage you to download the ADC 2 judging application and evaluate entrants for yourself.

Download Android Developer Challenge 2:

Bring Your Lab Coats

With the recent release of Android 2.0 and the growing number of available devices, we want to give developers a convenient way to test drive their apps on these new devices. We also want to make our Android advocates available to answer any questions you may have.

We are pleased to announce that we will host a series of all day Android developer labs over the next month in the following cities (dates in local time):

  • Mountain View, CA - Nov 9
  • New York, NY - Nov 16
  • London, UK - Nov 17
  • Tokyo, JP - Nov 18
  • Taipei, TW - Nov 20

Due to limited space, developers who have already published an application in Android Market will be given priority. You can request a spot on a first-come, first-serve basis by going to this page. We will send a follow-up email with venue information and other registration details to those who have secured a spot.

Thank you for your continued excitement in Android. We look forward to meeting many of you in the coming weeks!

Announcing Android 2.0 support in the SDK!

I am excited to announce that the Android SDK now supports Android 2.0 (also known as Eclair).

Android 2.0 brings new developer APIs for sync, Bluetooth, and a few other areas. Using the new sync, account manager and contacts APIs, you can write applications to enable users to sync their devices to various contact sources. You can also give users a faster way to communicate with others by embedding Quick Contact within your application. With the new Bluetooth API, you can now easily add peer-to-peer connectivity or gaming to your applications. To get a more complete list of the new capabilities you can add to your applications, please go to the Android 2.0 highlights page.

Current developers can use the SDK Manager to add Android 2.0 support to their SDK as well as update their SDK Tools to revision 3. New developers can download the Android SDK from the download site. After the download, Android platforms must be added using the SDK Manager

The SDK Manager allows you to add new Android platforms to your SDK.

Android SDK Tools, revision 3 is required to develop for Android 2.0. It includes support for code coverage through the Ant build system, as well as Mac OS X 10.6 (Snow Leopard) support for the SDK and related tools. For those of you who develop using Eclipse, we are releasing ADT version 0.9.4 through the usual Eclipse update mechanism.

Over the next few months, we expect to see more and more Android devices being released. These devices will be running Android 1.5, 1.6, or 2.0. We are also planning a minor version update of Android 2.0 towards the end of the year, and that will be the last update for 2009. Below are some of the things you can do to be better prepared:

  • Download the Android 2.0 platform and make sure your existing apps continue to work on new devices running Android 2.0.

  • Make sure that your apps work when using the WVGA (800x480) & FWVGA (854x480) emulator skins. We expect devices with these types of screen, running Android 2.0 to be launched soon.

Checkout the video below for more information about Android 2.0.

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.

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.

ADC 2 Round 1 Scoring Complete

ADC 2 icon

The response to round one of the Android Developer Challenge 2 has been phenomenal! We originally expected that it would take two weeks to get all the necessary data to complete scoring. Over the last 10 days, more than 26,000 Android users reviewed and submitted our target of over 100 scores per application. With this enthusiastic support of the Android community, we are closing the first round of ADC 2 judging today.

We will now be reviewing the results and preparing for round 2. Please stay tuned for information about round 2, where the community, combined with a panel of judges, will narrow down the top 20 applications in each category to determine the final winners. Until then, users with the ADC 2 judging application currently installed will get a notice saying that round 1 is over. When round 2 opens, the judging application will resume giving out new submissions to score. We look forward to seeing the results of the final round and hope that you choose to help us score these top apps as well!