Chapter 2: Intents and Intent Filters

In This Chapter

check.png Making a match

check.png Getting the lowdown on intents and intent filters

check.png Practicing with intents on an emulator or device

check.png Stacking up your activities and tasks

You can judge people’s mental ages by the kinds of foods they eat. For example, one of my friends seeks out new tastes from strange and exotic lands. Mentally, he’s a mature adult. As for me, I like cheeseburgers and chocolate. Mentally, I’m 12 years old.

So here’s an experiment: Put a meal on a table and then put a bunch of people in the room. Each person has a list of foods that he is willing to eat. Now use the people’s lists to figure out who is (and who isn’t) willing to eat the meal.

Things can become complicated. I love cheeseburgers . . . but no toppings, please! . . . unless the topping is mayonnaise. Yes, I want fries with that, but not if they’re sweet potato fries. And above all, if the food’s slimy, or if you have to explain where it comes from, I’m not eating it.

How to Make a Match

Android has two kinds of intents — explicit and implicit:

An explicit intent names an activity class whose instance is to be launched.

An implicit intent doesn’t call for a particular activity to be launched. Instead, an implicit intent describes some work to be done. An implicit intent names an action to be taken, along with other information required to perform the action.

cross-reference.eps I cover explicit intents in Chapter 1of this minibook.

Android’s use of implicit intents is like the meal-in-a-room experiment in this chapter’s introduction. An intent is like a meal. An intent filter is like a person’s list of acceptable foods.

First, an activity sends an intent. Then the system compares that intent with other activities’ intent filters to find out which activities have filters that match the intent (which activities can perform the desired action). See Figure 2-1.

Figure 2-1: Finding an activity (or other component) to match an intent.

9781118027707-fg030201.eps

In Figure 2-1, Android checks for a match between the intent and the first of Activity P’s filters. If the intent doesn’t match Activity P’s first filter, Android checks for a match between the intent and the second Activity P filter. If Android finds a match in one of Activity P’s filters, Android marks Activity P as one possible way to fulfill the intent (one possible component that may perform the work described by the intent).

Still in Figure 2-1, Android proceeds to test Activity Q’s, Activity R’s, and Activity S’s filters. Android keeps a list of all the activities that have at least one matching filter.

If exactly one activity has a matching intent filter, that activity starts running.

If no activities have any matching intent filters, the system throws an exception.

If more than one activity has a matching intent filter, the system chooses among the matching activities, or the system displays a menu asking the user to choose among the matching activities.

technicalstuff.eps Android’s startActivity, bindService, and sendBroadcast methods all take arguments of type Intent. So a component that matches an intent can be an activity, a service, or a broadcast receiver. For most of this chapter’s examples, you can safely think activity when you read the word component. Sometimes I blur the terminology and use activity as an example, even though a more complete explanation would use the word component.

cross-reference.eps In this minibook, I cover services in Chapter 3 and broadcast receivers in Chapter 4.

The parts of an intent

Figure 2-2 shows you the parts of an implicit intent. An intent has an action, data, categories, extras, and flags. Some of these things might be omitted, but Android sets stiff restrictions about what may or may not be omitted, and when.

Each item in Figure 2-2 consists of one or more Java strings. Some typical sample values are in italics. So the string “android.intent.action.MAIN” is a sample action value, and the predeclared android.content.Intent.FLAG_ACTIVITY_NO_HISTORY constant is a sample flag value.

tip.eps The conventions surrounding Android intents make it difficult to distinguish between strings and predeclared constants. In Figure 2-2, “android.intent.action.MAIN” is a string and android.content.Intent.FLAG_ACTIVITY_NO_HISTORY is a predeclared constant (a static final field named FLAG_ACTIVITY_NO_HISTORY in the android.content.Intent class). Oddly, the dots in the string “android.content.intent.MAIN” don’t mean very much. There’s no member named MAIN in any android.content.intent class.

remember.eps You can use strings, constants, or references to string resources in Java source files, but you can use only strings in XML documents.

Figure 2-2: An Android intent.

9781118027707-fg030202.eps

Figure 2-2 indicates that an intent’s data parts come in two flavors — URI and MIME type. An intent may have neither of these, one of the two, or both.

Still looking at Figure 2-2, the MIME in MIME type stands for Multipurpose Internet Mail Extensions. The original MIME standard describes the kinds of data that can be encoded and sent in e-mail messages. For example, when your e-mail program receives a message with Content-Type: text/ html, your program interprets the message as an HTML document and displays the content the way web browsers display web pages. When a program receives bits declared with MIME type audio/mp3, image/jpeg, or application/zip, the program interprets the bits as sounds, images, or ZIP files. In each case, the word before the slash is a top-level type, and the word after the slash is a subtype. Familiar top-level-type/subtype pairs include text/plain, text/html, text/xml, image/png, image/jpeg, and image/gif.

tip.eps Many of the names in Android’s SDK use the shortened term type instead of the full name MIME type.

warning_bomb.epsUnlike the use of MIME types in ordinary e-mail handling, the matching of Android’s MIME types is case-sensitive. So, for example, TEXT/PLAIN in an intent doesn’t match text/plain in a filter. Android’s developer guidelines recommend using only lowercase letters in the names of MIME types.

A Uniform Resource Locator (URL) is any familiar web address that you dictate when someone asks, “Where can I find that on the web?” A Uniform Resource Identifier (URI) looks like a URL, but URIs describe more than just web pages. Every URL is a URI, but a URI isn’t necessarily a URL.

In Android, a URI has from one to four parts, depending on how you count and on what you choose to omit. Figure 2-3 has some examples.

Figure 2-3: Uniform Resource Identifiers.

9781118027707-fg030203.eps

technicalstuff.eps The kind of URI that I illustrate in Figure 2-3 is a hierarchical URI. The alternative to a hierarchical URI is an opaque URI. An opaque URI, such as tel:6502530000 or mailto:[email protected], has a single colon instead of ://. Also, in an opaque URI, what comes after the colon varies widely depending on the scheme. In fact, what comes after the colon in an opaque URI is the URI’s scheme-specific part. So, for example, in the URI mailto:[email protected], the scheme is mailto and the scheme-specific part is [email protected]. An opaque URI has neither an authority nor a path.

