Chapter 2
In This Chapter
Making a match
Getting the lowdown on intents and intent filters
Practicing with intents on an emulator or device
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.
Android has two kinds of intents — explicit and implicit:
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.
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.
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 values. Some typical sample values are in italics.
"android.intent.action.MAIN"
is a value that an action might have.android.content.Intent.FLAG_ACTIVITY_NO_HISTORY
Java constant is a value that a flag might have. That Java constant stands for the number 1073741824 (which is the same as hexadecimal 40000000).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 email messages. For example, when your email 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
.
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.
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.)
To find a match between an implicit intent and an intent filter, Android performs three tests:
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 multifaceted approach. (That’s a fancy way to say that I explain matching a few times in a few different ways.)
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.)
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.
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.
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 one and only 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 one and only 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 setAction
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.
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 "allmycode.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 the string "android.intent.action.VIEW"
(or the constant android.content.Intent.ACTION_VIEW
, whose value is the string "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
String Value |
Constant Name |
An Activity with This Action in One of Its Filters Can … |
|
|
Be the first activity in a brand-new task stack (the task’s root activity). |
|
|
Display something. |
|
|
Show the user a certain kind of data and then let the user choose an item from the displayed data. With this action, you don’t specify the source (URI) of the data. |
|
|
Show the user a certain kind of data and then let the user choose an item from the displayed data. With this action, you specify the source (URI) of the data. |
|
|
Same as |
|
|
Edit something. |
|
|
Add a new empty item to something. (You fill the item later.) |
|
|
Delete something. |
|
|
Paste something from the Clipboard. |
|
|
Search for something. |
|
|
Search for something on the Internet. |
|
|
Handle an incoming call. |
|
|
Make a phone call. |
|
|
Launch a dialer with a particular phone number in place, but let the user press the Dial button. |
|
|
Do data synchronization. |
|
|
Send data to a recipient (with the recipient "to be determined" after the activity is launched). |
|
|
Send data to a recipient (with the recipient specified as part of the intent). |
Here’s a useful experiment:
Create a new Android project with two activities — the main activity and a second OtherActivity.java
activity.
For help creating an app’s second activity, see Book 1, Chapter 6.
activity
element in the project’s AndroidManifest.xml file
so that it reads as follows:
<activity android:name=".OtherActivity">
<intent-filter>
<action
android:name="com.allmycode.action.MY_ACTION" />
<category
android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
In the main activity, add the following code:
final String THE_ACTION =
"com.allmycode.action.MY_ACTION";
Intent = new Intent();
intent.setAction(THE_ACTION);
startActivity(intent);
Add this code after the call to setContentView
in the activity’s onCreate
method.
Using Android Studio’s Designer tool, 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.
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.
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.allmycode.action.MY_ACTION"
. Real developers type that "com.allmycode.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).
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 AndroidManifest.xml
file.
Continuing the experiment …
AndroidManifest.xml
file:
<action
android:name="com.allmycode.action.MY_ACTION" />
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"
.
In Step 7, your app intentionally crashes. Crashes make good learning experiences, but users tend not to 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();
}
Working through these particular lines of code, note that they start out by telling Java to try startActivity
— to call it, in other words. 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 Android Studio’s Logcat panel. After displaying the information, Java marches on to execute whatever code comes after the attempt to call startActivity
. Therefore, the app doesn’t crash — the error has been caught.
For more on try/catch blocks, see Book II, Chapter 3.
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>
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.
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"
; 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
String Value |
Constant Name |
An Activity with This Category in One of Its Filters Is … |
|
|
Able to respond to user actions and to be launched by calls to the |
|
|
Able to work in a web browser. |
|
|
Displayed as an icon on the device’s app launcher screen. |
|
|
When the system offers the user a choice of activities to do a job, the system lists activities with filters possessing this category. |
|
|
Launched when the user inserts the device into the dock of an automobile dashboard. |
|
|
Able to run with automated software testing tools. |
|
|
Able to browse and download new apps. |
|
|
Launched when the device first boots. |
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>
Figure 2-5 illustrates an interesting relationship between an intent’s data and a filter’s data:
These rules have some corollaries:
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.
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>
The section “Java methods and XML elements,” 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:
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.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.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.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>
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>
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.
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.
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. 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.
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.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).*/*
matches text/plain
. The type */*
also matches image/jpeg
, and so on.*
(one wildcard with no slash) and */html
don’t seem to match anything.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.
For more information about path expressions, visit http://developer.android.com/guide/topics/manifest/data-element.html
.
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.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:
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.
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 section “Java methods and XML elements,” earlier in this chapter.)
The section “Java methods and XML elements,” earlier in this chapter, lists methods like setAction
and addCategory
. Methods beginning with set
are for things like an intent’s action because an intent can’t have more than one action. Methods beginning with 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.
After filling in some EditText fields, click the Test button, and the app does what it does best:
Intent
class’s methods to compose an intent from your Intent fields' entries.IntentFilter
class’s methods to compose a filter from your Filter fields' entries.registerReceiver(myReceiver, filter)
to create a broadcast receiver with the new filter.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.
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.
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. (In Figure 2-12, the app is named App A, and the app’s main activity is named A1.)
Imagine that the main activity displays a button. When the user presses that button, Android launches another of Application A’s activities. As a task stack grows, the root activity remains at the bottom of the task stack. (See Figure 2-13.)
At this point, the user sees activity A2. (Activity A1 isn’t visible. Activity A1 has been Stopped. Think of Activity A1 as being hidden beneath Activity A2.)
Now imagine that the user presses the device’s Home button. Then, on the Home screen, the user touches another app’s icon (the icon for Application B). This creates a brand new task stack (Task B in Figure 2-14).
At this point, the user sees activity B1. (Task A has been placed on the back burner.) Of course, actions taken in Application B can launch other activities of Application B. Each new activity goes on top of the Task B stack. (See Figure 2-15.)
Look again at Figure 2-15. When Activity B3 starts up, the user sees Activity B3 on the device’s screen. Activities B4 and B1 are waiting behind B3, and the whole of Task A is sitting silently somewhere in Android’s virtual holding space.
If the user presses the device’s Back button, Activity B3 pops off of the Task B stack, and Activity B4 reappears. (See Figure 2-16.)
But imagine that the user presses the device’s Home button followed by Application A’s icon. Task A belongs to Application A, so Android shifts its attention back to Task A. The user sees Activity A2 (the activity on top of the Task A stack) on the device’s screen. (See Figure 2-17.)
But here’s an interesting wrinkle. A call to startActivity
in Activity A’s code can launch an activity belonging to a different app (Application B, for example). 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. (See Figure 2-18.)
How does Android keep all this straight (without driving Joe User crazy)? Here’s how: Each task stack is associated with a single application — namely, the app containing the task stack’s root activity. And, typically, that single application has an icon on the device’s Home screen or Apps screen.
The user can switch between tasks by pressing the device’s Home button (and maybe the Apps button) and then pressing the icon whose app is associated with the desired task. Alternatively, the user can find the desired app by pressing the device’s Recents button (also known as the Overview button). Look again at Figure 2-18. After pressing the A app’s icon, the user sees whatever is at the top of A’s task stack, and that might be an activity belonging to the B app.
An intent can contain six kinds of information:
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 = 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-19.)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-20.)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-21.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-22.) But with the FLAG_ACTIVITY_NEW_TASK
flag, Android looks for a task associated with App B.
With or without a previously existing App B task, Android displays the newly started activity on the user’s screen.
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
: Don’t display this activity’s app when the user presses the device’s Recents 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-23.)
The FLAG_ACTIVITY_CLEAR_TASK
feature joined Android’s SDK with the release of Honeycomb. If your minimum SDK 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-24.)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 0x1000800
0.
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. Here’s how:
In Android Studio’s main menu, choose Run ⇒ Run ‘app'.
An app starts running on an emulator or device of your choosing.
At the bottom of Android Studio’s main window, click the Terminal tool button.
As a result, your development computer’s Terminal or Command Prompt (MS-DOS) window appears in the lower portion of the Android Studio window.
In the Terminal panel, type adb shell
.
As a result, you see the pound sign (#
) prompt. This prompt comes from the Android system (the emulator or the real device). Whatever command you type next goes directly to the running Android system.
Type dumpsys activity
.
A lot of text whizzes by very quickly in the Terminal panel. Fortunately, you can scroll to see text that raced off the screen.
If you do a bit of scrolling, you see something like this:
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
Stack #1:
Task id #268
TaskRecord{ae33feb #268 A=com.allyourcode.appb U=0 sz=1}
Intent { act=android.intent.action.MAIN
cat=[android.intent.category.LAUNCHER] flg=0x10000000
cmp=com.allyourcode.appb/.MainActivity }
Hist #0: ActivityRecord{e150d7c u0
com.allyourcode.appb/.MainActivity t268}
Intent { act=android.intent.action.MAIN
cat=[android.intent.category.LAUNCHER] flg=0x10000000
cmp=com.allyourcode.appb/.MainActivity }
ProcessRecord{28bf81bb 1447:com.allyourcode.appb/u0a89}
Task id #267
TaskRecord{3afe0148 #267 A=com.allyourcode.appa U=0 sz=2}
Intent { act=android.intent.action.MAIN
cat=[android.intent.category.LAUNCHER] flg=0x10000000
cmp=com.allyourcode.appa/.MainActivity }
Hist #1: ActivityRecord{1dc06f54 u0
com.allyourcode.appa/.Activity2A t267}
Intent { cmp=com.allyourcode.appa/.Activity2A }
ProcessRecord{4b366d8 1388:com.allyourcode.appa/u0a88}
Hist #0: ActivityRecord{2e0b1892 u0
com.allyourcode.appa/.MainActivity t267}
Intent { act=android.intent.action.MAIN
cat=[android.intent.category.LAUNCHER] flg=0x10000000
cmp=com.allyourcode.appa/.MainActivity }
ProcessRecord{4b366d8 1388:com.allyourcode.appa/u0a88}
Running activities (most recent first):
TaskRecord{ae33feb #268 A=com.allyourcode.appb U=0 sz=1}
Run #2: ActivityRecord{e150d7c u0
com.allyourcode.appb/.MainActivity t268}
TaskRecord{3afe0148 #267 A=com.allyourcode.appa U=0 sz=2}
Run #1: ActivityRecord{1dc06f54 u0
com.allyourcode.appa/.Activity2A t267}
Run #0: ActivityRecord{2e0b1892 u0
com.allyourcode.appa/.MainActivity t267}
mResumedActivity: ActivityRecord{e150d7c u0
com.allyourcode.appb/.MainActivity t268}
Stack #0:
Task id #266
TaskRecord{83aabe1 #266 A=com.android.launcher3 U=0 sz=1}
Intent { act=android.intent.action.MAIN
cat=[android.intent.category.HOME] flg=0x10000000
cmp=com.android.launcher3/.Launcher }
Hist #0: ActivityRecord{3bf784bb u0
com.android.launcher3/.Launcher t266}
Intent { act=android.intent.action.MAIN
cat=[android.intent.category.HOME] flg=0x10000000
cmp=com.android.launcher3/.Launcher }
ProcessRecord{1941462e 807:com.android.launcher3/u0a37}
Running activities (most recent first):
TaskRecord{83aabe1 #266 A=com.android.launcher3 U=0 sz=1}
Run #0: ActivityRecord{3bf784bb u0
com.android.launcher3/.Launcher t266}
With some careful experimentation, you’ll be able to decipher the output of the dumpsys activity
command and to use the output to gain a deeper understanding of Android’s activities, tasks, and intent flags.
44.222.82.133