52862.fb2 Beginning Android - читать онлайн бесплатно полную версию книги . Страница 8

Beginning Android - читать онлайн бесплатно полную версию книги . Страница 8

PART 5Content Providers and Services 

CHAPTER 27Using a Content Provider 

Any Uri in Android that begins with the content://scheme represents a resource served up by a content provider. Content providers offer data encapsulation using Uri instances as handles — you neither know nor care where the data represented by the Uri comes from, so long as it is available to you when needed. The data could be stored in a SQLite database, or in flat files, or retrieved off a device, or be stored on some far-off server accessed over the Internet.

Given a Uri, you can perform basic CRUD (create, read, update, delete) operations using a content provider. Uri instances can represent either collections or individual pieces of content. Given a collection Uri, you can create new pieces of content via insert operations. Given an instance Uri, you can read data represented by the Uri, update that data, or delete the instance outright.

Android lets you use existing content providers or create your own. This chapter covers using content providers; Chapter 28 will explain how you can serve up your own data using the content provider framework.

Pieces of Me

The simplified model of the construction of a content Uri is the scheme, the namespace of data, and, optionally, the instance identifier, all separated by slashes in URL-style notation. The scheme of a content Uri is always content://.

So, a content Uri of content://constants/5 represents the constants instance with an identifier of 5.

The combination of the scheme and the namespace is known as the “base Uri” of a content provider, or a set of data supported by a content provider. In the previous example, content://constants is the base Uri for a content provider that serves up information about “constants” (in this case, physical constants).

The base Uri can be more complicated. For example, the base Uri for contacts is content://contacts/people, as the contacts content provider may serve up other data using other base Uri values.

The base Uri represents a collection of instances. The base Uri combined with an instance identifier (e.g., 5) represents a single instance.

Most of the Android APIs expect these to be Uri objects, though in common discussion, it is simpler to think of them as strings. The Uri.parse() static method creates a Uri out of the string representation.

Getting a Handle

Where do these Uri instances come from?

The most popular starting point, if you know the type of data you want to work with, is to get the base Uri from the content provider itself in code. For example, CONTENT_URI is the base Uri for contacts represented as people — this maps to content://contacts/people. If you just need the collection, this Uri works as is; if you need an instance and know its identifier, you can call addId() on the Uri to inject it, so you have a Uri for the instance.

You might also get Uri instances handed to you from other sources, such as getting Uri handles for contacts via sub-activities responding to ACTION_PICK intents. In this case, the Uri is truly an opaque handle… unless you decide to pick it apart using the various getters on the Uri class.

You can also hard-wire literal String objects and convert them into Uri instances via Uri.parse(). For example, in Chapter 25, the sample code used an EditText with content://contacts/people pre-filled in. This isn’t an ideal solution, as the base Uri values could conceivably change over time.

Making Queries

Given a base Uri, you can run a query to return data out of the content provider related to that Uri. This has much of the feel of SQL: you specify the “columns” to return, the constraints to determine which “rows” to return, a sort order, etc. The difference is that this request is being made of a content provider, not directly of some database (e.g., SQLite).

The nexus of this is the managedQuery() method available to your activity. This method takes five parameters:

1. The base Uri of the content provider to query, or the instance Uri of a specific object to query

2. An array of properties of instances from that content provider that you want returned by the query

3. A constraint statement, functioning like a SQL WHERE clause

4. An optional set of parameters to bind into the constraint clause, replacing any ?s that appear there

5. An optional sort statement, functioning like a SQL ORDER BY clause

This method returns a Cursor object, which you can use to retrieve the data returned by the query.

“Properties” is to content providers as columns are to databases. In other words, each instance (row) returned by a query consists of a set of properties (columns), each representing some piece of data.

This will hopefully make more sense given an example.

Our content provider examples come from the ContentProvider/Constants sample application, specifically the ConstantsBrowser class:

constantsCursor = managedQuery(Provider.Constants.CONTENT_URI,

 PROJECTION, nullnullnull);

In the call to managedQuery(), we provide:

• The Uri passed into the activity by the caller (CONTENT_URI), in this case representing the collection of physical constants managed by the content provider

• A list of properties to retrieve (see the following code)

• Three null values, indicating that we do not need a constraint clause (the Uri represents the instance we need), nor parameters for the constraint, nor a sort order (we should only get one entry back)

private static final String[] PROJECTION = new String[] {

 Provider.Constants._ID, Provider.Constants.TITLE,

 Provider.Constants.VALUE};

The biggest “magic” here is the list of properties. The lineup of what properties are possible for a given content provider should be provided by the documentation (or source code) for the content provider itself. In this case, we define logical values on the Provider content provider implementation class that represent the various properties (namely, the unique identifier, the display name or title, and the value of the constant).

Adapting to the Circumstances

Now that we have a Cursor via managedQuery(), we have access to the query results and can do whatever we want with them. You might, for example, manually extract data from the Cursor to populate widgets or other objects.

However, if the goal of the query was to return a list from which the user should choose an item, you probably should consider using SimpleCursorAdapter. This class bridges between the Cursor and a selection widget, such as a ListView or Spinner. Pour the Cursor into a SimpleCursorAdapter, hand the adapter off to the widget, and you’re set — your widget will show the available options.

For example, here is the onCreate() method from ConstantsBrowser, which gives the user a list of physical constants:

@Override

public void onCreate(Bundle savedInstanceState) {

 super.onCreate(savedInstanceState);

 constantsCursor = managedQuery(Provider.Constants.CONTENT_URI,

  PROJECTION, nullnullnull);

 ListAdapter adapter = new SimpleCursorAdapter(this,

  R.layout.row, constantsCursor,

  new String[] {Provider.Constants.TITLE, Provider.Constants.VALUE},

  new int[] {R.id.title, R.id.value});

 setListAdapter(adapter);

 registerForContextMenu(getListView());

}

After executing the managedQuery() and getting the Cursor, ConstantsBrowser creates a SimpleCursorAdapter with the following parameters:

• The activity (or other Context) creating the adapter; in this case, the ConstantsBrowser itself

• The identifier for a layout to be used for rendering the list entries (R.layout.row)

• The cursor (constantsCursor)

• The properties to pull out of the cursor and use for configuring the list entry View instances (TITLE and VALUE)

• The corresponding identifiers of TextView widgets in the list entry layout that those properties should go into (R.id.title and R.id.value)

After that, we put the adapter into the ListView, and we get the results shown in Figure 27-1.

Figure 27-1. ConstantsBrowser, showing a list of physical constants