The parts of an intent filter

Your app creates an intent and then calls startActivity(intent). Then what happens? Android has a list of activities installed on the device, and each activity has its intent filters. Android tries to match the intent with each intent filter. If an activity has any matching intent filters, that activity goes on the list of possible responders to the startActivity method call.

An activity’s non-matching filters don’t harm the activity’s chances of going on the list. Even if only one of an activity’s filters matches, the activity still goes on the list of possible responders.

So what constitutes a match between an intent and an intent filter? Funny you should ask! The answer is far from simple.

An intent filter can have actions, data entries, and categories. (Unlike an intent, an intent filter can have more than one action and more than one data entry. Like an intent, an intent filter can have more than one category.) Intent filters don’t have extras or flags. (See Figure 2-4.)

Figure 2-4: The parts of an intent filter.

9781118027707-fg030204.eps

To find a match between an implicit intent and an intent filter, Android performs three tests:

Android tests the intent’s action for a match with the filter’s actions.

Android tests the intent’s categories for a match with the filter’s categories.

Android tests the intent’s data for a match with the filter’s data.

Android’s rules for matching an intent’s action with a filter’s action are fairly straightforward. And the rules for matching the intent’s categories with the filter’s categories are okay. But neither of these rules is a memorable, one-sentence slogan. And the rules for matching the intent’s data with the filter’s data are quite complicated. Unfortunately, the official documentation about filter matching (http://developer.android.com/guide/topics/intents/intents-filters.html) is ambiguous and contains some errors.

So to help you understand how intents match intent filters, I take a multi­faceted approach. (That’s a fancy way to say that I explain matching a few times in a few different ways.)

Matching: The general idea using a (silly) analogy

Two kinds of people sign up to participate in a speed-dating event. On one side of the room, each participant represents a part of an Android intent. (So one person is an action, the next two people are categories, and so on. I warned you that this analogy would be silly!) On the other side of the room, each participant represents a part of a filter. (See Figure 2-5.)

Figure 2-5: Intent elements and filter elements in a speed-dating event.

9781118027707-fg030205.eps

Like all dating situations, the room might be imbalanced. The filter might have more actions or more categories. The intent might have more data. It’s almost never a fairy tale, one-to-one mix of people.

In this arena, some people are needier than others. For example, on the intent side, you have an action that absolutely insists on finding a match among the filters. On the filter side, you have a category that’s speed dating only to keep a friend company. This nonchalant category doesn’t need to find a match among the intent’s categories.

As a quick (and not entirely accurate) rule, the entire intent matches the entire filter if and only if each needy person finds a match. Anyone who isn’t needy doesn’t have to be matched. That is, the whole speed-dating event is successful even if no one who’s along only for the ride finds a match. Non-needy parts don’t derail the overall match between the intent and the filter.

So who’s needy and who isn’t? Figure 2-5 gives you a rough idea.

remember.eps The matching rules in Figures 2-5, 2-6, and 2-7 are general guidelines. The official rules include some important exceptions. For more info, see the next few sections.

The real story

An intent filter has three parts — actions, categories, and data. Android tests each part to determine whether a particular intent matches a particular filter. Each part consists of one or more Java strings. So, roughly speaking, an intent’s part matches a filter’s part if and only if intent_part.equals(filter_part). In this situation, equals is Java’s String comparison method.

In the preceding paragraph I write “roughly speaking” because Android’s rules for matching actions aren’t quite the same as the rules for matching categories, which in turn are different from the rules for matching data entries. How do you decide whether the one action in an intent matches the many actions in a filter? And what do you do with each part of a URI? Stay tuned, because the next several sections answer these questions.

remember.eps In the next few sections, be aware of the many kinds of matching — an intent with a filter, an intent with an activity, an intent action with a filter action, a scheme with an entire URI, and several other kinds of matching.

Java methods and XML elements

An intent’s Java methods include the following:

setAction: Sets the intent’s action.

addCategory: Adds a category to the intent.

setData: Sets the intent’s URI and removes the intent’s MIME type (if the intent has a MIME type).

setType: Sets the intent’s MIME type and removes the intent’s URI (if the intent has a URI).

setDataAndType: Sets both the intent’s URI and the intent’s MIME type. According to the docs, “This method should very rarely be used.”

You can describe an intent filter in an AndroidManifest.xml document or in Java code. In an AndroidManifest.xml document, the <intent-filter> element has <action>, <category>, and <data> subelements.

<action android:name=”string” />

<category android:name=”string” />

<data android:scheme=”string”

      android:host=”string”

      android:port=”string”

      android:path=”string”

      android:pathPattern=”string”

      android:pathPrefix=”string”

      android:mimeType=”string” />

The intent methods and the data element’s attributes aren’t parallel. For example, with an intent’s setAction method, you set an intent’s action (if you want the intent to have an action). But with a filter’s <action> element, you add one of possibly many actions to the filter. With an intent’s setData method, you set an intent’s URI (if you want the intent to have a URI). But with a filter’s <data> elements, you add individual pieces of a URI.

You typically set a filter’s values in the AndroidManifest.xml file. But in Java code, the android.content.IntentFilter class has lots of useful methods. I list a few here:

addAction: Adds an action to the filter.

addCategory: Adds a category to the filter.

addDataScheme: Adds a scheme to the filter.

addDataAuthority: Adds an authority to the filter.

addDataPath: Adds a path to the filter.

addDataType: Adds a MIME type to the filter.

As was the case with the intent methods and the data element’s attributes, the intent methods and the filter methods aren’t parallel. An intent’s set Action method does the obvious — it sets an intent’s action (if you want the intent to have an action). A filter’s addAction method, however, lets you add one of possibly many actions to the filter. An intent’s setData method sets an intent’s URI (if you want the intent to have a URI). A filter’s addDataScheme, addDataAuthority, and addDataPath methods, on the other hand, let you separately add pieces of a URI.

Matching actions

According to Figure 2-5, an intent’s action must be matched with one of the filter’s actions. That makes sense because an intent’s action says, “I want a component that can do such-and-such.” And the filter’s action says, “I can do such-and-such.” The filter might have other actions (be able to do other things), but having additional filter actions doesn’t prevent an intent from matching with a filter.

Exactly what is an action? The simplest answer is that an action is a string. You can create your own action string “thisismyaction” or “allmy code.intent.action.DO_THIS” — Android’s docs recommend the latter form. But Android also has a bunch of standard actions — actions reserved for certain kinds of work. For example, when a developer creates an activity that can display something (a document, a web page, an image, or whatever) the developer includes “android.intent.action.VIEW” in the activity’s filter. Then, when you want someone else’s activity to display something, you put “android.intent.action.VIEW” (or the constant android.content.Intent.ACTION_VIEW whose value is “android.intent.action.VIEW”) in your intent.

Table 2-1 lists some of my favorite standard actions.

Table 2-1	Some Standard Actions for Activities

Table 2-1	Some Standard Actions for Activities

Table 2-1	Some Standard Actions for Activities

cross-reference.epsFor a complete list of Android’s standard actions, visit http://developer.android.com/reference/android/content/Intent.html.

Here’s a useful experiment:

1. Create a new Android project with two activities — the main activity created by Eclipse and a second OtherActivity.java activity.

2. Add the following activity element to the project’s AndroidManifest.xml file:

<activity android:name=”.OtherActivity”>

  <intent-filter>

    <action

      android:name=”com.allmycode.action.MY_ACTION” />

    <category

      android:name=”android.intent.category.DEFAULT” />

  </intent-filter>

</activity>

3. In the main activity, add the following code:

final String THE_ACTION =

    “com.allmycode.action.MY_ACTION”;

Intent intent = new Intent();

intent.setAction(THE_ACTION);

startActivity(intent);

4. Using Eclipse’s Graphical Layout editor, change the layout of the other activity.

Any change is okay. The only reason for changing the other activity’s layout is to help you recognize which of the two activities (the main activity or the other activity) is on the emulator’s screen.

5. Run the project.

As soon as your emulator executes the code in Step 3, Android launches the other activity. The intent’s “com.allmycode.action.MY_ACTION” matches the filter’s “com.allmycode.action.MY_ACTION”, so the other activity starts running.

remember.eps Real Android developers use a standard action (such as the actions in Table 2-1), or they make up dotted action names, such as “com.allmy code.action.MY_ACTION”. Real developers type that “com.allmy code.action.MY_ACTION” string in the AndroidManifest.xml file (because they must). But in the Java code, real developers create a constant value to represent the string (because it’s good programming practice).

cross-reference.eps The category element in Step 2 of this section’s instructions is an anomaly that I cover in the later section “The fine print.” If you don’t want to skip to that section, simply add the category “android. intent.category.DEFAULT” to each filter in your Android}Manifest.xml file.

