Else D Start Activityintent

new AlertDialog.Builder(this) setTitle(getResources() .getString(R.string.alert_label)) .setMessage(R.string.no_link_message) .setPositiveButton("Continue", new OnClickListener() { public void onClick(DialogInterface dialog, int argl) {

return true; case MENU_MAP_REVIEW:

&& ! this.location.getText().equals("")) { intent = new Intent (Intent .ACTION_VIEW, Uri.parse("geo:0,0?q=" +

this . location. getText (). toString ())) ; <1-1 Set Intent for startActivity (intent) ; Q map menu item

new AlertDialog.Builder(this) .setTitle(getResources() .getString(R.string.alert_label)) .setMessage(R.string.no_location_message)

.setPositiveButton("Continue", new OnClickListener() { public void onClick(DialogInterface dialog, int argl) {

return true;

case MENU_CALL_REVIEW:

&& !this.phone.getText().equals("") && !this.phone.getText().equals("NA")) { String phoneString =

parsePhone(this.phone.getText().toString()); intent = new Intent(Intent.ACTION_CALL,

Uri . parse ("tel:" + phoneString)); <-1 Set Intent for startActivity(intent) ; © call menu item

new AlertDialog.Builder(this) .setTitle(getResources() .getString(R.string.alert_label)) .setMessage(R.string.no_phone_message)

.setPositiveButton("Continue", new OnClickListener() { public void onClick(DialogInterface dialog, int arg1) {

return true;

return super.onMenuItemSelected(featureId, item);

The Review object that the ReviewDetail Activity displays to the user contains the address and phone number for a restaurant and a link to the full online review. Using this Activity the user can choose, through the menu, to display a map with directions to the restaurant, call the restaurant, or view the full review in a web browser. To allow all of these actions to take place, ReviewDetail uses built-in Android applications, through implicit Intent calls.

First, an Intent class instance is initialized to null O, so it can later be used by the various menu cases. Then, if the MENU_WEB_REVIEW menu button is selected by the user, we create a new instance of the Intent variable by passing in an action and some data Q. For the action we are using the String constant Intent.ACTION_VIEW. The value of this constant is android.app.action.VIEW, a fully qualified String including the package so as to be unique. The Intent class has a host of constants like this that represent common actions, for example, Intent.ACTION_EDIT, Intent.ACTION_INSERT, and Intent. ACTION_DELETE. Various activities and services use these same values when they declare they support a particular Intent (and you can reuse these constants, too, where applicable; see the Android Javadocs for a complete list of what is available: http:// code.google.com/android/reference/android/content/Intent.html).

After the action is declared, the data comes into play. In this case we are using Uri.parse(link) to specify a Uri (where link is an HTTP URL). The parse(String s) method simply parses the parts of a URI and creates a Uri object. This Uri is used in the resolution process we will cover next. Basically, the type can be derived from the Uri, or else the scheme, authority, and path themselves can be used. This allows the correct component to answer the startActivity(Intent i) request © and render the resource identified by the Uri. As you can see, we haven't directly declared any particular Activity or Service for the Intent; we are simply saying we want to VIEW http://somehost/somepath. This is the late-binding aspect in action. When it comes to a web URL, it's pretty obvious how this works, but the same concept is applied in Android with many other built-in data types (and you can define your own when necessary, as you shall see).

The next menu item ReviewDetail handles is for the MENU_MAP_REVIEW case, where we see the Intent reinitialized to use the Intent.ACTION_VIEW again, but this time with a different type of Uri being parsed: "geo:0,0?q=" + street_address ©. This combination of VIEW and geo scheme invokes a different Intent, this time within the built-in maps application. And finally, we see the MENU_MAP_CALL case, where the Intent is reinitialized again, this time to make a phone call using the Intent. ACTION_CALL and the tel: Uri scheme Q.

Through those simple statements, our RestaurantFinder application is using implicit Intent invocation to allow the user to phone or map the restaurant selected or to view the full review web page. These menu buttons are shown in the screen shot in figure 4.1.

To get the menu buttons on the ReviewDetail activity of the RestaurantFinder sample application to work, we did not have to code all the functionality ourselves; we simply had to leverage the existing applications Android provides by telling the platform our intentions. Those last steps complete the RestaurantFinder application, which can now search for reviews, allow the user to select a particular review from a list, display a detailed review, and use additional built-in applications to find out more about a selected restaurant.

You will learn more about all of the built-in apps and action-data pairs in section 4.1.3. Now we turn our focus to more detail on the Intent-resolution process, where we will uncover more about Intent action and data.

4.1.2 Intent resolution

Three types of Android components can register to be Intent handlers: Activity, BroadcastReceiver, and Service. These components typically register with the platform to be the destination for particular intent types using the <intent-filter> element in the AndroidManifest.xml file, as we have seen.

Each <intent-filter> element is parsed into an IntentFilter object. When a package is installed on the platform, the components within are registered, including the Intent filters. Once the platform has a registry of Intent filters, it basically knows how to map any Intent requests that come in to the correct installed Activity, BroadcastReceiver, or Service.

Figure 4.1 The menu buttons on the RestaurantFinder sample application, used for invoking respective intents

When an Intent is requested, resolution takes place through the registered filters, using the action, data, and categories of the Intent. There are two basic rules about matching Intent to IntentFilter that you should be aware of:

■ The action and category must match.

■ If specified, the data type must match, or the combination of data scheme and authority and path must match.

In the next few sections we will explore these aspects in greater detail, as they are paramount to understanding how Intent classes work. ACTION AND CATEGORIES

The action and category parts are pretty simple. These boil down to String objects, one for the action, potential multiples for the categories. If the action is not specified in the IntentFilter, it will then match any action coming from an Intent (all actions work). With categories, the IntentFilter is a superset. An IntentFilter can have additional categories beyond what an Intent specifies to match but must have at least what the Intent specifies. Also, unlike with an action, an IntentFilter with no categories will match only an Intent with no categories (it is not treated as a wildcard). So first, action and category specifications have to match.

Before we move on to the next matching component, data, it's important to understand that data is optional. You can work with action and category alone, and in many cases that suffices. This is, for example, the technique we used in the ReviewList Activity we built in chapter 3. There the IntentFilter was defined (in the manifest XML), as shown in listing 4.2.

Listing 4.2 Manifest declaration of ReviewList Activity with intent-filter

<activity android:name="ReviewList" android:label="@string/app_name"> <intent-filter> <category android:name="android.intent.category.DEFAULT" /> <action android:name="com.msi.manning.restaurant.VIEW_LIST" /> </intent-filter> </activity>

To match the filter declared in listing 4.2, we used the following Intent in code (where Constants.INTENT_ACTION_VIEW_LIST is the String com.msi.manning. restaurant.VIEW_LIST):

Intent intent = new Intent (Constants . INTENT_ACTION_VIEW_LIST) ; startActivity(intent);

NOTE The DEFAULT category designation on an Activity means that the Activity should be present as an option for the default action—center button press—for a particular type of data. This is usually specified in an IntentFilter, but it does not typically need to be present in an Intent (the filter will still match; categories are a superset).

DATA

After the action and categories are resolved, Intent data comes into play. The data can be either an explicit MIME type or a combination of scheme, authority, and path. Either of these data forms can be derived from a Uri. The Uri shown in figure 4.2 is an example of using scheme, authority, and path.

As opposed to scheme, authority, and path, using an explicit MIME type within a Uri looks like the following:

content://com.google.provider.NotePad/notes

You might reasonably ask how this is differentiated from scheme/authority/path, because those elements are really still there. The answer is the content:// scheme. That indicates a type override to the platform. The type itself is defined in the manifest of the package supplying the content provider. We will look at more details concerning content providers later in this chapter.

When IntentFilter classes are defined, they set the boundaries for what they will match in terms of type, scheme, authority, and path. A somewhat convoluted resolution path follows:

1 If scheme is present and type is not present, intents with any type will match.

2 If type is present and scheme is not present, intents with any scheme will match.

3 If neither scheme nor type is present, only intents with neither scheme nor type will match.

4 If an authority is specified, a scheme must also be specified.

5 If a path is specified, a scheme and authority must also be specified.

The majority of times what you are matching will be fairly straightforward, but as you can see, with these rules and multiple levels of authorities and schemes, it can get complicated. To boil down Intent resolution, think of Intent and IntentFilter as separate pieces of the same puzzle. When you call an Intent in an Android application, the system resolves the Activity or Service (or BroadcastReceiver) to handle your request through this resolution process using the action, categories, and data (type or scheme, authority, and path) provided. The system searches all the pieces of the puzzle it has until it finds one that meshes with the one you have just handed it, and then it snaps those pieces together to make the late-binding connection.

A more involved example of this matching is shown in figure 4.3. There you can see that an IntentFilter is defined with an action, the default category, and a combination of scheme and authority (leaving out the path so that any path will match). An example of an Intent that would match this filter is also shown, in this case using a Uri that is passed in by the next sample application we will build, WeatherReporter.

The IntentFilter shown in figure 4.3 matches with the action, category, and data (extracted from the Uri passed in) of the Intent being used. This Intent and filter come from the next sample application we are going to begin working on, a weather:// com.msi.manning/loc?zip=12345

weather:// com.msi.manning/loc?zip=12345

scheme authority path

Figure 4.2 The portions of a URI that are used in Android, showing scheme, authority, and path scheme authority path

Figure 4.2 The portions of a URI that are used in Android, showing scheme, authority, and path

weather-reporting and -alerting application. This application will carry us through the remaining concepts in this chapter and into the next.

4.1.3 Matching a custom URI

The concept behind WeatherReporter, the next sample application we will build, is that it will make use of the Yahoo! Weather API to retrieve weather data and display it to the user on the Android platform. Optionally this application will also alert users of severe weather for locations they have indicated they are interested in (based on either the current location of the device or the specified postal code).

Within this project you will see how a custom URI can be defined and registered with a matching Intent filter to allow any other application to invoke a weather report through an Intent. (Defining and publishing an Intent in this way allows other applications to easily use our application.) When complete, the main screen of the WeatherRe-porter application will look like what is shown in figure 4.4.

Ssn Francis», Cfl us

Date:

Fair (night)

Temperature: 52 F (wind [Ml 52 F) Barometer: 30.1 S and Steady Humidity: 69»- Wind: Omph Sunrise: 7:18 am - Sunset &:33 pm

Forecast:

Tue:

Sunny High:73 F loikss f

Specify location

Saved locations

Specify location

Saved locations

Figure 4.4 The main screen in the sample WeatherReporter application showing the weather forecast for the current location and a check box to indicate whether alerts should be enabled

To begin this application we first have to cover basics, such as the manifest file. Although we have already explored manifest files in previous chapters, here we will fill in details for this application, and we will further reinforce how Intent filters are defined in XML. The manifest for WeatherReporter is shown in listing 4.3.

Listing 4.3 The Android manifest file for the WeatherReporter application

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.msi.manning.weather"> <application android:icon="@drawable/weather_sun_clouds_120" android:label="@string/app_name" android:theme="@android:style/Theme.Black" android:allowClearUserData="true">

<activity android:name="ReportViewSavedLocations" android: label= "@string/app_name_view_saved_locations" />

<activity android:name="ReportSpecifyLocation" android:label=

"@string/app_name_specify_location" />

Define activities

<activity android:name="ReportViewDetail" android: label= "@string/app_name_view_detail"> <intent-filter>

<action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="weather" android:host="com.msi.manning" /> </intent-filter> <intent-filter>

<action android:name="android.intent.action.VIEW" /> <data android:scheme="weather" android:host= "'com.msi.manning" /> </intent-filter> <intent-filter>

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

"'android. intent. category. LAUNCHER " /> </intent-filter>

</activity>

<receiver android:name=",service.WeatherAlertServiceReceiver"> <-'

<intent-filter>

<action android:name=

"android, intent. action. BOOT_COMPLETED" /> </intent-filter>

</receiver> Q Define a <service android:name= ".service.WeatherAlertService" /> <1-' service

</application>

Include necessary permissions Q

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

<uses-permission android :name=

"android.permission.ACCESS_FINE_LOCATION" />

<uses-permission

. , Include necessary permissions Q

android:name= ' r

"android.permission. ACCESS_LOCATION_EXTRA_COMMANDS " / >

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

</manifest>

In the WeatherReporter manifest we have three activities defined O. The most interesting is the ReportViewDetail Activity, which we will show a portion of in listing 4.4. This Activity has multiple Intent filters defined that match it, including one denoting it is the MAIN LAUNCHER, and one with the weather://com.msi.manning scheme and authority shown in figures 4.2 and 4.3. This is the custom URI our application supports.

You can use any combination of scheme, authority, and path—as we have here—or you can use an explicit MIME type. We will find out more about MIME types and how they are processed in chapter 5, where will look specifically at how to work with data sources and use an Android concept known as a ContentProvider.

After these activities we use the <receiver> element in the manifest file to refer to a BroadcastReceiver class ©. We will uncover what a BroadcastReceiver is all about in section 4.2, but the important part for now is that an <intent-filter> is also used here to associate an Intent—in this case for the BOOT_COMPLETED action. With this association we are telling the platform to invoke the WeatherAlertServiceReceiver class after the boot-up sequence is completed.

In our manifest we also have a Service definition ©. You will see how this Service is built, and how it is used with our WeatherReporter application to poll for severe weather alerts in the background, in section 4.3. The last thing in our manifest is a series of permissions the application requires Q.

With the foundation for our sample application in place via the manifest, the next thing we need to look at is the onStart method of main Activity WeatherReporter will use, which is shown in listing 4.4. This is where data from the Uri that matches the Intent filter is parsed and used to display a weather report.

Listing 4.4 onStart method of the ReportViewDetail Activity

@Override public void onStart () { O Establish super. onStart () ; I database helper C Get d.evice this .dbHelper = new DBHelper (this) ; <-' I location this.deviceZip = WeatherAlertService.deviceLocationZIP;

&& (getIntent().getData().getEncodedQuery() != null) && (getIntent().getData().getEncodedQuery().length() > 8)) { String queryString =

getIntent () . getData () . getEncodedQuery () ; <1-1 Parse this . reportZip — queryString. substring (4 , 9) ; Intent data this.useDeviceLocation — false; } else {

this.reportZip — this.deviceZip; this.useDeviceLocation — true;

this.savedLocation = this.dbHelper.get(this.reportZip);

this.deviceAlertEnabledLocation =

this .dbHelper .get (DBHelper . DEVICE_ALERT_ENABLED_ZIP);

if (this.useDeviceLocation) {

this.currentCheck.setText(R.string.view_checkbox_current); if (this.deviceAlertEnabledLocation != null) {

this . currentCheck. setChecked (true) ; <1-1 Set status of

} else { E alert-enabled this.currentCheck.setChecked(false); <-' check box

this . currentCheck. setText (R. string. view_checkbox_specific); if (this.savedLocation != null) {

if (this.savedLocation.alertenabled == 1) {

this . currentCheck. setChecked (true) ; <1-1 Set status of

} else { E alert-enabled this. currentCheck. setChecked (false) ; <1-' check box

loadReport (this . reportZip) ; <—© Load weather report

The complete ReportViewDetail Activity can be obtained by grabbing the source code in its entirety from http://www.manning.com/UnlockingAndroid. In the portion of the class shown in listing 4.4, the onStart method, we are focusing on parsing data from the Uri passed in as part of the Intent that invokes the Activity.

First in this class snippet we are establishing a database helper object O. This will be used to query a local SQLite database that stores user-specified location data. We will show more about how data is handled in general, and the details of this helper class, in chapter 5.

In this method we are also obtaining the postal code of the current device location from a LocationManager in the WeatherAlertService class (defaulting to 94102, San Francisco, CA) Q. This is significant because it's important to understand that we want our application to be location-aware. We want the location of the device (wherever it is) to be the default weather report and alert location. As the user travels with the phone, this location should automatically be updated. We will cover more about location and LocationManager in chapter 11. For now, note that the device location is returned to us here as a postal code.

After obtaining the device location, we move on to the key aspect of obtaining Uri data from an Intent. We are parsing the Uri passed in to obtain the queryString and embedded postal code to use for the user's specified location ©. if this location is present, we use it; if not, we default to the device location postal code.

Once we have determined the postal code to use, we move on to set the status of the check box that indicates whether or not alerts should be enabled for the location being displayed E. We have two kinds of alerts: one for the device location (wherever that location may be at a given time) and another for the user's specified saved locations.

Finally, we call the loadReport method, which is used to make the call out to the Yahoo! Weather API to obtain data, and then we use a Handler to send a Message to update the needed UI View elements Q. These details are not shown in this code portion, because we are focusing on Intent handling in this section, but the pattern is the same one used in previous listings.

The key with this Activity is the way it is registered in the manifest to receive weather: / / com.msi. manning intents and then parses the path of the URI for data. This allows any application to invoke this Activity without knowing any details other than the URI. This is the separation-of-responsibilities pattern the Android platform design encourages at work (the late binding).

Now that you've seen the manifest and pertinent details of the main Activity class for the WeatherReporter application we will be building in the next few sections, and we have covered a good bit about how Intent and IntentFilter classes work together to wire up calls between components in general, we will take a look at some of the built-in Android applications that work the same way. These enable you to launch activities by simply passing in the correct URI.

4.1.4 Using Android-provided activities

Another way to get a feel for how Intent resolution works in Android and how URIs are used is to explore the built-in Activity support. Android ships with a very useful set of core applications that provide access via the formats shown in table 4.2.

Table 4.2 Common Android application Intent action and Uri combinations and the purpose of each

Action

Uri

Description

Intent

ACTION_

_VIEW

geo:latitude,longitude

Opens the maps application to the specified latitude and longitude

Intent

ACTION_

_VIEW

geo:0,0?q=street+address

Opens the maps application to the specified address

Intent

ACTION_

_CALL

tel:phone_number

Opens the phone application and calls the specified number

Intent

ACTION_

_DIAL

tel:phone_number

Opens the phone application and dials (but does not call) the specified number

Intent

ACTION_

_DIAL

voicemail:

Opens the phone application and dials (but does not call) the voice-mail number

Intent

ACTION_

_VIEW

http://web_address

Opens the browser application to the specified URL

Intent

ACTION_

_VIEW

https://web_address

Opens the browser application to the specified URL

Intent

ACTION_

_WEB_SEARCH

plain_text

Opens the browser application and use Google Search

Using the actions and URIs shown in table 4.2, you can hook into the built-in maps application, phone application, or browser application. These powerful applications are very easy to invoke using the correct Intent. We used several of these in the last chapter with our RestaurantFinder application. Android also includes support for another construct, the ContentProvider, which also uses a form of a URI to provide access to data. You will learn more about this system, which is what exposes the contacts and media parts of the Android system, in chapter 5.

By comparing the actions and URIs for the built-in Android applications, you can get a feel for the fact that some applications use a Uri that is parsed into a type (contacts, media), and others use the scheme, or scheme and authority, or scheme and authority and path—the various ways to match data discussed in section 4.1.2.

With a handle on the basics of resolution and a quick look at built-in intents out of the way, we need to get back to our WeatherReporter sample application. The next thing we will discuss is another usage for the Intent concept, namely, using a BroadcastReceiver.

0 0

Post a comment