52862.fb2
Up to now, the focus of this book has been on activities opened directly by the user from the device’s launcher. This, of course, is the most obvious case for getting your activity up and visible to the user. In many cases it is the primary way the user will start using your application.
However, the Android system is based upon lots of loosely-coupled components. What you might accomplish in a desktop GUI via dialog boxes, child windows, and the like are mostly supposed to be independent activities. While one activity will be “special”, in that it shows up in the launcher, the other activities all need to be reached… somehow.
The “how” is via intents.
An intent is basically a message that you pass to Android saying, “Yo! I want to do… er… something! Yeah!” How specific the “something” is depends on the situation — sometimes you know exactly what you want to do (e.g., open up one of your other activities), and sometimes you don’t.
In the abstract, Android is all about intents and receivers of those intents. So, now that we are well-versed in creating activities, let’s dive into intents, so we can create more complex applications while simultaneously being “good Android citizens.”
When Sir Tim Berners-Lee cooked up the Hypertext Transfer Protocol — HTTP — he set up a system of verbs plus addresses in the form of URLs. The address indicated a resource, such as a Web page, graphic, or server-side program. The verb indicated what should be done: GET to retrieve it, POST to send form data to it for processing, etc.
Intents are similar, in that they represent an action plus context. There are more actions and more components to the context with Android intents than there are with HTTP verbs and resources, but the concept is still the same.
Just as a Web browser knows how to process a verb + URL pair, Android knows how to find activities or other application logic that will handle a given intent.
The two most important pieces of an intent are the action and what Android refers to as the “data”. These are almost exactly analogous to HTTP verbs and URLs — the action is the verb, and the “data” is a Uri
, such as content://contacts/people/1
representing a contact in the contacts database. Actions are constants, such as ACTION_VIEW
(to bring up a viewer for the resource), ACTION_EDIT
(to edit the resource), or ACTION_PICK
(to choose an available item given a
Uri representing a collection, such as content://contacts/people
).
If you were
to create an intent combining ACTION_VIEW
with a content Uri
of content://contacts/people/1
, and pass that intent to Android, Android would know to find and open an activity capable of viewing that resource.
There are other criteria you can place inside an intent (represented as an Intent object), besides the action and “data” Uri
, such as:
• A category. Your “main” activity will be in the LAUNCHER
category, indicating it should show up on the launcher menu. Other activities will probably be in the DEFAULT
or ALTERNATIVE
categories.
• A MIME type, indicating the type of resource you want to operate on, if you don’t know a collection Uri
.
• A component, which is to say, the class of the activity that is supposed to receive this intent. Using components this way obviates the need for the other properties of the intent. However, it does make the intent more fragile, as it assumes specific implementations.
• “Extras”, which is a Bundle of other information you want to pass along to the receiver with the intent, that the receiver might want to take advantage of. What pieces of information a given receiver can use is up to the receiver and (hopefully) is well-documented.
You will find rosters of the standard actions and categories in the Android SDK documentation for the Intent class.
As previously noted, if you specify the target component in your intent, Android has no doubt where the intent is supposed to be routed to — it will launch the named activity. This might be OK if the target intent is in your application. It definitely is not recommended for sending intents to other applications. Component names, by and large, are considered private to the application and are subject to change. Content Uri
templates and MIME types are the preferred ways of identifying services you wish third-party code to supply.
If you do not specify the target component, then Android has to figure out what activities (or other intent receivers) are eligible to receive the intent. Note the use of the plural “activities”, as a broadly-written intent might well resolve to several activities. That is the… ummm… intent (pardon the pun), as you will see later in this chapter. This routing approach is referred to as implicit routing.
Basically, there are three rules, all of which must be true for a given activity to be eligible for a given intent:
1. The activity must support the specified action.
2. The activity must support the stated MIME type (if supplied).
3. The activity must support all of the categories named in the intent.
The upshot is that you want to make your intents specific enough to find the right receiver(s), and no more specific than that.
This will become clearer as we work through some examples later in this chapter.
All Android components that wish to be notified via intents must declare intent filters, so Android knows which intents should go to that component. To do this, you need to add intent-filter
elements to your AndroidManifest.xml
file.
All of the example projects have intent filters defined, courtesy of the Android application-building script (activityCreator
or the IDE equivalent). They look something like this:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.skeleton">
<application>
<activity android:name=".Now" android:label="Now">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Note the intent-filter
element under the activity
element. Here, we declare that this activity:
• is the main activity for this application
• is in the LAUNCHER
category, meaning it gets an icon in the Android main menu
Because this activity is the main one for the application, Android knows this is the component it should launch when somebody chooses the application from the main menu.
You are welcome to have more than one action or more than one category in your intent filters. That indicates that the associated component (e.g., activity) handles multiple different sorts of intents.
More than likely, you will also want to have your secondary (non-MAIN) activities specify the MIME type of data they work on. Then, if an intent is targeted for that MIME type — either directly, or indirectly by the Uri referencing something of that type — Android will know that the component handles such data.
For example, you could have an activity declared like this:
<activity android:name=".TourViewActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.commonsware.tour" />
</intent-filter>
</activity>
This activity will get launched by an intent requesting to view a Uri
representing a vnd.android.cursor.item/vnd.commonsware.tour
piece of content. That intent could come from another activity in the same application (e.g., the MAIN activity for this application) or from another activity in another Android application that happens to know a Uri
that this activity handles.
In the examples shown previously, the intent filters were set up on activities. Sometimes, tying intents to activities is not exactly what we want:
• Some system events might cause us to want to trigger something in a service rather than an activity.
• Some events might need to launch different activities in different circumstances, where the criteria are not solely based on the intent itself, but some other state (e.g., if we get intent X and the database has a Y, then launch activity M; if the database does not have a Y, then launch activity N).
For these cases, Android offers the intent receiver, defined as a class implementing the BroadcastReceiver
interface. Intent receivers are disposable objects designed to receive intents — particularly broadcast intents — and take action, typically involving launching other intents to trigger logic in an activity, service, or other component.
The BroadcastReceiver
interface has only one method: onReceive()
. Intent receivers implement that method, where they do whatever it is they wish to do upon an incoming intent. To declare an intent receiver, add a receiver element to your AndroidManifest.xml
file:
<receiver android:name=".MyIntentReceiverClassName" />
An intent receiver is only alive for as long as it takes to process onReceive()
— as soon as that method returns, the receiver instance is subject to garbage collection and will not be reused. This means intent receivers are somewhat limited in what they can do, mostly to avoid anything that involves any sort of callback. For example, they cannot bind to a service, and they cannot open a dialog box.
The exception is if the BroadcastReceiver
is implemented on some longer-lived component, such as an activity or service — in that case, the intent receiver lives as long as its “host” does (e.g., until the activity is frozen). However, in this case, you cannot declare the intent receiver via AndroidManifest.xml
. Instead, you need to call registerReceiver()
on your Activity
’s onResume()
callback to declare interest in an intent, then call unregisterReceiver()
from your Activity
’s onPause()
when you no longer need those intents.
There is one hiccup with using Intent objects to pass arbitrary messages around: it only works when the receiver is active. To quote from the documentation for BroadcastReceiver
:
If registering a receiver in your Activity.onResume() implementation, you should unregister it in Activity.onPause(). (You won’t receive intents when paused, and this will cut down on unnecessary system overhead). Do not unregister in Activity.onSaveInstanceState(), because this won’t be called if the user moves back in the history stack.
Hence, you can only really use the Intent framework as an arbitrary message bus if:
• Your receiver does not care if it misses messages because it was not active.
• You provide some means of getting the receiver “caught up” on messages it missed while it was inactive.
In Chapters 30 and 31 on creating and using services, you will see an example of the former condition, where the receiver (service client) will use Intent-based messages when they are available but does not need them if the client is not active.
The theory behind the Android UI architecture is that developers should decompose their application into distinct activities, each implemented as an Activity
, each reachable via Intents
, with one “main” activity being the one launched by the Android launcher. For example, a calendar application could have activities for viewing the calendar, viewing a single event, editing an event (including adding a new one), and so forth.
This, of course, implies that one of your activities has the means to start up another activity. For example, if somebody clicks on an event from the view-calendar activity, you might want to show the view-event activity for that event. This means that, somehow, you need to be able to cause the view-event activity to launch and show a specific event (the one the user clicked upon).
This can be further broken down into two scenarios:
• You know what activity you want to launch, probably because it is another activity in your own application.
• You have a content Uri
to… something, and you want your users to be able to do… something with it, but you do not know up front what the options are.
This chapter covers the first scenario; the next chapter handles the second.
One key question you need to answer when you decide to launch an activity is, does your activity need to know when the launched activity ends?
For example, suppose you want to spawn an activity to collect authentication information for some Web service you are connecting to — maybe you need to authenticate with OpenID[30] in order to use an OAuth[31] service. In this case, your main activity will need to know when the authentication is complete so it can start to use the Web service.
In this case the launched activity is clearly subordinate to the launching activity. Therefore you probably want to launch the child as a sub-activity, which means your activity will be notified when the child activity is complete.
On the other hand, imagine an email application in Android. When the user elects to view an attachment, neither you nor the user necessarily expects the main activity to know when the user is done viewing that attachment.
In this scenario, the launched activity is more a peer of your activity, so you probably want to launch the “child” just as a regular activity. Your activity will not be informed when the “child” is done, but, then again, your activity really doesn’t need to know.
The two requirements for starting an activity are an Intent and your choice of how to start it up.
As discussed in Chapter 1, Intent
s encapsulate a request, made to Android, for some activity or other Intent
receiver to do something.
If the activity you intend to launch is one of your own, you may find it simplest to create an explicit Intent
, naming the component you wish to launch. For example, from within your activity, you could create an Intent
like this:
new Intent(this, HelpActivity.class);
This would stipulate that you wanted to launch the HelpActivity
. This activity would need to be named in your AndroidManifest.xml
file, though not necessarily with any Intent
filter, since you are trying to request it directly.
Or you could put together an Intent
for some Uri
, requesting a particular action:
Uri uri = Uri.parse("geo:" + lat.toString() + "," + lon.toString());
Intent i = new Intent(Intent.ACTION_VIEW, uri);
Here, given that we have the latitude and longitude of some position (lat
and lon
, respectively) of type Double
, we construct a geo
scheme Uri
and create an Intent
requesting to view this Uri(ACTION_VIEW)
.
Once you have your Intent
, you need to pass it to Android and get the child activity to launch. You have four choices:
• The simplest option is to call startActivity()
with the Intent
— this will cause Android to find the best-match activity and pass the Intent
to it for handling. Your activity will not be informed when the “child” activity is complete.
• You can call startActivityForResult()
, passing it the Intent
and a number (unique to the calling activity). Android will find the best-match activity and pass the Intent
over to it. However, your activity will be notified when the child activity is complete via the onActivityResult()
callback (see the text following this list).
• You can call sendBroadcast()
. In this case, Android will pass the Intent
to all registered BroadcastReceivers
that could possibly want this Intent, not just the best match.
• You can call sendOrderedBroadcast()
. Here Android will pass the Intent
to all candidate BroadcastReceivers
one at a time — if any one “consumes” the Intent
, the rest of the candidates are not notified.
Most of the time, you will wind up using startActivity()
or startActivityForResult()
— broadcast Intents
are more typically raised by the Android system itself.
With startActivityForResult()
, as noted, you can implement the onActivityResult()
callback to be notified when the child activity has completed its work. The callback receives the unique number supplied to startActivityForResult()
, so you can determine which child activity is the one that has completed. You also get the following:
• A result code from the child activity calling setResult()
. Typically this is RESULT_OK
or RESULT_CANCELLED
, though you can create your own return codes (pick a number starting with RESULT_FIRST_USER
).
• An optional String
containing some result data, possibly a URL to some internal or external resource — for example, an ACTION_PICK Intent
typically returns the selected bit of content via this data string.
• An optional Bundle
containing additional information beyond the result code and data string.
To demonstrate launching a peer activity, take a peek at the Activities/Launch
sample application in the Source Code section at http://apress.com. The XML layout is fairly straightforward: two fields for the latitude and longitude, plus a button:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1,2"
>
<TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="2dip"
android:paddingRight="4dip"
android:text="Location:"
/>
<EditText android:id="@+id/lat"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:cursorVisible="true"
android:editable="true"
android:singleLine="true"
android:layout_weight="1"
/>
<EditText android:id="@+id/lon"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:cursorVisible="true"
android:editable="true"
android:singleLine="true"
android:layout_weight="1"
/>
</TableRow>
</TableLayout>
<Button android:id="@+id/map"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Show Me!"
/>
</LinearLayout>
The button’s OnClickListener
simply takes the latitude and longitude, pours them into a geo
scheme Uri
, then starts the activity.
package com.commonsware.android.activities;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class LaunchDemo extends Activity {
private EditText lat;
private EditText lon;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
Button btn = (Button)findViewById(R.id.map);
lat = (EditText)findViewById(R.id.lat);
lon = (EditText)findViewById(R.id.lon);
btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
String _lat = lat.getText().toString();
String _lon = lon.getText().toString();
Uri uri=Uri.parse("geo:"+_lat+","+_lon);
startActivity(new Intent(Intent.ACTION_VIEW, uri));
}
});
}
}
The activity is not much to look at (Figure 24-1).
Figure 24-1. The LaunchDemo sample application, with a location filled in
If you fill in a location (e.g., 38.8891 latitude and -77.0492 longitude) and click the button, the resulting map is more interesting (Figure 24-2). Note that this is the built-in Android map activity — we did not create our own activity to display this map.
Figure 24-2. The map launched by LaunchDemo, showing the Lincoln Memorial in Washington DC
In a Chapter 34, you will see how you can create maps in your own activities, in case you need greater control over how the map is displayed.
One of the main features of the modern desktop Web browser is tabbed browsing, where a single browser window can show several pages split across a series of tabs. On a mobile device this may not make a lot of sense, given that you lose screen real estate for the tabs themselves.
In this book, however, we do not let little things like sensibility stop us, so let me demonstrate a tabbed browser, using TabActivity
and Intents
.
As you may recall from Chapter 10, a tab can have either a View
or an Activity
as its content. If you want to use an Activity
as the content of a tab, you provide an Intent
that will launch the desired Activity
; Android’s tab-management framework will then pour the Activity
’s user interface into the tab.
Your natural instinct might be to use an http:Uri
the way we used a geo:Uri
in the previous example:
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("http://commonsware.com"));
That way, you could use the built-in Browser application and get all of the features that it offers.
Alas, this does not work. You cannot host other applications’ activities in your tabs — only your own activities, for security reasons.
So, we dust off our WebView
demos from Chapter 13 and use those instead, repackaged as Activities/IntentTab
.
Here is the source to the main activity, the one hosting the TabView
:
public class IntentTabDemo extends TabActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TabHost host = getTabHost();
host.addTab(host.newTabSpec("one").setIndicator("CW")
.setContent(new Intent(this, CWBrowser.class)));
host.addTab(host.newTabSpec("two").setIndicator("Android")
.setContent(new Intent(this, AndroidBrowser.class)));
}
}
As you can see, we are using TabActivity
as the base class, and so we do not need our own layout XML — TabActivity
supplies it for us. All we do is get access to the TabHost
and add two tabs, each specifying an Intent that directly refers to another class. In this case, our two tabs will host a CWBrowser
and an AndroidBrowser
, respectively.
Those activities are simple modifications to the earlier browser demos:
public class CWBrowser extends Activity {
WebView browser;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
browser = new WebView(this);
setContentView(browser);
browser.loadUrl("http://commonsware.com");
}
}
public class AndroidBrowser extends Activity {
WebView browser;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
browser = new WebView(this);
setContentView(browser);
browser.loadUrl("http://code.google.com/android");
}
}
They simply load a different URL into the browser: the CommonsWare home page in one (Figure 24-3), the Android home page in the other (Figure 24-4). The resulting UI shows what tabbed browsing could look like on Android.
Figure 24-3. The IntentTabDemo sample application, showing the first tab
Figure 24-4. The IntentTabDemo sample application, showing the second tab
Using distinct subclasses for each targeted page is rather wasteful. Instead we could have packaged the URL to open as an “extra” in an Intent
and used that Intent
to spawn a general-purpose BrowserTab
activity, which would read the URL out of the Intent
“extra,” and use that. The proof of this is left as an exercise for the reader.
Sometimes you know just what you want to do, such as display one of your other activities. Sometimes, you have a pretty good idea of what you want to do, such as view the content represented by a Uri
, or have the user pick a piece of content of some MIME type. Sometimes you’re lost. All you have is a content Uri
, and you don’t really know what you can do with it.
For example, suppose you were creating a common tagging sub-system for Android, where users could tag pieces of content — contacts, Web URLs, geographic locations, etc. Your sub-system would hold onto the Uri
of the content plus the associated tags, so other sub-systems could, say, ask for all pieces of content referencing some tag.
That’s all well and good. However, you probably need some sort of maintenance activity, where users could view all their tags and the pieces of content so tagged. This might even serve as a quasi-bookmark service for items on their phone. The problem is, the user is going to expect to be able to do useful things with the content they find in your sub-system, such as dial a contact or show a map for a location.
The problem is, you have absolutely no idea what is possible with any given content Uri
. You probably can view any of them, but can you edit them? Can you dial them? Since new applications with new types of content could be added by any user at any time, you can’t even assume you know all possible combinations just by looking at the stock applications shipped on all Android devices.
Fortunately, the Android developers thought of this.
Android offers various means by which you can present to your users a set of likely activities to spawn for a given content Uri
— even if you have no idea what that content Uri
really represents. This chapter explores some of these Uri
action introspection tools.
Sometimes you know your content Uri
represents a collection of some type, such as content://contacts/people
representing the list of contacts in the stock Android contacts list. In this case, you can let the user pick a contact that your activity can then use (e.g., tag it, dial it).
To do this, you need to create an Intent for the ACTION_PICK
on the target Uri
, then start a sub-activity (via startActivityForResult()
) to allow the user to pick a piece of content of the specified type. If your onActivityResult()
callback for this request gets a RESULT_OK
result code, your data string can be parsed into a Uri
representing the chosen piece of content.
For example, take a look at Introspection/Pick
in the sample applications in the Source Code section of http://apress.com. This activity gives you a field for a collection Uri
(with content://contacts/people
pre-filled in for your convenience), plus a really big Gimme! button:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<EditText android:id="@+id/type"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:cursorVisible="true"
android:editable="true"
android:singleLine="true"
android:text="content://contacts/people"
/>
<Button
android:id="@+id/pick"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Gimme!"
android:layout_weight="1"
/>
</LinearLayout>
Upon being clicked, the button creates the ACTION_PICK
on the user-supplied collection Uri
and starts the sub-activity. When that sub-activity completes with RESULT_OK
, the ACTION_VIEW
is invoked on the resulting content Uri
.
public class PickDemo extends Activity {
static final int PICK_REQUEST = 1337;
private EditText type;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
type = (EditText)findViewById(R.id.type);
Button btn = (Button)findViewById(R.id.pick);
btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_PICK,
Uri.parse(type.getText().toString()));
startActivityForResult(i, PICK_REQUEST);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode==PICK_REQUEST) {
if (resultCode==RESULT_OK) {
startActivity(new Intent(Intent.ACTION_VIEW,
data.getData()));
}
}
}
}
The result: the user chooses a collection (Figure 25-1), picks a piece of content (Figure 25-2), and views it (Figure 25-3).
Figure 25-1. The PickDemo sample application, as initially launched
Figure 25-2. The same application, after the user has clicked the Gimme! button, showing the list of available people
Figure 25-3. A view of a contact, launched by PickDemo after the user has chosen one of the people from the pick list
Another way to give the user ways to take actions on a piece of content, without you knowing what actions are possible, is to inject a set of menu choices into the options menu via addIntentOptions()
. This method, available on Menu
, takes an Intent
and other parameters and fills in a set of menu choices on the Menu instance, each representing one possible action. Choosing one of those menu choices spawns the associated activity.
The canonical example of using addIntentOptions()
illustrates another flavor of having a piece of content and not knowing the actions that can be taken. In the previous example, showing ActivityAdapter
, the content was from some other Android application, and we know nothing about it. It is also possible, though, that we know full well what the content is — it’s ours. However, Android applications are perfectly capable of adding new actions to existing content types, so even though you wrote your application and know what you expect to be done with your content, there may be other options you are unaware of that are available to users.
For example, imagine the tagging sub-system mentioned in the introduction to this chapter. It would be very annoying to users if every time they wanted to tag a piece of content, they had to go to a separate tagging tool then turn around and pick the content they just had been working on (if that is even technically possible) before associating tags with it. Instead they would probably prefer a menu choice in the content’s own “home” activity where they can indicate they want to tag it, which leads them to the set-a-tag activity and tells that activity what content should get tagged.
To accomplish this, the tagging sub-system should set up an Intent filter, supporting any piece of content with its own action (e.g., ACTION_TAG
) and a category of CATEGORY_ALTERNATIVE
, which is the convention for one application adding actions to another application’s content.
If you want to write activities that are aware of possible add-ons like tagging, you should use addIntentOptions()
to add those add-ons’ actions to your options menu, such as the following:
Intent intent = new Intent(null, myContentUri);
intent.addCategory(Intent.ALTERNATIVE_CATEGORY);
menu.addIntentOptions(Menu.ALTERNATIVE, 0,
new ComponentName(this, MyActivity.class), null, intent, 0, null);
Here, myContentUri
is the content Uri
of whatever is being viewed by the user in this activity, MyActivity
is the name of the activity class, and menu
is the menu being modified.
In this case, the Intent we are using to pick actions from requires that appropriate Intent
receivers support the CATEGORY_ALTERNATIVE
. Then we add the options to the menu with addIntentOptions()
and the following parameters:
• The sort position for this set of menu choices, typically set to 0 (which appear in the order added to the menu) or ALTERNATIVE
(which appear after other menu choices).
• A unique number for this set of menu choices, or 0 if you do not need a number.
• A ComponentName
instance representing the activity that is populating its menu — this is used to filter out the activity’s own actions so the activity can handle its own actions as it sees fit.
• An array of Intent
instances that are the “specific” matches — any actions matching those Intents are shown in the menu before any other possible actions.
• The Intent
for which you want the available actions.
• A set of flags. The only one of likely relevance is represented as MATCH_DEFAULT_ONLY
, which means matching actions must also implement the DEFAULT_CATEGORY
category. If you do not need this, use a value of 0 for the flags.
• An array of Menu.Items
, which will hold the menu items matching the array of Intent
instances supplied as the “specifics,” or null
if you do not need those items (or are not using “specifics”).
Both the ActivityAdapter
family and addIntentOptions()
use queryIntentActivityOptions()
for the “heavy lifting” of finding possible actions. The queryIntentActivityOptions()
method is implemented on PackageManager
, which is available to your activity via getPackageManager()
.
The queryIntentActivityOptions()
method takes some of the same parameters as does addIntentOptions()
, notably the caller ComponentName
, the “specifics” array of Intent
instances, the overall Intent
representing the actions you are seeking, and the set of flags. It returns a List
of Intent instances matching the stated criteria, with the “specifics” ones first.
If you would like to offer alternative actions to users, but by means other than addIntentOptions()
, you could call queryIntentActivityOptions()
, get the Intent
instances, then use them to populate some other user interface (e.g., a toolbar).
Some Android handsets, like the T-Mobile G1, offer a slide-out keyboard that triggers rotating the screen from portrait to landscape. Other handsets might use accelerometers to determine screen rotation, like the iPhone does. As a result, it is reasonable to assume that switching from portrait to landscape and back again may be something your users will look to do.
Android has a number of ways for you to handle screen rotation, so your application can properly handle either orientation. All these facilities do is help you detect and manage the rotation process — you are still required to make sure you have layouts that look decent on each orientation.
By default, when there is a change in the phone configuration that might affect resource selection, Android will destroy and re-create any running or paused activities the next time they are to be viewed. While this could happen for a variety of different configuration changes (e.g., change of language selection), it will most likely trip you up mostly for rotations, since a change in orientation can cause you to load a different set of resources (e.g., layouts).
The key here is that this is the default behavior. It may even be the behavior that is best for one or more of your activities. You do have some control over the matter, though, and can tailor how your activities respond to orientation changes or similar configuration switches.
Since, by default, Android destroys and re-creates your activity on a rotation, you may only need to hook into the same onSaveInstanceState()
that you would if your activity were destroyed for any other reason (e.g., low memory). Implement that method in your activity and fill in the supplied Bundle
with enough information to get you back to your current state. Then, in onCreate()
(or onRestoreInstanceState()
, if you prefer), pick the data out of the Bundle
and use it to bring your activity back to the way it was.
To demonstrate this, let’s take a look at the Rotation/RotationOne
project. It, and the other sample projects used in this chapter, which are also found in the source code section of the Apress web site, use a pair of main.xml layouts, one in res/layout/
and one in res/layout-land/
for use in landscape mode. Here is the portrait layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button android:id="@+id/pick"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:text="Pick"
android:enabled="true"
/>
<Button android:id="@+id/view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:text="View"
android:enabled="false"
/>
</LinearLayout>
while here is the similar landscape layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button android:id="@+id/pick"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:text="Pick"
android:enabled="true"
/>
<Button android:id="@+id/view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:text="View"
android:enabled="false"
/>
</LinearLayout>
Basically, it is a pair of buttons, each taking up half the screen. In portrait mode, the buttons are stacked; in landscape mode, they are side-by-side.
If you were to simply create a project, put in those two layouts, and compile it, the application would appear to work just fine — a rotation (Ctrl-F12 in the emulator) will cause the layout to change. And while buttons lack state, if you were using other widgets (e.g., EditText
), you would even find that Android hangs onto some of the widget state for you (e.g., the text entered in the EditText
).
What Android cannot automatically help you with is anything held outside the widgets.
This application is derived from the Pick demo used in Chapter 24. There, clicking one button would let you pick a contact, then view the contact. Here, we split those into separate buttons, with the “View” button only enabled when we actually have a contact.
Let’s see how we handle this, using onSaveInstanceState()
:
public class RotationOneDemo extends Activity {
static final int PICK_REQUEST = 1337;
Button viewButton = null;
Uri contact = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button btn = (Button)findViewById(R.id.pick);
btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_PICK,
Uri.parse("content://contacts/people"));
startActivityForResult(i, PICK_REQUEST);
}
});
viewButton = (Button)findViewById(R.id.view);
viewButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
startActivity(new Intent(Intent.ACTION_VIEW, contact));
}
});
restoreMe(savedInstanceState);
viewButton.setEnabled(contact!=null);
}
@Override protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode==PICK_REQUEST) {
if (resultCode==RESULT_OK) {
contact = data.getData();
viewButton.setEnabled(true);
}
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (contact!=null) {
outState.putString("contact", contact.toString());
}
}
private void restoreMe(Bundle state) {
contact = null;
if (state!=null) {
String contactUri = state.getString("contact");
if (contactUri!=null) {
contact = Uri.parse(contactUri);
}
}
}
}
By and large, it looks like a normal activity… because it is. Initially, the “model” — a Uri
named contact
— is null. It is set as the result of spawning the ACTION_PICK
sub-activity. Its string representation is saved in onSaveInstanceState()
and restored in restoreMe()
(called from onCreate()
). If the contact is not null, the “View” button is enabled and can be used to view the chosen contact.
Visually, it looks like Figures 26-1 and 26-2.
Figure 26-1. The RotationOne application, in portrait mode
Figure 26-2. The RotationOne application, in landscape mode
The benefit to this implementation is that it handles a number of system events beyond mere rotation, such as being closed by Android due to low memory.
For fun, comment out the restoreMe()
call in onCreate()
and try running the application. You will see that the application “forgets” a contact selected in one orientation when you rotate the emulator or device.
The problem with onSaveInstanceState()
is that you are limited to a Bundle
. That’s because this callback is also used in cases where your whole process might be terminated (e.g., low memory), so the data to be saved has to be something that can be serialized and has no dependencies upon your running process.
For some activities, that limitation is not a problem. For others, though, it is more annoying. Take an online chat, for example. You have no means of storing a socket in a Bundle
, so by default, you will have to drop your connection to the chat server and re-establish it. That not only may be a performance hit, but it might also affect the chat itself, such as you appearing in the chat logs as disconnecting and reconnecting.
One way to get past this is to use onRetainNonConfigurationInstance()
instead of onSaveInstanceState()
for “light” changes like a rotation. Your activity’s onRetainNonConfigurationInstance()
callback can return an Object
, which you can retrieve later via getLastNonConfigurationInstance()
. The Object
can be just about anything you want — typically, it will be some kind of “context” object holding activity state, such as running threads, open sockets, and the like. Your activity’s onCreate()
can call getLastNonConfigurationInstance()
— if you get a non-null response, you now have your sockets and threads and whatnot. The biggest limitation is that you do not want to put in the saved context anything that might reference a resource that will get swapped out, such as a Drawable
loaded from a resource.
Let’s take a look at the Rotation/RotationTwo
sample project, which uses this approach to handling rotations. The layouts, and hence the visual appearance, is the same as with Rotation/RotationOne
. Where things differ slightly is in the Java code:
public class RotationTwoDemo extends Activity {
static final int PICK_REQUEST = 1337;
Button viewButton = null;
Uri contact = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button btn = (Button)findViewById(R.id.pick);
btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_PICK,
Uri.parse("content://contacts/people"));
startActivityForResult(i, PICK_REQUEST);
}
});
viewButton = (Button)findViewById(R.id.view);
viewButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
startActivity(new Intent(Intent.ACTION_VIEW, contact));
}
});
restoreMe();
viewButton.setEnabled(contact!=null);
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode==PICK_REQUEST) {
if (resultCode==RESULT_OK) {
contact = data.getData();
viewButton.setEnabled(true);
}
}
}
@Override
public Object onRetainNonConfigurationInstance() {
return(contact);
}
private void restoreMe() {
contact = null;
if (getLastNonConfigurationInstance()!=null) {
contact = (Uri)getLastNonConfigurationInstance();
}
}
}
In this case, we override onRetainNonConfigurationInstance()
, returning the actual Uri
for our contact, rather than a string representation of it. In turn, restoreMe()
calls getLastNonConfigurationInstance()
, and if it is not null, we hold onto it as our contact and enable the “View” button.
The advantage here is that we are passing around the Uri
rather than a string representation. In this case, that is not a big saving. But our state could be much more complicated, including threads and sockets and other things we cannot pack into a Bundle
.
Even this, though, may still be too intrusive to your application. Suppose, for example, you are creating a real-time game, such as a first-person shooter. The “hiccup” your users experience as your activity is destroyed and re-created might be enough to get them shot, which they may not appreciate. While this would be less of an issue on the T-Mobile G1, since a rotation requires sliding open the keyboard and therefore is unlikely to be done mid-game, other devices might rotate based solely upon the device’s position as determined by accelerometers.
The third possibility for handling rotations, therefore, is to tell Android that you will handle them completely yourself and that you do not want assistance from the framework. To do this:
1. Put an android:configChanges
entry in your AndroidManifest.xml
file, listing the configuration changes you want to handle yourself versus allowing Android to handle for you.
2. Implement onConfigurationChanged()
in your Activity
, which will be called when one of the configuration changes you listed in android:configChanges
occurs.
Now, for any configuration change you want, you can bypass the whole activity-destruction process and simply get a callback letting you know of the change.
To see this in action, turn to the Rotation/RotationThree
sample application. Once again, our layouts are the same, so the application looks the same as the preceding two samples. However, the Java code is significantly different, because we are no longer concerned with saving our state, but rather with updating our UI to deal with the layout.
But first, we need to make a small change to our manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.rotation.three"
android:versionCode="1"
android:versionName="1.0.0">
<application android:label="@string/app_name">
<activity android:name=".RotationThreeDemo"
android:label="@string/app_name"
android:configChanges="keyboardHidden|orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Here, we state that we will handle keyboardHidden
and orientation
configuration changes ourselves. This covers us for any cause of the “rotation” — whether it is a sliding keyboard or a physical rotation. Note that this is set on the activity, not the application — if you have several activities, you will need to decide for each which of the tactics outlined in this chapter you wish to use.
The Java code for this project follows:
public class RotationThreeDemo extends Activity {
static final int PICK_REQUEST = 1337;
Button viewButton = null;
Uri contact = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupViews();
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode==PICK_REQUEST) {
if (resultCode==RESULT_OK) {
contact = data.getData();
viewButton.setEnabled(true);
}
}
}
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setupViews();
}
private void setupViews() {
setContentView(R.layout.main);
Button btn = (Button)findViewById(R.id.pick);
btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_PICK,
Uri.parse("content://contacts/people"));
startActivityForResult(i, PICK_REQUEST);
}
});
viewButton = (Button)findViewById(R.id.view);
viewButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
startActivity(new Intent(Intent.ACTION_VIEW, contact));
}
});
viewButton.setEnabled(contact!=null);
}
}
The onCreate()
implementation delegates most of its logic to a setupViews()
method, which loads the layout and sets up the buttons. The reason this logic was broken out into its own method is because it is also called from onConfigurationChanged()
.
In the previous three sections, we covered ways to deal with rotational events. There is, of course, a radical alternative: tell Android not to rotate your activity at all. If the activity does not rotate, you do not have to worry about writing code to deal with rotations.
To block Android from rotating your activity, all you need to do is add android:screenOrientation="portrait"
(or "landscape"
, as you prefer) to your AndroidManifest.xml
file, as shown (from the Rotation/RotationFour
sample project):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.rotation.four"
android:versionCode="1"
android:versionName="1.0.0">
<application android:label="@string/app_name">
<activity android:name=".RotationFourDemo"
android:screenOrientation="portrait"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Since this is applied on a per-activity basis, you will need to decide which of your activities may need this turned on.
At this point, your activity is locked into whatever orientation you specified, regardless of what you do. The following screen shots show the same activity as in the previous three sections, but using the previous manifest and with the emulator set for both portrait and landscape orientation. Note that the UI does not move a bit, but remains in portrait mode as can be seen in Figures 26-3 and 26-4.
Figure 26-3. The RotationFour application, in portrait mode
Figure 26-4. The RotationFour application, in landscape mode
All of these scenarios assume that you rotate the screen by opening up the keyboard on the device (or pressing <Ctrl>-<F12>
in the emulator). Certainly, this is the norm for Android applications.
However, we haven’t covered the iPhone Scenario.
You may have seen one (or several) commercials for the iPhone, showing how the screen rotates just by turning the device. By default, you do not get this behavior with the T-Mobile G1 — instead, the screen rotates based on whether the keyboard is open or closed.
However, it is very easy for you to change this behavior, so your screen will rotate based on the position of the phone: just add android:screenOrientation="sensor"
to your AndroidManifest.xml
file (as seen in the Rotation/RotationFive
sample project):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.rotation.five"
android:versionCode="1"
android:versionName="1.0.0">
<application android:label="@string/app_name">
<activity android:name=".RotationFiveDemo"
android:screenOrientation="sensor"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
The “sensor”, in this case, tells Android you want the accelerometers to control the screen orientation, so the physical shift in the device orientation controls the screen orientation.
At least on the G1, this appears to only work when going from the traditional upright portrait position to the traditional landscape position — rotating 90 degrees counter-clockwise. Rotating the device 90 degrees clockwise results in no change in the screen.
Also note that this setting disables having the keyboard trigger a rotation event. Leaving the device in the portrait position, if you slide out the keyboard, in a “normal” Android activity, the screen will rotate; in a android:screenOrientation="sensor"
activity, the screen will not rotate.
http://openid.net/
http://oauth.net/