Continuing the experiment . . .

6. Comment out (or delete) the following element from your project’s AndroidManifest.xml file:

    <action

      android:name=”com.allmycode.action.MY_ACTION” />

7. Run your project again.

When your emulator executes the Java code in Step 3, your app crashes. The filter has no action matching your intent’s “com.allmycode.action.MY_ACTION”, and (in all likelihood) no other activity on your emulator has a filter containing “com.allmycode.action.MY_ACTION”.

tip.eps In Step 7, your app intentionally crashes. Crashes make good learning experiences, but users don’t appreciate such learning experiences. To avoid the kind of disaster you see in Step 7, call the PackageManager class’s queryIntentActivities method before attempting to call startActivity. Alternatively, you can put your startActivity call in a try/catch block with the ActivityNotFoundException:

try {        

  startActivity(intent);

} catch (ActivityNotFoundException e) {

  e.printStackTrace();

}

This code tells Java to call startActivity. If Android can’t start an activity (that is, if Android can’t find an activity to match the intent), Java jumps to the statement e.printStackTrace(), which displays error information in Eclipse’s LogCat view. After displaying the information, Java marches on to execute whatever code comes after the attempt to call startActivity. Therefore, the app doesn’t crash.

cross-reference.eps For more on try/catch blocks, see Book II, Chapter 3.

8. Uncomment the element that you commented out in Step 6.

9. Modify the other activity’s element in the AndroidManifest.xml file as follows:

<activity android:name=”.OtherActivity”>

  <intent-filter>

    <action

      android:name=”com.allmycode.action.MY_ACTION” />

    <action

      android:name=”com.allmycode.action.X_ACTION” />

    <category

      android:name=”android.intent.category.DEFAULT” />

  </intent-filter>

</activity>

10. Run the project again.

When your emulator executes the Java code in Step 3, Android launches the other activity. The intent’s “com.allmycode.action.MY_ACTION” matches the filter’s “com.allmycode.action.MY_ACTION”, and the filter’s additional “com.allmycode.action.X_ACTION” doesn’t require a match.

tip.eps Following this example’s steps for each intent and filter that you want to test can become very tedious. So to help you test matches, I’ve created a special Android app. For details, see the “Practice, Practice, Practice” section, later in this chapter.

Matching categories

According to Figure 2-5, each of an intent’s categories must be matched with one of the filter’s categories. That makes sense because an intent’s category says, “I want a component of such-and-such kind.” And the filter’s category says, “I’m a such-and-such kind of component.” The filter might have other categories, but having additional filter categories doesn’t prevent an intent from matching with a filter.

Exactly what is a category? Like an action, a category is a string. You can create your own category string “thisismycategory” or “allmycode.intent.category.THIS_KIND”, and Android’s docs recommend the latter form. But Android also has a bunch of standard categories — categories reserved for certain kinds of components. Table 2-2 lists some of my favorites.

Table 2-2	Some Standard Categories

Table 2-2	Some Standard Categories

Consider the kitty-cat intent created with the following Java code:

final String THE_ACTION =

        “com.allmycode.action.MY_ACTION”;

final String THE_CATEGORY =

        “com.allmycode.category.KITTY”;