If you need more control over the views than you can reasonably achieve with the stock view construction logic, subclass SimpleCursorAdapter and override getView() to create your own widgets to go into the list, as demonstrated in Chapter 9.

Doing It By Hand

Of course, you can always do it the “hard way” — pulling data out of the Cursor by hand. The Cursor interface is similar in concept to other database access APIs offering cursors as objects, though, as always, the devil is in the details.

Position

Cursor instances have a built-in notion of position, akin to the Java Iterator interface. To get to the various rows, you can use:

• moveToFirst() to move to the first row in the result set or moveToLast() to move to the last row in the result set

• moveToNext() to move to the next row and determine if there is yet another row to process (moveToNext() returns true if it points to another row after moving, false otherwise)

• moveToPrevious() to move to the previous row, as the opposite to moveToNext()

moveToPosition() to move to a specific index, or move() to move to a relative position plus or minus from your current position

• getPosition() to return your current index

• a whole host of condition methods, including isFirst(), isLast(), isBeforeFirst(), and isAfterLast()

Getting Properties

Once you have the Cursor positioned at a row of interest, you have a variety of methods to retrieve properties from that row, with different methods supporting different types (getString(), getInt(), getFloat(), etc.). Each method takes the zero-based index of the property you want to retrieve.

If you want to see if a given property has a value, you can use isNull() to test it for null-ness.

Give and Take

Of course, content providers would be astonishingly weak if you couldn’t add or remove data from them, only update what is there. Fortunately, content providers offer these abilities as well.

To insert data into a content provider, you have two options available on the ContentProvider interface (available through getContentProvider() to your activity):

• Use insert() with a collection Uri and a ContentValues structure describing the initial set of data to put in the row

• Use bulkInsert() with a collection Uri and an array of ContentValues structures to populate several rows at once

The insert() method returns a Uri for you to use for future operations on that new object. The bulkInsert() method returns the number of created rows; you would need to do a query to get back at the data you just inserted.

For example, here is a snippet of code from ConstantsBrowser to insert a new constant into the content provider, given a DialogWrapper that can provide access to the title and value of the constant:

private void processAdd(DialogWrapper wrapper) {

 ContentValues values = new ContentValues(2);

 values.put(Provider.Constants.TITLE, wrapper.getTitle());

 values.put(Provider.Constants.VALUE, wrapper.getValue());

 getContentResolver().insert(Provider.Constants.CONTENT_URI,

  values);

 constantsCursor.requery();

}

Since we already have an outstanding Cursor for the content provider’s contents, we call requery() on that to update the Cursor’s contents. This, in turn, will update any SimpleCursorAdapter you may have wrapping the Cursor — and that will update any selection widgets (e.g., ListView) you have using the adapter.

To delete one or more rows from the content provider, use the delete() method on ContentResolver. This works akin to a SQL DELETE statement and takes three parameters:

1. A Uri representing the collection (or instance) you wish to update 

2. A constraint statement, functioning like a SQL WHERE clause, to determine which rows should be updated

3. An optional set of parameters to bind into the constraint clause, replacing any ?s that appear there

Beware of the BLOB!

Binary large objects — BLOBs — are supported in many databases, including SQLite. However, the Android model is more aimed at supporting such hunks of data via their own separate content Uri values. A content provider, therefore, does not provide direct access to binary data, like photos, via a Cursor. Rather, a property in the content provider will give you the content Uri for that particular BLOB. You can use getInputStream() and getOutputStream() on your ContentProvider to read and write the binary data.

Quite possibly, the rationale is to minimize unnecessary data copying. For example, the primary use of a photo in Android is to display it to the user. The ImageView widget can do just that, via a content Uri to a JPEG. By storing the photo in a manner that has its own Uri, you do not need to copy data out of the content provider into some temporary holding area just to be able to display it — just use the Uri. The expectation, presumably, is that few Android applications will do much more than upload binary data and use widgets or built-in activities to display that data.

CHAPTER 28Building a Content Provider

Building a content provider is probably the most complicated and tedious task in all of Android development. There are many requirements of a content provider, in terms of methods to implement and public data members to supply. And, until you try using it, you have no great way of telling if you did any of it correctly (versus, say, building an activity and getting validation errors from the resource compiler).

That being said, building a content provider is of huge importance if your application wishes to make data available to other applications. If your application is keeping its data solely to itself, you may be able to avoid creating a content provider, just accessing the data directly from your activities. But if you want your data to possibly be used by others — for example, if you are building a feed reader and you want other programs to be able to access the feeds you are downloading and caching — then a content provider is right for you.

First, Some Dissection

As was discussed in the previous chapter, the content Uri is the linchpin behind accessing data inside a content provider. When using a content provider, all you really need to know is the provider’s base Uri; from there you can run queries as needed or construct a Uri to a specific instance if you know the instance identifier.

When building a content provider, though, you need to know a bit more about the innards of the content Uri.

A content Uri has two to four pieces, depending on the situation:

