|
Cyril Sermon (@admin) |
The anonymous nature of runtime binding makes it important to understand how Android resolves an implicit Intent into a particular application component.
As you saw previously, when using startActivity, the implicit Intent resolves to a single Activity. If there are multiple Activities capable of performing the given action on the specified data, the “best” of those Activities will be launched.
The process of deciding which Activity to start is called Intent resolution. The aim of Intent resolution is to find the best Intent Filter match possible using the following process:
Android puts together a list of all the Intent Filters available from the installed packages.
Intent Filters that do not match the action or category associated with the Intent being resolved are removed from the list.
2.1. Action matches are made if the Intent Filter either includes the specified action or has no action specified.
An Intent Filter will only fail the action match check if it has one or more actions defined, where none of them match the action specified by the Intent.
2.2. Category matching is stricter. Intent Filters must include all the categories defined in the resolving Intent. An Intent Filter with no categories specified only matches Intents with no categories.
Finally, each part of the Intent’s data URI is compared to the Intent Filter’s data tag. If Intent Filter defines the scheme, host/authority, path, or mime type, these values are compared to the Intent’s URI. Any mismatches will remove the Intent Filter from the list.
Specifying no data values in an Intent Filter will match with all Intent data values.
3.1. The mime type is the data type of the data being matched. When matching data types, you can use wild cards to match subtypes (e.g., earthquakes/*). If the Intent Filter spec-ifies a data type, it must match the Intent; specifying no data type resolves to all of them.
3.2. The scheme is the “protocol” part of the URI — for example, http:, mailto:, or tel:.
3.3. The host name or “data authority” is the section of the URI between the scheme and the path (e.g., www.google.com). For a host name to match, the Intent Filter’s scheme must also pass.
3.4. The data path is what comes after the authority (e.g., /ig). A path can only match if the scheme and host-name parts of the data tag also match.
If more than one component is resolved from this process, they are ordered in terms of priority, with an optional tag that can be added to the Intent Filter node. The highest ranking component is then returned.
Native Android application components are part of the Intent resolution process in exactly the same way as third-party applications. They do not have a higher priority and can be completely replaced with new Activities that declare Intent Filters that service the same action requests.
When an application component is started through an implicit Intent, it needs to find the action it is to perform and the data upon which to perform it.
Call the getIntent method — usually from within the onCreate method — to extract the Intent used to launch a component, as shown below:
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
Intent intent = getIntent();
}
Use the getData and getAction methods to find the data and action of the Intent. Use the type-safe get<type>Extra methods to extract additional information stored in its extras Bundle.
String action = intent.getAction();
Uri data = intent.getData();
You can use the startNextMatchingActivity method to pass responsibility for action handling to the next best matching application component, as shown in the snippet below:
Intent intent = getIntent();
if (isAfterMidnight)
startNextMatchingActivity(intent);
This allows you to add additional conditions to your components that restrict their use beyond the abil-ity of the Intent Filter–based Intent resolution process.
In some cases, your component may wish to perform some processing, or offer the user a choice, before passing the Intent on to the native handler.
In this example, you’ll create a new sub-Activity that services the PICK_ACTION for contact data. It dis-plays each of the contacts in the contact database and lets the user select one, before closing and return-ing its URI to the calling Activity.
It’s worth noting that this example is somewhat contrived. Android already supplies an Intent Filter for picking a contact from a list that can be invoked by using the content:/contacts/people/ URI in an implicit Intent. The purpose of this exercise is to demonstrate the form, even if this particular implementation isn’t overly useful.
Create a new ContactPicker project that includes a ContactPicker Activity. package com.paad.contactpicker;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Contacts.People;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.AdapterView.OnItemClickListener;
public class ContactPicker extends Activity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
}
}
Modify the main.xml layout resource to include a single ListView control. This control will be used to display the contacts.
<?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”>
<ListView
android:id=”@+id/contactListView”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
/>
</LinearLayout>
Create a new listitemlayout.xml layout resource that includes a single Text View. This will be used to display each contact in the List View.
<?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”>
<TextView
android:id=”@+id/itemTextView”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:padding=”10px”
android:textSize=”16px”
android:textColor=”#FFF”
/>
</LinearLayout>
Return to the ContactPicker Activity. Override the onCreate method, and extract the data path from the calling Intent.
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
Intent intent = getIntent();
String dataPath = intent.getData().toString();
4.1. Create a new data URI for the people stored in the contact list, and bind it to the List View using a SimpleCursorArrayAdapter.
The SimpleCursorArrayAdapter lets you assign Cursor data, used by Content Providers, to Views. It’s used here without further comment but is examined in more detail later in this chapter.
final Uri data = Uri.parse(dataPath + “people/”);
final Cursor c = managedQuery(data, null, null, null, null);
String[] from = new String[] {People.NAME};
int[] to = new int[] { R.id.itemTextView };
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
R.layout.listitemlayout,
c,
from,
to);
ListView lv = (ListView)findViewById(R.id.contactListView); lv.setAdapter(adapter);
4.2. Add an ItemClickListener to the List View. Selecting a contact from the list should return a path to the item to the calling Activity.
lv.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int pos,
long id) {
Move the cursor to the selected item c.moveToPosition(pos);
Extract the row id.
int rowId = c.getInt(c.getColumnIndexOrThrow(“_id”)); // Construct the result URI.
Uri outURI = Uri.parse(data.toString() + rowId);
Intent outData = new Intent();
outData.setData(outURI);
setResult(Activity.RESULT_OK, outData);
finish();
}
});
4.3. Close off the onCreate method.
}
Modify the application manifest and replace the intent-filter tag of the Activity to add sup-port for the pick action on contact data.
<?xml version=”1.0” encoding=”utf-8”?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android” package=”com.paad.contactpicker”>
<application android:icon=”@drawable/icon”>
<activity android:name=”ContactPicker”
android:label=”@string/app_name”>
<intent-filter>
<action android:name=”android.intent.action.PICK”/> <category android:name=”android.intent.category.DEFAULT”/> <data android:path=”contacts”
android:scheme=”content”>
</data>
</intent-filter>
</activity>
</application>
</manifest>
This completes the sub-Activity. To test it, create a new test harness ContentPickerTester Activity. Create a new layout resource — contentpickertester — that includes a TextView to display the selected contact and a Button to start the sub-Activity.
<?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”>
<TextView
android:id=”@+id/selected_contact_textview”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
/>
<Button
android:id=”@+id/pick_contact_button”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”Pick Contact”
/>
</LinearLayout>
Override the onCreate method of the ContentPickerTester to add a Click Listener to the button so that it implicitly starts a new sub-Activity by specifying the PICK_ACTION and the contact database URI (content://contacts/).
package com.paad.contactpicker;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Contacts.People;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class ContentPickerTester extends Activity {
public static final int PICK_CONTACT = 1;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.contentpickertester);
Button button = (Button)findViewById(R.id.pick_contact_button);
button.setOnClickListener(new OnClickListener() { public void onClick(View _view) {
Intent intent = new Intent(Intent.ACTION_PICK, Uri.parse(“content://contacts/”));
startActivityForResult(intent, PICK_CONTACT);
}
});
}
}
When the sub-Activity returns, use the result to populate the Text View with the selected con-tact’s name.
@Override
public void onActivityResult(int reqCode, int resCode, Intent data) { super.onActivityResult(reqCode, resCode, data);
switch(reqCode) {
case (PICK_CONTACT) : {
if (resCode == Activity.RESULT_OK) {
Uri contactData = data.getData();
Cursor c = managedQuery(contactData, null, null, null, null);
c.moveToFirst();
String name;
name = c.getString(c.getColumnIndexOrThrow(People.NAME)); TextView tv;
tv = (TextView)findViewById(R.id.selected_contact_textview); tv.setText(name);
}
break;
}
}
}
With your test harness complete, simply add it to your application manifest. You’ll also need to add a READ_CONTACTS permission within a uses-permission tag, to allow the application to access the contacts database.
<?xml version=”1.0” encoding=”utf-8”?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.paad.contactpicker”>
<application android:icon=”@drawable/icon”>
<activity android:name=”.ContactPicker”
android:label=”@string/app_name”>
<intent-filter>
<action android:name=”android.intent.action.PICK”/> <category android:name=”android.intent.category.DEFAULT”/> <data android:path=”contacts” android:scheme=”content”/>
</intent-filter>
</activity>
<activity android:name=”.ContentPickerTester” android:label=”Contact Picker Test”>
<intent-filter>
<action android:name=”android.intent.action.MAIN”/> <category android:name=”android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
<uses-permission android:name=”android.permission.READ_CONTACTS”/> </manifest>
When your Activity is running, press the button. The contact picker Activity should be shown as in Figure 5-1.
Once you select a contact, the parent Activity should return to the foreground with the selected contact name displayed, as shown in Figure 5-2.