Intent intent = new Intent();

intent.setAction(THE_ACTION);

intent.addCategory(THE_CATEGORY);

startActivity(intent);

This kitty-cat intent matches a filter with the following XML code:

<activity android:name=”.OtherActivity”>

  <intent-filter>

    <action

      android:name=”com.allmycode.action.MY_ACTION” />

    <category

      android:name=”com.allmycode.category.KITTY” />

    <category

      android:name=”android.intent.category.DEFAULT” />

  </intent-filter>

</activity>

The kitty-cat intent also matches the following intent because (in the language of speed dating) filter categories aren’t needy.

<activity android:name=”.OtherActivity”>

  <intent-filter>

    <action

      android:name=”com.allmycode.action.MY_ACTION” />

    <category

      android:name=”com.allmycode.category.KITTY” />

    <category

      android:name=”Otto.Schmidlap” />

    <category

      android:name=”android.intent.category.DEFAULT” />

  </intent-filter>

</activity>

The kitty-cat intent does not match the following intent because an intent’s categories are needy:

<activity android:name=”.OtherActivity”>

  <intent-filter>

    <action

      android:name=”com.allmycode.action.MY_ACTION” />

    <category

      android:name=”Otto.Schmidlap” />

    <category

      android:name=”android.intent.category.DEFAULT” />

  </intent-filter>

</activity>

Matching data

Figure 2-5 illustrates an interesting relationship between an intent’s data and a filter’s data:

If an intent has a URI or if a filter has a URI, one of the filter’s URIs must match the intent’s URI.

If an intent has a MIME type or if a filter has a MIME type, one of the filter’s MIME types must match the intent’s MIME type.

These rules have some corollaries:

An intent without a URI cannot match a filter without a URI. A filter without a URI cannot match an intent without a URI.

An intent without a MIME type cannot match a filter without a MIME type. A filter without a MIME type cannot match an intent without a MIME type.

How does all this stuff about URIs and MIME types make sense? The deal is, data doesn’t perform the same role as an action or a category in matching a filter with an intent. Imagine that an intent announces, “I want a component to perform android.intent.action.VIEW,” and a certain activity’s filter announces, “I can perform android.intent.action.VIEW.” The intent doesn’t care if the filter announces that it can perform other actions.

But what if an intent announces, “I want a component to handle the URI tel:6502530000”? (The URI tel:6502530000 places a call to Google’s corporate headquarters in Mountain View, California.) An appropriate filter contains the tel scheme. (See Figure 2-6.) Now imagine another intent with no tel: URI and a filter whose only scheme is the tel scheme. (Again, see Figure 2-6.) In this case, the filter says, “I can do something useful with a telephone number, and when I’m invoked, I expect to receive a telephone number.” If the intent has no tel: URI, a match isn’t appropriate.

So the coupling between an intent’s and a filter’s data is stronger than the coupling between actions or the coupling between categories. With the URI part of the data, both the intent and the filter are needy. The same is true of the data’s MIME types.

Figure 2-6: Matching the tel scheme.

9781118027707-fg030206.eps

The following intent and filter form a match:

final String THE_ACTION =

        “com.allmycode.action.MY_ACTION”;

Intent intent = new Intent();

intent.setAction(THE_ACTION);

intent.setData(Uri.parse(“http:”));

startActivity(intent);

<intent-filter>

  <action

    android:name=”com.allmycode.action.MY_ACTION” />

  <category

    android:name=”android.intent.category.DEFAULT” />

  <data android:scheme=”http” />

  <data android:scheme=”mymadeupscheme” />

</intent-filter>

The same intent with a slightly modified filter does not form a match because the set of MIME types in a filter is needy:

<intent-filter>

  <action

    android:name=”com.allmycode.action.MY_ACTION” />

  <category

    android:name=”android.intent.category.DEFAULT” />

  <data android:scheme=”http” />

  <data android:scheme=”mymadeupscheme” />

  <data android:mimeType=”text/html” />

</intent-filter>

To match this modified filter, you need either of the following intents:

final String THE_ACTION =

        “com.allmycode.action.MY_ACTION”;

Intent intent = new Intent();

intent.setAction(THE_ACTION);

intent.setDataAndType(Uri.parse(“http:”), “text/html”);

startActivity(intent);

final String THE_ACTION =

        “com.allmycode.action.MY_ACTION”;

Intent intent = new Intent();

intent.setAction(THE_ACTION);

intent.setDataAndType

    (Uri.parse(“mymadeupscheme:”), “text/html”);

startActivity(intent);

Finally, the following intent and filter form a match because the MIME type in the intent matches one of the MIME types in the filter:

final String THE_ACTION =

        “com.allmycode.action.MY_ACTION”;

Intent intent = new Intent();

intent.setAction(THE_ACTION);

intent.setType(“text/html”);

startActivity(intent);

<intent-filter>

  <action

    android:name=”com.allmycode.action.MY_ACTION” />

  <category

    android:name=”android.intent.category.DEFAULT” />

  <data android:mimeType=”abc/xyz” />

  <data android:mimeType=”text/html” />

</intent-filter>

Matching parts of the data

The “Java methods and XML elements” section, earlier in this chapter, lists methods and XML elements. With an intent’s setData method, you set an intent’s URI (if you want the intent to have a URI). With a filter’s <data> elements, you add individual pieces of a URI. The filter’s pieces don’t have to fit together. For example, the following intent and filter form a match:

final String THE_ACTION =

        “com.allmycode.action.MY_ACTION”;

Intent intent = new Intent();

intent.setAction(THE_ACTION);