• It always has a scheme (content://), indicating it is a content Uri instead of a Uri to a Web resource (http://).

• It always has an authority, which is the first path segment after the scheme. The authority is a unique string identifying the content provider that handles the content associated with this Uri.

• It may have a data type path, which is the list of path segments after the authority and before the instance identifier (if any). The data type path can be empty if the content provider handles only one type of content. It can be a single path segment (foo) or a chain of path segments (foo/bar/goo) as needed to handle whatever data-access scenarios the content provider requires.

• It may have an instance identifier, which is an integer identifying a specific piece of content. A content Uri without an instance identifier refers to the collection of content represented by the authority (and, where provided, the data path).

For example, a content Uri could be as simple as content://sekrits, which would refer to the collection of content held by whatever content provider was tied to the sekrits authority (e.g., SecretsProvider). Or, it could be as complex as content://sekrits/card/pin/17, which would refer to a piece of content (identified as 17) managed by the sekrits content provider that is of the data type card/pin.

Next, Some Typing

Next you need to come up with some MIME types corresponding with the content from your content provider.

Android uses both the content Uri and the MIME type as ways to identify content on the device. A collection content Uri — or, more accurately, the combination authority and data type path — should map to a pair of MIME types. One MIME type will represent the collection; the other will represent an instance. These map to the Uri patterns discussed in the previous section for no-identifier and identifier cases, respectively. As you saw in Chapters 24 and 25, you can fill a MIME type into an Intent to route the Intent to the proper activity (e.g., ACTION_PICK on a collection MIME type to call up a selection activity to pick an instance out of that collection).

The collection MIME type should be of the form vnd.X.cursor.dir/Y, where X is the name of your firm, organization, or project, and Y is a dot-delimited type name. So, for example, you might use vnd.tlagency.cursor.dir/sekrits.card.pin as the MIME type for your collection of secrets.

The instance MIME type should be of the form vnd.X.cursor.item/Y, usually for the same values of X and Y as you used for the collection MIME type (though that is not strictly required).

Step #1: Create a Provider Class

Just as an activity and intent receiver are both Java classes, so is a content provider. So, the big step in creating a content provider is crafting its Java class, with a base class of ContentProvider.

In your subclass of ContentProvider, you are responsible for implementing six methods that, when combined, perform the services that a content provider is supposed to offer to activities wishing to create, read, update, or delete content.

onCreate()

As with an activity, the main entry point to a content provider is onCreate(). Here you can do whatever initialization you want. In particular, here is where you should lazy-initialize your data store. For example, if you plan on storing your data in such-and-so directory on an SD card, with an XML file serving as a “table of contents,” you should check if that directory and XML file are there and, if not, create them so the rest of your content provider knows they are out there and available for use.

Similarly, if you have rewritten your content provider sufficiently to cause the data store to shift structure, you should check to see what structure you have now and adjust it if what you have is out-of-date. You don’t write your own “installer” program and so have no great way of determining if, when onCreate() is called, this is the first time ever for the content provider, the first time for a new release of a content provider that was upgraded in place, or just a normal startup.

If your content provider uses SQLite for storage, you can detect if your tables exist by querying on the sqlite_master table. This is useful for lazy-creating a table your content provider will need.

For example, here is the onCreate() method for Provider, from the ContentProvider/Constants sample application available in the Source Code section of http://apress.com:

@Override

public boolean onCreate() {

 db = (new DatabaseHelper(getContext())).getWritableDatabase();

 return(db == null) ? false true;

}

While that doesn’t seem all that special, the “magic” is in the private DatabaseHelper object, described in the chapter on database access.

query()

As one might expect, the query() method is where your content provider gets details on a query some activity wants to perform. It is up to you to actually process said query.

The query method gets the following as parameters:

• A Uri representing the collection or instance being queried

• A String[] representing the list of properties that should be returned

• A String representing what amounts to a SQL WHERE clause, constraining which instances should be considered for the query results

• A String[] representing values to “pour into” the WHERE clause, replacing any ? found there

• A String representing what amounts to a SQL ORDER BY clause

You are responsible for interpreting these parameters however they make sense and returning a Cursor that can be used to iterate over and access the data.

As you can imagine, these parameters are aimed toward people using a SQLite database for storage. You are welcome to ignore some of these parameters (e.g., you can elect not to try to roll your own SQL WHERE-clause parser), but you need to document that fact so activities attempt to query you only by instance Uri and not using parameters you elect not to handle.

For SQLite-backed storage providers, however, the query() method implementation should be largely boilerplate. Use a SQLiteQueryBuilder to convert the various parameters into a single SQL statement, then use query() on the builder to actually invoke the query and give you a Cursor back. The Cursor is what your query() method then returns.

For example, here is query() from Provider:

@Override

public Cursor query(Uri url, String[] projection, String selection,

 String[] selectionArgs, String sort) {

 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

 qb.setTables(getTableName());

 if (isCollectionUri(url)) {

  qb.setProjectionMap(getDefaultProjection());

 } else {

  qb.appendWhere(getIdColumnName()+"="+url.getPathSegments().get(1));

 }

 String orderBy;

 if (TextUtils.isEmpty(sort)) {

  orderBy = getDefaultSortOrder();

 } else {

  orderBy = sort;

 }

 Cursor c = qb.query(db, projection, selection, selectionArgs,

  nullnull, orderBy);

 c.setNotificationUri(getContext().getContentResolver(), url);

 return c;

}

We create a SQLiteQueryBuilder and pour the query details into the builder. Note that the query could be based on either a collection or an instance Uri — in the latter case, we need to add the instance ID to the query. When done, we use the query() method on the builder to get a Cursor for the results.

insert()

Your insert() method will receive a Uri representing the collection and a ContentValues structure with the initial data for the new instance. You are responsible for creating the new instance, filling in the supplied data, and returning a Uri to the new instance.

If this is a SQLite-backed content provider, once again, the implementation is mostly boilerplate: validate that all required values were supplied by the activity, merge your own notion of default values with the supplied data, and call insert() on the database to actually create the instance.

For example, here is insert() from Provider:

@Override

public Uri insert(Uri url, ContentValues initialValues) {

 long rowID;

 ContentValues values;

 if (initialValues!=null) {

  values = new ContentValues(initialValues);

 } else {

  values = new ContentValues();

 }

 if (!isCollectionUri(url)) {

  throw new IllegalArgumentException("Unknown URL " + url);

 }

 for (String colName : getRequiredColumns()) {

  if (values.containsKey(colName) == false) {

   throw new IllegalArgumentException("Missing column: " + colName);

  }

 }

 populateDefaultValues(values);

 rowID = db.insert(getTableName(), getNullColumnHack(), values);

 if (rowID > 0) {

  Uri uri = ContentUris.withAppendedId(getContentUri(), rowID);

  getContext().getContentResolver().notifyChange(uri, null);

  return uri;

 }

 throw new SQLException("Failed to insert row into " + url);

}

The pattern is the same as before: use the provider particulars plus the data to be inserted to actually do the insertion. Please note the following:

• You can insert only into a collection Uri, so we validate that by calling isCollectionUri().

• The provider knows what columns are required (getRequiredColumns()), so we iterate over those and confirm our supplied values cover the requirements.

• The provider is responsible for filling in any default values (populateDefaultValues()) for columns not supplied in the insert() call and not automatically handled by the SQLite table definition.

update()

Your update() method gets the Uri of the instance or collection to change, a ContentValues structure with the new values to apply, a String for a SQL WHERE clause, and a String[] with parameters to use to replace ? found in the WHERE clause. Your responsibility is to identify the instance(s) to be modified (based on the Uri and WHERE clause), then replace those instances’ current property values with the ones supplied.

This will be annoying unless you’re using SQLite for storage. Then you can pretty much pass all the parameters you received to the update() call to the database, though the update() call will vary slightly depending on whether you are updating one instance or several.

For example, here is update() from Provider:

@Override

public int update(Uri url, ContentValues values, String where,

 String[] whereArgs) {

 int count;

 if (isCollectionUri(url)) {

  count = db.update(getTableName(), values, where, whereArgs);

 } else {

  String segment = url.getPathSegments().get(1);

  count = db.update(getTableName(), values, getIdColumnName() + "="

   + segment + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""),

   whereArgs);

 }

 getContext().getContentResolver().notifyChange(url, null);

 return count;

}