intent.setData(Uri.parse(“abc://example.com:2222”));

startActivity(intent);

<intent-filter>

  <data android:scheme=”xyz” android:host=”example.com” />

  <data android:port=”2222” />

  <action

    android:name=”com.allmycode.action.MY_ACTION” />

  <category

    android:name=”android.intent.category.DEFAULT” />

  <data android:scheme=”abc” />

</intent-filter>

A filter can have schemes “abc” and “xyz”, and authority “example.com”. Then the filter’s data matches both intent data “abc://example.com” and intent data “xyz://example.com”. This works even if you lump “xyz” and “example.com” in the same <data> element.

With a filter’s addDataScheme, addDataAuthority, and addDataPath methods, you separately add pieces of a URI. For example, the following intent and filter form a match:

final String THE_ACTION =

        “com.allmycode.action.MY_ACTION”;

Intent intent = new Intent();

intent.setAction(THE_ACTION);

intent.setData(Uri.parse(“abc://example.com:2222”));

final IntentFilter filter = new IntentFilter();

filter.addAction(THE_ACTION);

// Constant com.content.Intent.CATEGORY_DEFAULT has

//   value “android.intent.category.DEFAULT”

filter.addCategory(Intent.CATEGORY_DEFAULT);

filter.addDataScheme(“abc”);

filter.addDataScheme(“xyz”);

filter.addDataAuthority(“example.com”, “2222”);

At this point, a few observations are in order.

An intent has three similar methods — setData, setDataAndType, and setType. You call setData for an intent with a URI but no MIME type. You call setType for an intent with a MIME type but no URI. You call setDataAndType only for an intent with both a URI and a MIME type.

You don’t pass a string to the setData method or to the first parameter of the setDataAndType method. Instead, you pass an instance of the android.net.Uri class. You do this by applying the method Uri.parse to a string of characters.

Here’s a silly but important detail: A call to intent.setData(Uri.parse(“http:”)) with a colon after http matches the filter element <data android:scheme=”http” /> without a colon after the http. Other combinations of colon/no-colon for a URI scheme fail to make a match.

Matching URIs

Figure 2-5 illustrates an imaginary speed-dating event for the parts of an intent and an intent filter. The figure doesn’t address the matching of one URI with another. So imagine that the URIs in Figure 2-5 bring their darling little children (their schemes, authorities, and paths) to the speed-dating event. As the evening begins, the kids go off to a separate room for a speed-dating event of their own. (Sure, they’re too young to date. But it’s good practice for adolescence.) Figure 2-7 illustrates the neediness situation in the kids’ event.

The situation with the URI’s kids is similar to the situation with all data. Everybody’s happy as long as each thing on the intent side matches something on the filter side. For example, the following intent and filter form a match:

final String THE_ACTION =

        “com.allmycode.action.MY_ACTION”;

Intent intent = new Intent();

intent.setAction(THE_ACTION);

intent.setData(Uri.parse(“abc://example.com:2222”));

startActivity(intent);

<intent-filter>

  <action

    android:name=”com.allmycode.action.MY_ACTION” />

  <category

    android:name=”android.intent.category.DEFAULT” />

  <data android:scheme=”abc” />

</intent-filter>

Figure 2-7: Among the parts of a URI, who needs to meet someone?

9781118027707-fg030207.eps

But with the same intent, the following filter isn’t a match:

<intent-filter>

  <action

    android:name=”com.allmycode.action.MY_ACTION” />

  <category

    android:name=”android.intent.category.DEFAULT” />

  <data android:scheme=”abc” />

  <data android:host=”example.com” />

  <data android:port=”2222” />

  <data android:path=”/some/stuff” />

</intent-filter>

The fine print

With all the fuss about filter matching in the previous sections, you’d think the issue was covered and done with. But the work is never done. Here’s a list of filter matching’s most important gotchas and exceptions:

Android treats activities differently from other components (such as services and broadcast receivers).

You can create an implicit intent with no actions. If you do, a call to startActivity(intent) doesn’t find a match among any activity filters. However, calls to sendBroadcast(intent) or to bindService(intent, ...) may find matches.

warning_bomb.epsAccording to the Android docs, “... an Intent object that doesn’t specify an action automatically passes the test — as long as the filter contains at least one action.” As far as I can tell, this statement in the docs is incorrect.

With respect to categories, Android treats activities differently from other components.

When you try to start an activity, Android behaves as if the intent contains the “android.intent.category.DEFAULT” category. (Android does this even if you don’t execute code to add that category to the intent.) Because of this, an activity filter without the “android.intent.category.DEFAULT” category never matches an intent. Broadcast receivers and services don’t suffer from this anomaly.

tip.eps The activity that starts when the user first presses an app’s icon is the app’s main activity. A main activity’s filter normally contains the action “android.intent.action.MAIN” and the category “android.intent.category.LAUNCHER”. If you want an activity to function only as a main activity (and never be started by an app’s call to startActivity), you can safely omit “android.intent.category.DEFAULT” from the activity’s filter.

Flip back to Figure 2-3 to see the kinds of URIs you can create. A URI with an authority must have a scheme, and a URI with a path must have an authority and a scheme. Also, a port without a host is ignored. So the following strings are not valid URIs:

example.com — has an authority but no scheme.

http:///folder/subfolder — has a path but no authority.

technicalstuff.eps The official Android docs provide the following loophole: “ . . . if a host is not specified, the port is ignored.” So a URI like http://:2000/folder is strange but valid. I’ve created such URIs in captivity, but I’ve never encountered one in the wild.

A filter URI with nothing but a scheme matches any intent URI with the same scheme. Take, for example, the intent URI to call Google’s corporate headquarters, tel:6502530000. An appropriate filter probably contains the tel scheme but not the number 6502530000. (A filter whose sole URI is tel:6502530000 can call only Google’s corporate headquarters. The filter would be useful only for an Unsatisfied Google Customer app.)

In the same way, a filter URI with a scheme, an authority, and no path matches any intent filter with the same scheme and the same authority.

The schemes content and file get special treatment. If the intent’s URI has scheme content or scheme file, and the intent has a MIME type, you can omit the scheme from the filter. (You must still have a match between the intent’s MIME type and one of the filter’s MIME types.) Someday this rule will make sense to me.

Certain parts of the data may contain wildcards or simplified regular expressions. Here are a few examples:

• The type text/* matches text/plain. The type text/* also matches text/html and text/whatever. The type text/* matches text/ (with a slash and no subtype) but does not match text (with no slash and no subtype).

• The type */* matches text/plain. The type */* also matches image/jpeg, and so on.

• The type * (one wildcard with no slash) and */html don’t seem to match anything.

• The type text/ht* doesn’t match text/html. (With a top-level type or a subtype, the wildcard must be “all or nothing.”)

• Paths use a simplified regular expression form. (In other words, paths can include wildcards and other funky symbols.) For example, the intent URI http://example.com/folder matches the filter URI with scheme http, authority example.com, and path pattern /fol.*. In the AndroidManifest.xml file, the data element’s android:path, android:pathPrefix, and android:pathPattern attributes distinguish among the various possibilities.

cross-reference.eps For more information about path expressions, visit http://developer.android.com/guide/topics/manifest/data-element.html.

• With the exception of the host name, the strings in an intent and its filter are case-sensitive. So text/html doesn’t match TEXT/HTML, and HTTP doesn’t match http. But http://example.com matches http://EXAMPLE.com. Android’s docs recommend using mostly lowercase letters.

Practice, Practice, Practice

If I had a nickel for every time I misinterpreted something in Android’s Intent Filters documentation, I’d have enough to fill my tank with gas. (That’s pretty impressive, isn’t it?) I want to believe that this chapter’s sections on intent and filter matching are clear and unambiguous. But in my heart, I know that almost all spoken-language sentences are moving targets. Take, for example, the following sentences:

Put Mommy in the car behind us.

I want David Copperfield to read.

I’ll put the bandage on myself.

Everything shouldn’t be blue.

Chew one tablet three times a day until finished.

I saved everyone five dollars.

Cars towed at owner’s expense.

Our cream is so gentle that it never stings most people, even after shaving.

I hope someday that you love me as much as Amy.

If he were to learn that wild bears are related to dogs, and never hurt people, then he’d be happier.

The best test of your understanding is not the way you nod while you read this book’s paragraphs. Instead, the best test is when you try your own examples on an emulator or a device. If you can accurately predict the results much of the time, you understand the subject.

Unfortunately, testing intent and filter matching can be tedious. For every change, you have to edit Java code, then edit the AndroidManifest.xml file, and then reinstall your app. Some time ago, after many hours of such testing, I was “mad as hell and I wasn’t going to take it anymore.” I wrote an app to test filter matches one after another without modifying files or reinstalling anything. I named it the Intentsity app (because, as an author, I’m tired of worrying about things being spelled correctly). Needless to say, the app is available for your use through this book’s website — www.allmycode.com/android. (You can thank me later.)

The upper half of the app’s main screen is shown in Figure 2-8.

Figure 2-8: A screen for entering intent and intent filter strings.

9781118027707-fg030208.tif

The Intentsity app’s screen has an Intent part and a Filter part. Both parts have EditText fields for filling in String values. Each EditText field represents an Intent instance method or an IntentFilter instance method. (For a list of such methods, see the “Java methods and XML elements” section, earlier in this chapter.)

technicalstuff.eps Android has no features for setting an activity’s intent filter using Java code. But you can create another component — a broadcast receiver — and set the broadcast receiver’s filter using Java code. (You use the IntentFilter method calls described in the “Java methods and XML elements” section.) Accordingly, my Intentsity app tests the values you type in the EditText fields by attempting to communicate with a broadcast receiver. The fields in the lower part of the app (the Filter fields) match with the Java methods for creating an IntentFilter object, not with the attributes in the AndroidManifest.xml document. To keep things as faithful as possible to Android’s real behavior, my app respects the fact that a broadcast receiver’s filter can do without the category “android.intent.category.DEFAULT”. So when you move from the Intentsity app to your own project, remember to add “android.intent.category.DEFAULT” to your activities’ filters.

The “Java methods and XML elements” section, earlier in this chapter, lists methods like setAction and addCategory. Methods beginning with the set are for things like an intent’s action because an intent can’t have more than one action. Methods beginning with the add are for things like an intent’s category because an intent can have more than one category.

Figure 2-8 shows a New Intent Category button. When you click this button, the app creates an additional addCategory row.

Figure 2-9 shows the bottom of the Intentsity app’s scrolling screen.

Figure 2-9: Press Test to check for a match.

9781118027707-fg030209.tif

After filling in some EditText fields, click the Test button, and the app does what it does best:

The app calls the Intent class’s methods to compose an intent from your Intent fields’ entries.

The app calls the IntentFilter class’s methods to compose a filter from your Filter fields’ entries.

The app calls registerReceiver(myReceiver, filter) to create a broadcast receiver with the new filter.

The app calls sendBroadcast(intent) to shout out to all the system’s broadcast receivers.

If the receiver’s filter matches your intent, the receiver displays a screen like the one in Figure 2-10.

Figure 2-10: It’s a match!

9781118027707-fg030210.tif

With or without a match, the app displays toString versions of your intent and intent filter. Figure 2-11 shows the display for a failed attempt to match.

Figure 2-11: The app displays the values in an intent and a filter.

9781118027707-fg030211.tif

warning_bomb.epsThe Intentsity app doesn’t want you to type variables in the EditText fields. In your own Java code, the call setAction(Intent.ACTION_VIEW) sets the intent’s action to the string “android.intent.action.VIEW”. But in the Intentsity app, typing Intent.ACTION_VIEW in the topmost field sets the intent’s action to the string “Intent.ACTION_VIEW”, which is not equal to (and therefore doesn’t match) a filter string “android.intent.action.VIEW”.

With the Intentsity app, you can test your understanding of intents and intent filters. But you can also examine the app’s source code for some tips and tricks. The following sections have a few highlights.

No magic

I keep things as simple as possible in order to turn your EditText entries into an intent and a filter. The simplicity guards against discrepancies between the Intentsity app’s behavior and the behavior you get when you code your own app. In the following code snippet, I grab text from EditText fields and plug that text into Android’s set and add methods. To grab text from the addCategory fields, I loop through the fields that the user has created.

private Intent createIntentFromEditTextFields() {

  String theAction =

          actionText.getText().toString().trim();

  Intent intent = new Intent();

  if (theAction.length() != 0) {

    intent.setAction(theAction);

  }

  if (intentCategoriesLayout != null) {

    int count =

            intentCategoriesLayout.getChildCount();

    for (int i = 0; i < count; i++) {

      String cat =

        ((EditText) ((ViewGroup) intentCategoriesLayout

          .getChildAt(i))... // More code goes here

          .getText().toString().trim();

      if (cat.length() != 0) {

        intent.addCategory(cat);

      }

    }

  }

  // Et cetera, et cetera, ...

  return intent;

}

Using a ScrollView

The app’s main screen takes up more space than is typically available on a mobile phone. So I enclose the whole screen in a ScrollView. As its name suggests, a ScrollView lets the user slide things onto the screen as other things slide off. The big restriction on a ScrollView is that a ScrollView may contain only one direct child. So in the following code, you can’t put another View element between the end tags </LinearLayout> and </ScrollView>:

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

<ScrollView xmlns:android=

   “http://schemas.android.com/apk/res/android”

  android:id=”@+id/scrollView1”

  android:layout_width=”match_parent”

  android:layout_height=”wrap_content”>

  

  <LinearLayout android:orientation=”vertical”

    android:layout_width=”match_parent”>

      <!-- More layout stuff within

           the LinearLayout goes here.

           This stuff may include

           additional view elements. -->

  </LinearLayout>

    <!-- No additional view elements may appear here. -->

</ScrollView>

Defining a layout in Java code

With my Intentsity app, a user can add new elements to an existing layout. Android has a ListView class that handles runtime additions to a layout. With the Intentsity app, though, I found Android’s addView and removeView methods easier to wield. The addView and removeView methods allow you to manipulate layouts dynamically in Java code rather than statically in XML resource files. The following excerpt gives you a taste of this technique:

void addRow(final LinearLayout layout,

        String label, String hintStr,

        boolean addRadioGroup) {

    LinearLayout.LayoutParams rowLayoutParams =

            new LinearLayout.LayoutParams(

            LinearLayout.LayoutParams.FILL_PARENT,

            ViewGroup.LayoutParams.WRAP_CONTENT);

    LinearLayout.LayoutParams editTextLayoutParams =

            new LinearLayout.LayoutParams(

            180, LinearLayout.LayoutParams.WRAP_CONTENT);

    LinearLayout row = new LinearLayout(this);

    row.setOrientation(LinearLayout.HORIZONTAL);

    row.setGravity(Gravity.CENTER_VERTICAL);

    TextView textView = new TextView(this);

    textView.setText(label);

    row.addView(textView);

    EditText editText = new EditText(this);

    editText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);

    editText.setHint(hintStr);

    editText.setLayoutParams(editTextLayoutParams);

    if (!isFirstTime) {

        editText.requestFocus();

    }

    row.addView(editText);

    // Blah, blah, blah,...

    Button button = new Button(this);

    button.setTextSize(10);

    button.setTypeface(null, Typeface.BOLD);

    button.setText(“X”);

    button.setTypeface(

            Typeface.SANS_SERIF, Typeface.BOLD);

    button.setOnClickListener(new OnClickListener() {

        public void onClick(View view) {

            layout.removeView(

                    (LinearLayout) view.getParent());

        }

    });

    row.addView(button);

    row.setLayoutParams(rowLayoutParams);

    layout.addView(row);

}

Activities and Stacks

The activity that starts when the user first presses an app’s icon is the app’s main activity. When the user first presses the app’s icon, this main activity becomes the root activity in a new task stack. (At first, the root activity is the only activity on the task stack. As the stack grows, the root activity normally remains at the bottom of the stack.)

cross-reference.eps To learn more about tasks, see Chapter 1 of this minibook.

A call to startActivity in Activity A’s code can launch an activity (Activity B) belonging to a different app. When this happens, Android launches Activity B by pushing Activity B onto Activity A’s task stack. So one task may contain activities belonging to more than one application.

But each task is associated with a single application — namely, the app containing the task’s root activity. And typically, that single application has an icon on the device’s Home screen or Apps screen. (See Figure 2-12.)

Figure 2-12: Each task has an app, which (in turn) has an icon.

9781118027707-fg030212.eps

The user can switch between tasks by pressing the Home button and then pressing the icon whose app is associated with the desired task. In this sense, the user has task-level control over the device’s behavior. The user doesn’t have activity-level control. (For example, the user can’t routinely return to an activity that’s in the middle [rather than the top] of a task stack.)

The activity stack

In most of Android’s documentation, stack doesn’t refer to a task stack. Instead, the docs refer to the activity stack (or the back stack). The activity stack is the system’s ever-changing, last-in/first-out history list.

You can think of the activity stack as a list of activities. But more precisely, the items in the activity stack are tasks, and the tasks themselves contain the activities.

When the user presses the Back button, Android pops an activity off the activity stack. (That is, Android pops an activity off whatever task is at the top of the activity stack.)

When the user presses Home and then presses an icon to return to an older task, Android reorders the activity stack so that the task being revisited is on top of the activity stack. (Because of this reordering, the activity stack isn’t strictly a last-in/first-out list. For that matter, an individual task doesn’t always behave like a last-in/first-out list either.)

Imagine starting an emulator by running an application in Eclipse. (Call this application App A.) With App A showing in the emulator, pressing Home brings you to Android’s own launcher task (the Home screen). Then, when you press a second application’s icon (the icon representing App B), the activity stack contains three tasks, as shown at the start of Figure 2-13.

Figure 2-13: Tasks on the activity stack.

9781118027707-fg030213.eps

Because the activity stack is normally a last-in/first-out list, Android’s own launcher task is sandwiched between the two application tasks.

With three tasks on the emulator’s activity stack, run another app (App C) in Eclipse. Starting an app from Eclipse doesn’t involve Android’s launcher, so the emulator’s activity stack now contains four tasks, as shown in Figure 2-13.

Pressing the Back button pops App C off the stack and returns you to App B. Pressing the Back button a second time pops App B off the stack and returns you to the launcher. (Again, see Figure 2-13.)

Pressing the Back button a third time keeps you at the launcher because you can’t back up beyond the Home screen.

Fly the flag

An intent can contain six kinds of information:

The name of a component to be invoked (making the intent an explicit intent rather than an implicit intent)

A set of extras

One action

A set of categories

Some data (one URI and/or one MIME type)

A set of flags

I cover the first two kinds of information in Chapter 1 of this minibook, and I beat the third, fourth, and fifth kinds of information to death in this chapter’s previous sections. So this section deals with the sixth kind of information — namely, flags.

A flag tells Android how to deal with a component. In most cases, the component is an activity that you’re launching by calling startActivity or startActivityForResult. A typical programming pattern is as follows:

Intent intent = new Intent();

intent.setAction(someActionString);

intent.addCategory(someCategoryString);

intent.addFlags(int_value_representing_one_or_more_flags);

startActivity(intent);

Examples of Android’s standard flags include the following:

Intent.FLAG_ACTIVITY_NO_ANIMATION: When starting the new activity, don’t animate the activity’s entrance. That is, if the norm is to slide the new activity over the existing activity, don’t slide it. Just make the new activity “poof” onto the screen.

Intent.FLAG_ACTIVITY_NO_HISTORY: Start the new activity, but destroy this new activity as soon as the user navigates away from it. For example, if the user presses Home and then returns to this task, restore the task as if this new activity had never been added. (See Figure 2-14.)

Intent.FLAG_ACTIVITY_SINGLE_TOP: If an instance of the activity is already on top of the activity stack, don’t start another instance of that activity. Instead, use the instance that’s already on top of the stack. (See Figure 2-15.)

Intent.FLAG_ACTIVITY_CLEAR_TOP: If the activity being started already has an instance somewhere on the task stack, don’t add a new instance at the top of the task stack. Instead, grab all activities above the existing instance, and pop them off the stack. (Yes, destroy them.) See Figure 2-16.

Intent.FLAG_ACTIVITY_NEW_TASK: Each task is associated with an application. Imagine that you have two applications — App A and App B — and that the currently active task is associated with App A. Inside this task, you call startActivity to launch an activity in the other app — App B. What happens?

Without the FLAG_ACTIVITY_NEW_TASK flag, Android pushes the newly starting activity on top of the current stack. (See Figure 2-17.) But with the FLAG_ACTIVITY_NEW_TASK flag, Android looks for a task associated with App B.

Figure 2-14: The effect of adding FLAG_ACTIVITY_NO_HISTORY.

9781118027707-fg030214.eps

Figure 2-15: The effect of adding FLAG_ACTIVITY_SINGLE_TOP.

9781118027707-fg030215.eps

Figure 2-16: The effect of adding FLAG_ACTIVITY_CLEAR_TOP.

9781118027707-fg030216.eps

If Android finds such a task, Android pushes the newly starting activity onto that task.

If Android doesn’t find such a task, Android creates a task associated with App B. (See Figure 2-17.)

With or without a previously existing App B task, Android displays the newly started activity on the user’s screen.

Figure 2-17: The effect of adding FLAG_ACTIVITY_NEW_TASK.

9781118027707-fg030217.eps

Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS: Don’t display this activity’s app when the user holds down the Home button.

Android creates a “Recents” item when you create a new task. Combining FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS with FLAG_ACTIVITY_NEW_TASK suppresses the creation of a new “Recents” item.

Intent.FLAG_ACTIVITY_CLEAR_TASK: When used with FLAG_ACTIVITY_NEW_TASK and the new activity is already part of a task, make the task containing the activity be the active task and obliterate all other activities currently on that task. (See Figure 2-18.)

warning_bomb.eps The FLAG_ACTIVITY_CLEAR_TASK feature joined Android’s SDK with the release of Honeycomb. If your target is older than Honeycomb, don’t try to use FLAG_ACTIVITY_CLEAR_TASK.

Intent.FLAG_ACTIVITY_REORDER_TO_FRONT: If an instance of the activity being started is already part of the current task, reorder the task’s activities so that the instance is on top. (See Figure 2-19.)

Intent.FLAG_EXCLUDE_STOPPED_PACKAGES: When searching for an activity to start, consider only activities that are currently active or paused.

Each of these flags has a Java int value, and you can combine two or more flags with Java’s bitwise OR operator (|). For example, you can write

intent.addFlags(FLAG_ACTIVITY_NEW_TASK |

                FLAG_ACTIVITY_CLEAR_TASK);

The result is 0x10000000 | 0x00008000, which is 0x10008000.

warning_bomb.eps The FLAG_ACTIVITY_CLEAR_TASK feature joined Android’s SDK with the release of Honeycomb. If you try to use this flag’s numeric value (0x00008000) on a pre-Honeycomb system, you don’t get a compile-time error. After all, the compiler thinks 0x00008000 is a perfectly good hexadecimal number, even when you pass the number to the addFlags method. But at runtime, a pre-Honeycomb system says, “I can’t do addFlags(0x00008000), so I think I’ll display a rude application has stopped unexpectedly message.” So to catch such errors before runtime, always use the constant name FLAG_ACTIVITY_CLEAR_TASK as well as other SDK constants.

ontheweb.eps Needless to say, I’ve created an app to help you experiment with intent flags. My flag-testing app is very much like the Intentsity app that I describe previously in this chapter. I haven’t given my flag-testing app a fancy name, but you can download the app and its source code from this book’s website — www.allmycode.com/android.

tip.eps To test your intent flags, you must keep track of the apps, tasks, and activities as they run on your emulator or device. This isn’t always straightforward. Fortunately, Android’s dumpsys command can show you a snapshot of the current state of affairs. To see an up-to-date list of your emulator’s activity stack and its tasks, type adb shell dumpsys activity in your development computer’s command window. Alternatively, you can log on to the emulator’s shell (by typing adb shell) and then issue the dumpsys activity command within the emulator’s own command window.

Figure 2-18: The effect of adding FLAG_ACTIVITY_CLEAR_TASK.

9781118027707-fg030218.eps

Figure 2-19: The effect of adding FLAG_ACTIVITY_REORDER_TO_FRONT.

9781118027707-fg030219.eps

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.11.89