In this case, updates can either be to a specific instance or applied across the entire collection, so we check the Uri (isCollectionUri()) and, if it is an update for the collection, just perform the update. If we are updating a single instance, we need to add a constraint to the WHERE clause to only update for the requested row.

delete()

Like update(), delete() receives a Uri representing the instance or collection to work with and a WHERE clause and parameters. If the activity is deleting a single instance, the Uri should represent that instance and the WHERE clause may be null. But the activity might be requesting to delete an open-ended set of instances, using the WHERE clause to constrain which ones to delete.

As with update(), though, this is simple if you are using SQLite for database storage (sense a theme?). You can let it handle the idiosyncrasies of parsing and applying the WHERE clause — all you have to do is call delete() on the database.

For example, here is delete() from Provider:

@Override

public int delete(Uri url, String where, String[] whereArgs) {

 int count;

 long rowId = 0;

 if (isCollectionUri(url)) {

  count = db.delete(getTableName(), where, whereArgs);

 } else {

  String segment = url.getPathSegments().get(1);

  rowId = Long.parseLong(segment);

  count = db.delete(getTableName(), getIdColumnName() + "="

   + segment + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""),

   whereArgs);

 }

 getContext().getContentResolver().notifyChange(url, null);

 return count;

}

This is almost a clone of the update() implementation described earlier in this chapter — either delete a subset of the entire collection or delete a single instance (if it also satisfies the supplied WHERE clause).

getType()

The last method you need to implement is getType(). This takes a Uri and returns the MIME type associated with that Uri. The Uri could be a collection or an instance Uri; you need to determine which was provided and return the corresponding MIME type.

For example, here is getType() from Provider:

@Override

public String getType(Uri url) {

 if (isCollectionUri(url)) {

  return(getCollectionType());

 }

 return(getSingleType());

}

As you can see, most of the logic delegates to private getCollectionType() and getSingleType() methods:

private String getCollectionType() {

 return("vnd.android.cursor.dir/vnd.commonsware.constant");

}

private String getSingleType() {

 return("vnd.android.cursor.item/vnd.commonsware.constant");

}

Step #2: Supply a Uri

You also need to add a public static member… somewhere, containing the Uri for each collection your content provider supports. Typically this is a public static final Uri put on the content-provider class itself:

public static final Uri CONTENT_URI =

 Uri.parse("content://com.commonsware.android.tourit.Provider/tours");

You may wish to use the same namespace for the content Uri that you use for your Java classes, to reduce the chance of collision with others.

Step #3: Declare the Properties

Remember those properties you referenced when you were using a content provider in the previous chapter? Well, you need to have those too for your own content provider.

Specifically, you want a public static class implementing BaseColumns that contains your property names, such as this example from Provider:

public static final class Constants implements BaseColumns {

 public static final Uri CONTENT_URI =

  Uri.parse("content://com.commonsware.android.constants.Provider/constants");

 public static final String DEFAULT_SORT_ORDER = "title";

 public static final String TITLE = "title";

 public static final String VALUE = "value";

}

If you are using SQLite as a data store, the values for the property name constants should be the corresponding column name in the table, so you can just pass the projection (array of properties) to SQLite on a query(), or pass the ContentValues on an insert() or update().

Note that nothing in here stipulates the types of the properties. They could be strings, integers, or whatever. The biggest limitation is what a Cursor can provide access to via its property getters. The fact that there is nothing in code that enforces type safety means you should document the property types well so people attempting to use your content provider know what they can expect.

Step #4: Update the Manifest

The glue tying the content-provider implementation to the rest of your application resides in your AndroidManifest.xml file. Simply add a provider element as a child of the <application> element:

<provider

 android:name=".Provider"

 android:authorities="com.commonsware.android.tourit.Provider" />

The android:name property is the name of the content-provider class, with a leading dot to indicate it is in the stock namespace for this application’s classes (just like you use with activities).

The android:authorities property should be a semicolon-delimited list of the authority values supported by the content provider. Recall, from earlier in this chapter, that each content Uri is made up of a scheme, an authority, a data type path, and an instance identifier. Each authority from each CONTENT_URI value should be included in the android:authorities list.

Now when Android encounters a content Uri, it can sift through the providers registered through manifests to find a matching authority. That tells Android which application and class implements the content provider, and from there Android can bridge between the calling activity and the content provider being called.

Notify-on-Change Support

An optional feature your content provider offers its clients is notify-on-change support. This means that your content provider will let clients know if the data for a given content Uri changes.

For example, suppose you have created a content provider that retrieves RSS and Atom feeds from the Internet based on the user’s feed subscriptions (via OPML, perhaps). The content provider offers read-only access to the contents of the feeds, with an eye toward several applications on the phone using those feeds versus everyone implementing their own feed-poll-fetch-and-cache system. You have also implemented a service that will get updates to those feeds asynchronously, updating the underlying data store. Your content provider could alert applications using the feeds that such-and-so feed was updated, so applications using that specific feed could refresh and get the latest data.

On the content-provider side, to do this call notifyChange() on your ContentResolver instance (available in your content provider via getContext().getContentResolver()). This takes two parameters: the Uri of the piece of content that changed, and the ContentObserver that initiated the change. In many cases, the latter will be null; a non-null value simply means the observer that initiated the change will not be notified of its own changes.

On the content-consumer side, an activity can call registerContentObserver() on its ContentResolver (via getContentResolver()). This ties a ContentObserver instance to a supplied Uri — the observer will be notified whenever notifyChange() is called for that specific Uri. When the consumer is done with the Uri, unregisterContentObserver() releases the connection.

CHAPTER 29Requesting and Requiring Permissions

In the late 1990s a wave of viruses spread through the Internet, delivered via email, using contact information culled from Microsoft Outlook. A virus would simply email copies of itself to each of the Outlook contacts that had an email address. This was possible because, at the time, Outlook did not take any steps to protect data from programs using the Outlook API, since that API was designed for ordinary developers, not virus authors.

Nowadays, many applications that hold onto contact data secure that data by requiring that a user explicitly grant rights for other programs to access the contact information. Those rights could be granted on a case-by-case basis or once at install time.

Android is no different, in that it requires permissions for applications to read or write contact data. Android’s permission system is useful well beyond contact data, and for content providers and services beyond those supplied by the Android framework.

You, as an Android developer, will frequently need to ensure your applications have the appropriate permissions to do what you want to do with other applications’ data. You may also elect to require permissions for other applications to use your data or services, if you make those available to other Android components. This chapter covers how to accomplish both these ends.

Mother, May I?

Requesting the use of other applications’ data or services requires the uses-permission element to be added to your AndroidManifest.xml file. Your manifest may have zero or more uses-permission elements, all as direct children of the root manifest element.

The uses-permission element takes a single attribute, android:name, which is the name of the permission your application requires:

<uses-permission

 android:name="android.permission.ACCESS LOCATION" />

The stock system permissions all begin with android.permission and are listed in the Android SDK documentation for Manifest.permission. Third-party applications may have their own permissions, which hopefully they have documented for you. Here are some of the more important built-in permissions:

• INTERNET, if your application wishes to access the Internet through any means, from raw Java sockets through the WebView widget

• READ CALENDAR, READ CONTACTS, and the like for reading data out of the built-in content providers

• WRITE CALENDAR, WRITE CONTACTS, and the like for modifying data in the built-in content providers

Permissions are confirmed at the time the application is installed — the user will be prompted to confirm it is OK for your application to do what the permission calls for. This prompt is not available in the current emulator, however.

If you do not have the desired permission and you try to do something that needs it, you may get a SecurityException informing you of the missing permission, but this is not a guarantee — failures may come in other forms, depending on if something else is catching and trying to handle that exception.

Halt! Who Goes There?

The other side of the coin, of course, is to secure your own application. If your application is merely activities and intent receivers, security may be just an outbound thing, where you request the right to use resources of other applications. If, on the other hand, you put content providers or services in your application, you will want to implement inbound security to control which applications can do what with the data.

Note that the issue here is less about whether other applications might “mess up” your data, and more about privacy of the user’s information or use of services that might incur expense. That is where the stock permissions for built-in Android applications are focused — can you read or modify contacts, can you send SMS messages, etc. If your application does not store information that might be considered private, security is less of an issue. If, on the other hand, your application stores private data, such as medical information, security is much more important.

The first step to securing your own application using permissions is to declare said permissions, once again in the AndroidManifest.xml file. In this case, instead of uses-permission, you add permission elements. Once again, you can have zero or more permission elements, all as direct children of the root manifest element.

Declaring a permission is slightly more complicated than using a permission. There are three pieces of information you need to supply:

1. The symbolic name of the permission. To keep your permissions from colliding with those from other applications, you should use your application’s Java namespace as a prefix.

2. A label for the permission: something short that is understandable by users.

3. A description for the permission: something a wee bit longer that is understandable by your users.

<permission

 android:name="vnd.tlagency.sekrits.SEE SEKRITS"

 android:label="@string/see sekrits label"

 android:description="@string/see sekrits description" />

This does not enforce the permission. Rather, it indicates that it is a possible permission; your application must still flag security violations as they occur.

There are two ways for your application to enforce permissions, dictating where and under what circumstances they are required. You can enforce permissions in your code, but the easier option is to indicate in the manifest where permissions are required.

Enforcing Permissions via the Manifest

Activities, services, and intent receivers can all declare an attribute named android:permission, whose value is the name of the permission that is required to access those items:

<activity

 android:name=".SekritApp"

 android:label="Top Sekrit"

 android:permission="vnd.tlagency.sekrits.SEE SEKRITS">

 <intent-filter>

  <action android:name="android.intent.action.MAIN" />

  <category

   android:name="android.intent.category.LAUNCHER" />

 </intent-filter>

</activity>

Only applications that have requested your indicated permission will be able to access the secured component. In this case, “access” means the following:

• Activities cannot be started without the permission.

• Services cannot be started, stopped, or bound to an activity without the permission.

• Intent receivers ignore messages sent via sendBroadcast() unless the sender has the permission.

Content providers offer two distinct attributes: readPermission and writePermission:

<provider

 android:name=".SekritProvider"

 android:authorities="vnd.tla.sekrits.SekritProvider"

 android:readPermission="vnd.tla.sekrits.SEE SEKRITS"

 android:writePermission="vnd.tla.sekrits.MOD SEKRITS" />

In this case, readPermission controls access to querying the content provider, while writePermission controls access to insert, update, or delete data in the content provider.

Enforcing Permissions Elsewhere

In your code, you have two additional ways to enforce permissions.

Your services can check permissions on a per-call basis via checkCallingPermission(). This returns PERMISSION GRANTED or PERMISSION DENIED, depending on whether the caller has the permission you specified. For example, if your service implements separate read and write methods, you could get the effect of readPermission and writePermission in code by checking those methods for the permissions you need from Java.

Also, you can include a permission when you call sendBroadcast(). This means that eligible receivers must hold that permission; those without the permission are ineligible to receive it. For example, the Android subsystem presumably includes the RECEIVE SMS permission when it broadcasts that an SMS message has arrived — this will restrict the receivers of that intent to be only those authorized to receive SMS messages.

May I See Your Documents?

There is no automatic discovery of permissions at compile time; all permission failures occur at runtime. Hence, it is important that you document the permissions required for your public APIs, including content providers, services, and activities intended for launching from other activities. Otherwise, the programmers attempting to interface with your application will have to find out the permission rules by trial and error.

Furthermore, you should expect that users of your application will be prompted to confirm any permissions your application says it needs. Hence, you need to document for your users what they should expect, lest they get confused by the question posed by the phone and elect to not install or use your application.

CHAPTER 30Creating a Service

As noted previously, Android services are for long-running processes that may need to keep running even when decoupled from any activity. Examples include playing music even if the “player” activity gets garbage-collected, polling the Internet for RSS/Atom feed updates, and maintaining an online chat connection even if the chat client loses focus due to an incoming phone call.

Services are created when manually started (via an API call) or when some activity tries connecting to the service via inter-process communication (IPC). Services will live until no longer needed and if RAM needs to be reclaimed. Running for a long time isn’t without its costs, though, so services need to be careful not to use too much CPU or keep radios active much of the time, or else the service causes the device’s battery to get used up too quickly.

This chapter covers how you can create your own services; the next chapter covers how you can use such services from your activities or other contexts. Both chapters will analyze the Service/WeatherPlus sample application, with this chapter focusing mostly on the WeatherPlusService implementation. WeatherPlusService extends the weather-fetching logic of the original Internet/Weather sample, by bundling it in a service that monitors changes in location, so the weather is updated as the emulator is “moved”.

Service with Class

Creating a service implementation shares many characteristics with building an activity. You inherit from an Android-supplied base class, override some lifecycle methods, and hook the service into the system via the manifest.

The first step in creating a service is to extend the Service class, in our case with our own WeatherPlusService subclass.

Just as activities have onCreate(), onResume(), onPause() and kin, Service implementations can override three different lifecycle methods:

1. onCreate(), which, as with activities, is called when the service process is created

2. onStart(), which is called when a service is manually started by some other process, versus being implicitly started as the result of an IPC request (discussed more in Chapter 31)

3. onDestroy(), which is called as the service is being shut down.

Common startup and shutdown logic should go in onCreate() and onDestroy(); onStart() is mostly if your service needs data passed into it from the starting process and you don’t wish to use IPC.

For example, here is the onCreate() method for WeatherPlusService:

@Override

public void onCreate() {

 super.onCreate();

 client = new DefaultHttpClient();

 format = getString(R.string.url);

 myLocationManager =

 (LocationManager)getSystemService(Context.LOCATION_SERVICE);

 myLocationManager.requestLocationUpdates("gps", 10000,

  10000.0f, onLocationChange);

}

First, we chain upward to the superclass, so Android can do any setup work it needs to have done. Then we initialize our HttpClient and format string as we did in the Weather demo. We then get the LocationManager instance for our application and request to get updates as our location changes, via the gps LocationProvider, which will be discussed in Chapter 33.

The onDestroy() method is much simpler:

@Override

public void onDestroy() {

 super.onDestroy();

 myLocationManager.removeUpdates(onLocationChange);

}

Here, we just shut down the location-monitoring logic, in addition to chaining upward to the superclass for any Android internal bookkeeping that might be needed.

In addition to those lifecycle methods, though, your service also needs to implement onBind(). This method returns an IBinder, which is the linchpin behind the IPC mechanism. If you’re creating a service class while reading this chapter, just have this method return null for now, and we’ll fill in the full implementation in the next section.

When IPC Attacks!

Services will tend to offer inter-process communication (IPC) as a means of interacting with activities or other Android components. Each service declares what methods it is making available over IPC; those methods are then available for other components to call, with Android handling all the messy details involved with making method calls across component or process boundaries.

The core of this, from the standpoint of the developer, is expressed in AIDL: the Android Interface Description Language. If you have used IPC mechanisms like COM, CORBA, or the like, you will recognize the notion of IDL. AIDL describes the public IPC interface, and Android supplies tools to build the client and server side of that interface.

With that in mind, let’s take a look at AIDL and IPC.

Write the AIDL

IDLs are frequently written in a “language-neutral” syntax. AIDL, on the other hand, looks a lot like a Java interface. For example, here is the AIDL for the IWeather:

package com.commonsware.android.service;

// Declare the interface.

interface IWeather {

 String getForecastPage();

}

As with a Java interface, you declare a package at the top. As with a Java interface, the methods are wrapped in an interface declaration (interface IWeather { ... }). And, as with a Java interface, you list the methods you are making available.

The differences, though, are critical.

First, not every Java type can be used as a parameter. Your choices are:

• Primitive values (int, float, double, boolean, etc.)

• String and CharSequence

• List and Map (from java.util)

• Any other AIDL-defined interfaces

• Any Java classes that implement the Parcelable interface, which is Android’s flavor of serialization

In the case of the latter two categories, you need to include import statements referencing the names of the classes or interfaces that you are using (e.g., import com.commonsware.android.ISomething). This is true even if these classes are in your own package — you have to import them anyway.

Next, parameters can be classified as in, out, or inout. Values that are out or inout can be changed by the service and those changes will be propagated back to the client. Primitives (e.g., int) can only be in; we included in for the AIDL for enable() just for illustration purposes.

Also, you cannot throw any exceptions. You will need to catch all exceptions in your code, deal with them, and return failure indications some other way (e.g., error code return values).

Name your AIDL files with the .aidl extension and place them in the proper directory based on the package name.

When you build your project, either via an IDE or via Ant, the aidl utility from the Android SDK will translate your AIDL into a server stub and a client proxy.

Implement the Interface

Given the AIDL-created server stub, now you need to implement the service, either directly in the stub, or by routing the stub implementation to other methods you have already written.

The mechanics of this are fairly straightforward:

• Create a private instance of the AIDL-generated .Stub class (e.g., IWeather.Stub)

• Implement methods matching up with each of the methods you placed in the AIDL

• Return this private instance from your onBind() method in the Service subclass

For example, here is the IWeather.Stub instance:

private final IWeather.Stub binder = new IWeather.Stub() {

 public String getForecastPage() {

  return(getForecastPageImpl());

 }

};

In this case, the stub calls the corresponding method on the service itself. That method, which simply returns the cached most-recent weather forecast for the current location, is shown here:

synchronized private String getForecastPageImpl() {

 return(forecast);

}

Note that AIDL IPC calls are synchronous, and so the caller is blocked until the IPC method returns. Hence, your services need to be quick about their work.

Manifest Destiny

Finally, you need to add the service to your AndroidManifest.xml file, for it to be recognized as an available service for use. That is simply a matter of adding a service element as a child of the application element, providing android:name to reference your service class.

For example, here is the AndroidManifest.xml file for WeatherPlus:

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

 package="com.commonsware.android.service">

 <uses-permission android:name="android.permission.INTERNET" />

 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

 <application android:label="@string/app_name">

  <activity android:name=".WeatherPlus" android:label="@string/app_name">

   <intent-filter>

    <action android:name="android.intent.action.MAIN" />

    <category android:name="android.intent.category.LAUNCHER" />

   </intent-filter>

  </activity>

  <service android:name=".WeatherPlusService" />

 </application>

</manifest>

Since the service class is in the same Java namespace as everything else in this application, we can use the shorthand dot-notation (".WeatherPlusService") to reference our class.

If you wish to require some permission of those who wish to start or bind to the service, add an android:permission attribute naming the permission you are mandating — see Chapter 35 for more details.

Lobbing One Over the Fence

Classic IPC is one-way: the client calls functions on the service. It is possible, through the creative use of AIDL, to allow the service to call back into an activity. However, this is a bit fragile, as the service may not know if the activity is still around or if it has been killed off to free up some memory.

An alternative approach, first mentioned in Chapter 23 which discusses Intent filters, is to have the service send a broadcast Intent that can be picked up by the activity… assuming the activity is still around and is not paused. We will examine the client side of this exchange in Chapter 31; for now, let us examine how the service can send a broadcast.

The theory behind the WeatherPlusService implementation is that the service gets “tickled” when the device (or emulator) position changes. At that point, the service calls out to the Web service and generates a new forecast Web page for the activity to display. At the same time, though, the service also sends a broadcast, to alert the activity that there is a page update available if it wants it.

Here is the high-level implementation of the aforementioned flow:

private void updateForecast(Location loc) {

 String url = String.format(format, loc.getLatitude(),

  loc.getLongitude());

 HttpGet getMethod = new HttpGet(url);

 try {

  ResponseHandlerString responseHandler = new BasicResponseHandler();

  String responseBody = client.execute(getMethod, responseHandler);

  String page = generatePage(buildForecasts(responseBody));

  synchronized(this) {

   forecast = page;

  }

  sendBroadcast(broadcast);

 } catch (Throwable t) {

  android.util.Log.e("WeatherPlus",

  "Exception in updateForecast()", t);

 }

}

Much of this is similar to the equivalent piece of the original Weather demo — perform the HTTP request, convert that into a set of Forecast objects, and turn those into a Web page. The first difference is that the Web page is simply cached in the service, since the service cannot directly put the page into the activity’s WebView. The second difference is that we call sendBroadcast(), which takes an Intent and sends it out to all interested parties. That Intent is declared up front in the class prologue:

private Intent broadcast = new Intent(BROADCAST_ACTION);

Here, BROADCAST_ACTION is simply a static String with a value that will distinguish this Intent from all others:

public static final String BROADCAST_ACTION =

 "com.commonsware.android.service.ForecastUpdateEvent";

Where’s the Remote? And the Rest of the Code?

In Android, services can either be local or remote. Local services run in the same process as the launching activity; remote services run in their own process. A detailed discussion of remote services will be added to a future edition of this book.

We will return to this service in Chapter 33, at which point we will flesh out how locations are tracked (and, in this case, mocked up).

CHAPTER 31Invoking a Service

Services can be used by any application component that “hangs around” for a reasonable period of time. This includes activities, content providers, and other services. Notably, it does not include pure intent receivers (i.e., intent receivers that are not part of an activity), since those will get garbage collected immediately after each instance processes one incoming Intent.

To use a service, you need to get an instance of the AIDL interface for the service, then call methods on that interface as if it were a local object. When done, you can release the interface, indicating you no longer need the service.

In this chapter, we will look at the client side of the Service/WeatherPlus sample application. The WeatherPlus activity looks an awful lot like the original Weather application — just a Web page showing a weather forecast as you can see in Figure 31-1.

Figure 31-1. The WeatherPlus service client

The difference is that, as the emulator “moves”, the weather forecast changes, based on updates provided by the service.

Bound for Success

To use a service, you first need to create an instance of your own ServiceConnection class. ServiceConnection, as the name suggests, represents your connection to the service for the purposes of making IPC calls. For example, here is the ServiceConnection from the WeatherPlus class in the WeatherPlus project:

private ServiceConnection svcConn = new ServiceConnection() {

 public void onServiceConnected(ComponentName className,

  IBinder binder) {

  service = IWeather.Stub.asInterface(binder);

  browser.postDelayed(new Runnable() {

   public void run() {

    updateForecast();

   }

  }, 1000);

 }

 public void onServiceDisconnected(ComponentName className) {

  service = null;

 }

};

Your ServiceConnection subclass needs to implement two methods:

1. onServiceConnected(), which is called once your activity is bound to the service

2. onServiceDisconnected(), which is called if your connection ends normally, such as you unbinding your activity from the service

Each of those methods receives a ComponentName, which simply identifies the service you connected to. More importantly, onServiceConnected() receives an IBinder instance, which is your gateway to the IPC interface. You will want to convert the IBinder into an instance of your AIDL interface class, so you can use IPC as if you were calling regular methods on a regular Java class (IWeather.Stub.asInterface(binder)).

To actually hook your activity to the service, call bindService() on the activity:

bindService(serviceIntent, svcConn, BIND_AUTO_CREATE);

The bindService() method takes three parameters:

1. An Intent representing the service you wish to invoke — for your own service, it’s easiest to use an intent referencing the service class directly (new Intent(this, WeatherPlusService.class))

2. Your ServiceConnection instance

3. A set of flags — most times, you will want to pass in BIND_AUTO_CREATE, which will start up the service if it is not already running

After your bindService() call, your onServiceConnected() callback in the ServiceConnection will eventually be invoked, at which time your connection is ready for use.

Request for Service

Once your service interface object is ready (IWeather.Stub.asInterface(binder)), you can start calling methods on it as you need to. In fact, if you disabled some widgets awaiting the connection, now is a fine time to re-enable them.

However, you will want to trap two exceptions. One is DeadObjectException — if this is raised, your service connection terminated unexpectedly. In this case, you should unwind your use of the service, perhaps by calling onServiceDisconnected() manually, as shown previously. The other is RemoteException, which is a more general-purpose exception indicating a cross-process communications problem. Again, you should probably cease your use of the service.

Prometheus Unbound

When you are done with the IPC interface, call unbindService(), passing in the ServiceConnection. Eventually, your connection’s onServiceDisconnected() callback will be invoked, at which point you should null out your interface object, disable relevant widgets, or otherwise flag yourself as no longer being able to use the service.

For example, in the WeatherPlus implementation of onServiceDisconnected() shown previously, we null out the IWeather service object.

You can always reconnect to the service, via bindService(), if you need to use it again.

Manual Transmission

In addition to binding to the service for the purposes of IPC, you can manually start and stop the service. This is particularly useful in cases where you want the service to keep running independently of your activities — otherwise, once you unbind the service, your service could well be closed down.

To start a service, simply call startService(), providing two parameters:

1. The Intent specifying the service to start (again, the easiest way is probably to specify the service class, if it’s your own service)

2. A Bundle providing configuration data, which eventually gets passed to the service’s onStart() method

Conversely, to stop the service, call stopService() with the Intent you used in the corresponding startService() call.

Catching the Lob

In Chapter 31, we showed how the service sends a broadcast to let the WeatherPlus activity know a change was made to the forecast based on movement. Now, we can see how the activity receives and uses that broadcast.

Here are the implementations of onResume() and onPause() for WeatherPlus:

@Override

public void onResume() {

 super.onResume();

 registerReceiver(receiver,

  new IntentFilter(WeatherPlusService.BROADCAST_ACTION));

}

@Override

public void onPause() {

 super.onPause();

 unregisterReceiver(receiver);

}

In onResume(), we register a static BroadcastReceiver to receive Intents matching the action declared by the service. In onPause(), we disable that BroadcastReceiver, since we will not be receiving any such Intents while paused, anyway.

The BroadcastReceiver, in turn, simply arranges to update the forecast on the UI thread:

private BroadcastReceiver receiver = new BroadcastReceiver() {

 public void onReceive(Context context, Intent intent) {

  runOnUiThread(new Runnable() {

   public void run() {

    updateForecast();

   }

  });

 }

};

And updateForecast() uses the interface stub to call into the service and retrieve the latest forecast page, also handling the case where the forecast is not yet ready (null):

private void updateForecast() {

 try {

  String page = service.getForecastPage();

  if (page==null) {

   browser.postDelayed(new Runnable() {

    public void run() {

     updateForecast();

    }

   }, 4000);

   Toast

   .makeText(this, "No forecast available", 2500).show();

  } else {

   browser.loadDataWithBaseURL(null, page, "text/html",

    "UTF-8", null);

  }

 } catch(final Throwable t) {

  svcConn.onServiceDisconnected(null);

  runOnUiThread(new Runnable() {

   public void run() {

    goBlooey(t);

   }

  });

 }

}

CHAPTER 32Alerting Users via Notifications

Pop-up messages. Tray icons and their associated “bubble” messages. Bouncing dock icons. You are no doubt used to programs trying to get your attention, sometimes for good reason. Your phone also probably chirps at you for more than just incoming calls: low battery, alarm clocks, appointment notifications, incoming text message or email, etc.

Not surprisingly, Android has a whole framework for dealing with these sorts of things, collectively called notifications.

Types of Pestering

A service, running in the background, needs a way to let users know something of interest has occurred, such as when email has been received. Moreover, the service may need some way to steer the user to an activity where they can act upon the event — reading a received message, for example. For this, Android supplies status-bar icons, flashing lights, and other indicators collectively known as notifications.

Your current phone may well have such icons, to indicate battery life, signal strength, whether Bluetooth is enabled, and the like. With Android, applications can add their own status-bar icons, with an eye towards having them appear only when needed (e.g., when a message has arrived).

In Android, you can raise notifications via the NotificationManager. The NotificationManager is a system service. To use it, you need to get the service object via getSystemService(NOTIFICATION_SERVICE) from your activity. The NotificationManager gives you three methods: one to pester (notify()) and two to stop pestering (cancel() and cancelAll()).

The notify() method takes a Notification, which is a data structure that spells out what form your pestering should take. The following sections describe all the public fields at your disposal (but bear in mind that not all devices will necessarily support all of these).

Hardware Notifications

You can flash LEDs on the device by setting lights to true, also specifying the color (as an #ARGB value in ledARGB) and what pattern the light should blink in (by providing off/on durations in milliseconds for the light via ledOnMS and ledOffMS).

You can play a sound, using a Uri to a piece of content held, perhaps, by a ContentManager(sound). Think of this as a ringtone for your application.

You can vibrate the device, controlled via a long[] indicating the on/off patterns (in milliseconds) for the vibration (vibrate). You might do this by default, or you might make it an option the user can choose when circumstances require a more subtle notification than an actual ringtone.

Icons

While the flashing lights, sounds, and vibrations are aimed at getting somebody to look at the device, icons are designed to take them the next step and tell them what’s so important.

To set up an icon for a Notification, you need to set two public fields: icon, where you provide the identifier of a Drawable resource representing the icon, and contentIntent, where you supply a PendingIntent to be raised when the icon is clicked. You should be sure the PendingIntent will be caught by something, perhaps your own application code, to take appropriate steps to let the user deal with the event triggering the notification.

You can also supply a text blurb to appear when the icon is put on the status bar (tickerText).

If you want all three, the simpler approach is to call setLatestEventInfo(), which wraps all three of those in a single call.

Seeing Pestering in Action

Let us now take a peek at the Notifications/Notify1 sample project (available in the Source Code section at http://apress.com), in particular the NotifyDemo class:

public class NotifyDemo extends Activity {

 private static final int NOTIFY_ME_ID = 1337;

 private Timer timer = new Timer();

 private int count = 0;

 @Override

 public void onCreate(Bundle savedInstanceState) {

  super.onCreate(savedInstanceState);

  setContentView(R.layout.main);

  Button btn = (Button)findViewById(R.id.notify);

  btn.setOnClickListener(new View.OnClickListener() {

   public void onClick(View view) {

    TimerTask task = new TimerTask() {

     public void run() {

      notifyMe();

     }

    };

    timer.schedule(task, 5000);

   }

  });

  btn = (Button)findViewById(R.id.cancel);

  btn.setOnClickListener(new View.OnClickListener() {

   public void onClick(View view) {

    NotificationManager mgr =

    (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

    mgr.cancel(NOTIFY_ME_ID);

   }

  });

 }

 private void notifyMe() {

  final NotificationManager mgr =

   (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

  Notification note = new Notification(R.drawable.red_ball,

   "Status message!", System.currentTimeMillis());

  PendingIntent i = PendingIntent.getActivity(this, 0,

   new Intent(this, NotifyMessage.class), 0);

  note.setLatestEventInfo(this, "Notification Title",

   "This is the notification message", i);

  note.number = ++count;

  mgr.notify(NOTIFY_ME_ID, note);

This activity sports two large buttons, one to kick off a notification after a five-second delay, and one to cancel that notification (if it is active). See Figure 32-1.

Figure 32-1. The NotifyDemo activity main view

Creating the notification, in notifyMe(), is accomplished in five steps:

1. Get access to the NotificationManager instance.

2. Create a Notification object with our icon (red ball), a message to flash on the status bar as the notification is raised, and the time associated with this event.

3. Create a PendingIntent that will trigger the display of another activity (NotifyMessage).

4. Use setLatestEventInfo() to specify that, when the notification is clicked on, we are to display a certain title and message, and if that is clicked on, we launch the PendingIntent.

5. Tell the NotificationManager to display the notification.

Hence, if we click the top button, after five seconds our red ball icon will appear in the status bar. Our status message will also appear briefly, as shown in Figure 32-2.

Figure 32-2. Our notification as it appears on the status bar, with our status message

If you click on the red ball, a drawer will appear beneath the status bar. Drag that drawer all the way to the bottom of the screen to show the outstanding notifications, including our own, as shown in Figure 32-3.

Figure 32-3. The notifications drawer, fully expanded, with our notification

If you click on the notification entry in the drawer, you’ll be taken to a trivial activity displaying a message — though in a real application this activity would do something useful based upon the event that occurred (e.g., take users to the newly arrived mail messages).

Clicking on the cancel button, or clicking on the Clear Notifications button in the drawer, will remove the red ball from the status bar.