Leveraging Content Providers - Android Application Design Essentials - Introduction to Android Application Development, Fourth Edition (2014)

Introduction to Android Application Development, Fourth Edition (2014)

Part IV. Android Application Design Essentials

Chapter 13. Leveraging Content Providers

Applications can access data within other applications on the Android system through content provider interfaces and expose internal application data to other applications by becoming a content provider. Content providers are the way applications can access user information, including contact data, images, audio and video on the device, and much more. In this chapter, we take a look at some of the content providers available on the Android platform and what you can do with them.


Image Warning

Always run content provider code on test devices, not your personal devices. It is very easy to accidentally wipe out all of the Contacts database, your browser bookmarks, or other types of data on your devices. Consider this fair warning, because we discuss operations such as how to query (generally safe) and modify (not so safe) various types of device data in this chapter.


Exploring Android’s Content Providers

Android devices ship with a number of built-in applications, many of which expose their data as content providers. Your application can access content provider data from a variety of sources. You can find the content providers included with Android in the package android.provider. Table 13.1 lists some useful content providers in this package.

Image

Table 13.1 Useful Built-in Content Providers

Now let’s look at some of the most popular and official content providers in more detail.


Image Tip

The code examples provided in this chapter use the CursorLoader class for performing cursor queries on a background thread using the loadInBackground() method. This prevents your application from blocking on the UI thread when performing cursor queries. This approach has replaced the Activity class method managedQuery() for performing cursor queries that block the UI thread and is officially deprecated. If you are targeting devices earlier than Honeycomb, you’ll want to import the CursorLoader class from the Android Support Package using android.support.v4.content.CursorLoader rather than importing the class from android.content.CursorLoader.


Using the MediaStore Content Provider

You can use the MediaStore content provider to access media on the phone and on external storage devices. The primary types of media you can access are audio, images, and video. You can access these different types of media through their respective content provider classes underandroid.provider.MediaStore.

Most of the MediaStore classes allow full interaction with the data. You can retrieve, add, and delete media files from the device. There are also a handful of helper classes that define the most common data columns that can be requested.

Table 13.2 lists some commonly used classes you can find under android.provider.MediaStore.

Image

Table 13.2 Common MediaStore Classes


Image Tip

Many of the code examples provided in this section are taken from the SimpleContentProvider application. The source code for the SimpleContentProvider application is provided for download on the book’s website.


The following code demonstrates how to request data from a content provider. A query is made to the MediaStore to retrieve the titles of all the audio files on the SD card of the handset and their respective durations. This code requires that you load some audio files onto the virtual SD card in the emulator.

String[] requestedColumns = {
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.DURATION
};

CursorLoader loader = new CursorLoader(this,
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
requestedColumns, null, null, null);
Cursor cur = loader.loadInBackground();

Log.d(DEBUG_TAG, "Audio files: " + cur.getCount());
Log.d(DEBUG_TAG, "Columns: " + cur.getColumnCount());

int name = cur.getColumnIndex(MediaStore.Audio.Media.TITLE);
int length = cur.getColumnIndex(MediaStore.Audio.Media.DURATION);

cur.moveToFirst();
while (!cur.isAfterLast()) {
Log.d(DEBUG_TAG, "Title" + cur.getString(name));
Log.d(DEBUG_TAG, "Length: " +
cur.getInt(length) / 1000 + " seconds");
cur.moveToNext();
}

The MediaStore.Audio.Media class has predefined strings for every data field (or column) exposed by the content provider. You can limit the audio file data fields requested as part of the query by defining a String array with the column names required. In this case, we limit the results to only the track title and the duration of each audio file.

We then use a CursorLoader and access the cursor using a loadInBackground() method call. The first parameter of the CursorLoader is the application context. The second parameter is the predefined URI of the content provider you want to query. The third parameter is the list of columns to return (audio file titles and durations). The fourth and fifth parameters control any selection-filtering arguments, and the sixth parameter provides a sort method for the results. We leave these null because we want all audio files at this location. By using the loadInBackground() method, we get a Cursor as a result. We then examine our Cursor for the results.

Using the CallLog Content Provider

Android provides a content provider to access the call log on the handset via the class android.provider.CallLog. At first glance, the CallLog might not seem to be a useful provider for developers, but it has some nifty features. You can use the CallLog to filter recently dialed calls, received calls, and missed calls. The date and duration of each call are logged and tied back to the Contacts application for caller identification purposes.

The CallLog is a useful content provider for customer relationship management (CRM) applications. The user can also tag specific phone numbers with custom labels within the Contacts application.

To demonstrate how the CallLog content provider works, let’s look at a hypothetical situation where we want to generate a report of all calls to a number with the custom label HourlyClient123. Android allows for custom labels on these numbers, which we leverage for this example:

String[] requestedColumns = {
CallLog.Calls.CACHED_NUMBER_LABEL,
CallLog.Calls.DURATION
};

CursorLoader loader = new CursorLoader(this,
CallLog.Calls.CONTENT_URI,
requestedColumns,
CallLog.Calls.CACHED_NUMBER_LABEL + " = ?",
new String[] { "HourlyClient123" },
null);
Cursor calls = loader.loadInBackground();

Log.d(DEBUG_TAG, "Call count: " + calls.getCount());

int durIdx = calls.getColumnIndex(CallLog.Calls.DURATION);
int totalDuration = 0;

calls.moveToFirst();
while (!calls.isAfterLast()) {
Log.d(DEBUG_TAG, "Duration: " + calls.getInt(durIdx));
totalDuration += calls.getInt(durIdx);
calls.moveToNext();
}

Log.d(DEBUG_TAG, "HourlyClient123 Total Call Duration: " + totalDuration);

This code is similar to the code shown for the MediaStore audio files. Again, we start with listing our requested columns: the call label and the duration of the call. This time, however, we don’t want to get every call in the log, only those with a label of HourlyClient123. To filter the results of the query to this specific label, it is necessary to specify the fourth and fifth parameters of the CursorLoader. Together, these two parameters are equivalent to a database WHERE clause. The fourth parameter specifies the format of the WHERE clause with the column name, with selection parameters (shown as ?) for each selection argument value. The fifth parameter, the String array, provides the values to substitute for each of the selection arguments (?) in order as you would do for a simple SQLite database query.

We use the same method to iterate the records of the Cursor and add up all the call durations.

Accessing Content Providers That Require Permissions

Your application needs a special permission to access the information provided by the CallLog content provider. You can declare the <uses-permission> tag using the Android IDE Wizard, or you can add the following to your AndroidManifest.xml file:

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

Although it’s a tad confusing, there is no CallLog provider permission. Instead, applications that access the CallLog use the READ_CONTACTS permission. Although the values are cached within this content provider, the data is similar to what you might find in the contacts provider.


Image Tip

You can find all available permissions in the class android.Manifest.permission.


Using the Browser Content Provider

Another useful built-in content provider is the Browser. The Browser content provider exposes the user’s browser site history and bookmarked websites. You access this content provider via the android.provider.Browser class. As with the CallLog class, you can use the information provided by the Browser content provider to generate statistics and to provide cross-application functionality. You might use the Browser content provider to add a bookmark for your application support website.

In this example, we query the Browser content provider to find the top five most frequently visited bookmarked sites.

String[] requestedColumns = {
Browser.BookmarkColumns.TITLE,
Browser.BookmarkColumns.VISITS,
Browser.BookmarkColumns.BOOKMARK
};

CursorLoader loader = new CursorLoader(this,
Browser.BOOKMARKS_URI,
requestedColumns,
Browser.BookmarkColumns.BOOKMARK + "=1",
null,
Browser.BookmarkColumns.VISITS + " DESC limit 5");
Cursor faves = loader.loadInBackground();

Log.d(DEBUG_TAG, "Bookmarks count: " + faves.getCount());

int titleIdx = faves.getColumnIndex(Browser.BookmarkColumns.TITLE);
int visitsIdx = faves.getColumnIndex(Browser.BookmarkColumns.VISITS);
int bmIdx = faves.getColumnIndex(Browser.BookmarkColumns.BOOKMARK);

faves.moveToFirst();

while (!faves.isAfterLast()) {
Log.d("SimpleBookmarks", faves.getString(titleIdx) + " visited "
+ faves.getInt(visitsIdx) + " times : "
+ (faves.getInt(bmIdx) != 0 ? "true" : "false"));
faves.moveToNext();
}

Again, the requested columns are defined, the query is made, and the cursor iterates through the results.

Note that the CursorLoader has become substantially more complex. Let’s take a look at the parameters to this method in more detail. The second parameter, Browser.BOOKMARKS_URI, is a URI for all browser history, not only the bookmarked items. The third parameter defines the requested columns for the query results. The fourth parameter specifies that the bookmark property must be true. This parameter is needed in order to filter within the query. Now the results are only browser history entries that have been bookmarked. The fifth parameter, selection arguments, is used only when replacement values are used. It is not used in this case, so the value is set to null. Last, the sixth parameter specifies an order for the results (most visited in descending order). Retrieving browser history information requires setting the READ_HISTORY_BOOKMARKS permission.


Image Tip

Notice that we also tacked a LIMIT statement onto the sixth parameter of CursorLoader. Although this is not specifically documented, we’ve found limiting the query results in this way works well and might even improve application performance in some situations where the query results are lengthy. Keep in mind that if the internal implementation of a content provider verified that the last parameter was only a valid ORDER BY clause, this may not always work. We are also taking advantage of the fact that most content providers are backed by SQLite. This need not be the case.


Using the CalendarContract Content Provider

Introduced officially in Android 4.0 (API Level 14), the CalendarContract provider allows you to manage and interact with the user’s calendar data on the device. You can use this content provider to create one-time and recurring events in a user’s calendar, set reminders, and more, provided the device user has appropriately configured calendar accounts (for example, Microsoft Exchange). In addition to the fully featured content provider, you can also quickly trigger a new event to be added to the user’s calendar using an Intent, like this:

Intent calIntent = new Intent(Intent.ACTION_INSERT);
calIntent.setData(CalendarContract.Events.CONTENT_URI);
calIntent.putExtra(CalendarContract.Events.TITLE,
"My Winter Holiday Party");
calIntent.putExtra(CalendarContract.Events.EVENT_LOCATION,
"My Ski Cabin at Tahoe");
calIntent.putExtra(CalendarContract.Events.DESCRIPTION,
"Hot chocolate, eggnog and sledding.");
startActivity(calIntent);

Here, we seed the calendar event title, location, and description using the appropriate intent Extras. These fields will be set in a form that displays for the user, who will then need to confirm the event in the Calendar app. To learn more about the CalendarContract provider, seehttp://d.android.com/guide/topics/providers/calendar-provider.html and http://d.android.com/reference/android/provider/CalendarContract.html.

Using the UserDictionary Content Provider

Another useful content provider is the UserDictionary provider. You can use this content provider for predictive text input on text fields and other user input mechanisms. Individual words stored in the dictionary are weighted by frequency and organized by locale. You can use the addWord()method within the UserDictionary.Words class to add words to the custom user dictionary.

Using the VoicemailContract Content Provider

The VoicemailContract content provider was introduced in API Level 14. You can use this content provider to add new voicemail content to the shared provider so that all voicemail content is accessible in one place. Application permissions, such as the ADD_VOICEMAIL permission, are necessary for accessing this provider. For more information, see the Android SDK documentation for the VoicemailContract class at http://d.android.com/reference/android/provider/VoicemailContract.html.

Using the Settings Content Provider

Another useful content provider is the Settings provider. You can use this content provider to access the device settings and user preferences. Settings are organized much as they are in the Settings application—by category. You can find information about the Settings content provider in theandroid.provider.Settings class. If your application needs to modify system settings, you’ll need to register the WRITE_SETTINGS or WRITE_SECURE_SETTINGS permissions in your application’s Android manifest file.

Introducing the ContactsContract Content Providers

The Contacts database is one of the most commonly used applications on Android devices. People always want contact information handy for contacting friends, family, coworkers, and clients. Additionally, most devices show the identity of a contact based on the Contacts application, including nicknames, photos, or icons.

Android provides a built-in Contacts application, and the contact data is exposed to other Android applications using the content provider interface. As an application developer, this means you can leverage the user’s contact data within your application for a more robust user experience.

The content provider for accessing user contacts was originally called Contacts. Android 2.0 (API Level 5) introduced an enhanced contacts management content provider class to manage the data available from the user’s contacts. This provider, called ContactsContract, includes a subclass called ContactsContract.Contacts. This is the preferred contacts content provider moving forward.

Your application needs special permission to access the private user information provided by the ContactsContract content provider. You must declare a <uses-permission> tag using the permission READ_CONTACTS to read this information. If your application modifies the Contacts database, you’ll need the WRITE_CONTACTS permission as well.


Image Tip

Some of the code examples provided in this section are taken from the SimpleContacts application. The source code for the SimpleContacts application is provided for download on the book’s website.


Working with the ContactsContract Content Provider

The more recent contacts content provider, ContactsContract.Contacts, introduced in API Level 5 (Android 2.0), provides a robust contact content provider that suits the more robust Contacts application that has evolved along with the Android platform.


Image Tip

The ContactsContract content provider was further enhanced in Android 4.0 (Ice Cream Sandwich, API Level 14) to incorporate substantive social networking features. Some of the new features include managing the device user’s identity, favorite methods of communication with specific contacts, as well as a new INVITE_CONTACT Intent type for applications to use to make contact connections. The device user’s personal profile is accessible through the ContactsContract.Profile class (which requires the READ_PROFILE application permission). The device user’s preferred methods of communicating with specific contacts can be accessed through the new ContactsContract.DataUsageFeedback class. For more information, see the Android SDK documentation for the android.provider.ContactsContractclass.


The following code utilizes the ContactsContract provider:

String[] requestedColumns = {
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER,
};
CursorLoader loader = new CursorLoader(this,
ContactsContract.Data.CONTENT_URI,
requestedColumns, null, null, "display_name desc limit 1");
Cursor contacts = loader.loadInBackground();

int recordCount = contacts.getCount();
Log.d(DEBUG_TAG, "Contacts count: " + recordCount);

if (recordCount > 0) {
int nameIdx = contacts
.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
int phoneIdx = contacts
.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);

contacts.moveToFirst();
Log.d(DEBUG_TAG, "Name: " + contacts.getString(nameIdx));
Log.d(DEBUG_TAG, "Phone: " + contacts.getString(phoneIdx));
}

Here, we can see that the code is using a query URI provided from the ContactsContract provider called ContactsContract.Data.CONTENT_URI. Second, you’re requesting different column names. The column names of the ContactsContract provider are organized more thoroughly to allow for far more dynamic contact configurations. This can make your queries slightly more complex. Luckily, the ContactsContract.CommonDataKinds class has a number of frequently used columns defined together. Table 13.3 shows some of the commonly used classes that can help you work with the ContactsContract content provider.

Image

Table 13.3 Commonly Used ContactsContract Data Column Classes


Image Tip

New additions have been made to the ContactsContract content provider in Android 4.3, providing the ability to query the contacts data for any changes. You now have the ability to determine the recently deleted contacts with ContactsContract.DeletedContacts. This is an extremely useful feature because prior to this addition, there was no standard way for an application to determine which contacts had been changed or deleted.


For more information on the ContactsContract provider, see the Android SDK documentation: http://d.android.com/reference/android/provider/ContactsContract.html.

Modifying Content Provider Data

Content providers are not only static sources of data. They can also be used to add, update, and delete data, if the content provider application has implemented this functionality. Your application must have the appropriate permissions (that is, WRITE_CONTACTS as opposed to READ_CONTACTS) to perform some of these actions. Let’s use the ContactsContract content provider and give some examples of how to modify the Contacts database.

Adding Records

Using the ContactsContract content provider, we can, for example, add a new record to the Contacts database programmatically. The code that follows adds a new contact named Ian Droid with a phone number of 6505551212, as shown here:

ArrayList<ContentProviderOperation> ops = new
ArrayList<ContentProviderOperation>();
int contactIdx = ops.size();

ContentProviderOperation.Builder op =

ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
op.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null);
op.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null);
ops.add(op.build());

op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
op.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
op.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
"Ian Droid");
op.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,
contactIdx);
ops.add(op.build());

op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
op.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER,
"6505551212");
op.withValue(ContactsContract.CommonDataKinds.Phone.TYPE,
ContactsContract.CommonDataKinds.Phone.TYPE_WORK);
op.withValue(ContactsContract.CommonDataKinds.Phone.MIMETYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
op.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,
contactIdx);
ops.add(op.build());

getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);

Here, we use the ContentProviderOperation class to create an ArrayList of operations to insert records into the Contacts database on the device. The first record we add with newInsert() is the ACCOUNT_NAME and ACCOUNT_TYPE of the contact. The second record we add with newInsert() is a name for the ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME column. We need to create the contact with a name before we can assign information, such as phone numbers. Think of this as creating a row in a table that provides a one-to-many relationship to a phone number table. The third record we add with newInsert() is a phone number for the contact that we are adding to the Contacts database.

We insert the data in the database found at the ContactsContract.Data.CONTENT_URI path. We use a call to getContentResolver().applyBatch() to apply all three ContentProvider operations at once using the ContentResolver associated with our Activity.


Image Tip

At this point, you might be wondering how the structure of the data can be determined. The best way is to thoroughly examine the documentation for the specific content provider with which you want to integrate your application.


Updating Records

Inserting data isn’t the only change you can make. You can update one or more rows as well. The following block of code shows how to update data within a content provider. In this case, we update a phone number field for a specific contact.

String selection = ContactsContract.Data.DISPLAY_NAME + " = ? AND " +
ContactsContract.Data.MIMETYPE + " = ? AND " +
String.valueOf(ContactsContract.CommonDataKinds.Phone.TYPE) + " = ? ";

String[] selectionArgs = new String[] {
"Ian Droid",
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
String.valueOf(ContactsContract.CommonDataKinds.Phone.TYPE_WORK)
};

ArrayList<ContentProviderOperation> ops =
new ArrayList<ContentProviderOperation>();

ContentProviderOperation.Builder op =
ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI);
op.withSelection(selection, selectionArgs);
op.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, "6501234567");
ops.add(op.build());

getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);

Again, we use the ContentProviderOperation class to create an ArrayList of operations to update a record in the Contacts database on the device—in this case, the phone number field previously given a TYPE_WORK attribute. This replaces any current phone number stored in the NUMBER field with a TYPE_WORK attribute currently stored with the contact. We add the ContentProviderOperation with the newUpdate() method and once again use applyBatch() on the ContentResolver class to complete our change. We can then confirm that only one row was updated.

Deleting Records

Now that you have cluttered up your Contacts application with sample user data, you might want to delete some of it. Deleting data is fairly straightforward. Another reminder: you should use these examples only on a test device, so you don’t accidentally delete all of your contact data from your device.

Deleting All Records

The following code deletes all rows at the given URI. Keep in mind that you should execute operations like this with extreme care.

ArrayList<ContentProviderOperation> ops =
new ArrayList<ContentProviderOperation>();

ContentProviderOperation.Builder op =
ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI);
ops.add(op.build());

getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);

The newDelete() method deletes all rows at a given URI, which in this case includes all rows at the RawContacts.CONTENT_URI location (in other words, all contact entries).

Deleting Specific Records

Often you want to select specific rows to delete by adding selection filters to remove rows matching a particular pattern.

For example, the following newDelete() operation matches all contact records with the name Ian Droid, which we used when we created the contact previously in the chapter.

String selection = ContactsContract.Data.DISPLAY_NAME + " = ? ";
String[] selectionArgs = new String[] { "Ian Droid" };

ArrayList<ContentProviderOperation> ops =
new ArrayList<ContentProviderOperation>();

ContentProviderOperation.Builder op =

ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI);
op.withSelection(selection, selectionArgs);
ops.add(op.build());

getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);

Using Third-Party Content Providers

Any application can implement a content provider to share its information safely and securely with other applications on a device. Some applications use content providers only to share information internally—within their own brand, for example. Others publish the specifications for their providers so that other applications can integrate with them.

If you poke around in the Android source code, or run across a content provider you want to use, consider this: a number of other content providers are available on the Android platform, especially those used by some of the typically installed Google applications (Calendar, Messaging, and so on). Be aware, though, that using undocumented content providers, simply because you happen to know how they work or have reverse-engineered them, is generally not a good idea. Use of undocumented and non-public content providers can make your application unstable. This post on the Android Developers Blog makes a good case for why this sort of hacking should be discouraged in commercial applications: http://android-developers.blogspot.com/2010/05/be-careful-with-content-providers.html.

Summary

Your application can leverage the data available within other Android applications, if they expose that data as content providers. Content providers such as MediaStore, Browser, CallLog, and ContactsContract can be leveraged by other Android applications, resulting in a robust, immersive experience for users. Applications can also share data among themselves by becoming content providers. Becoming a content provider involves implementing a set of methods that manage how and what data you expose for use in other applications.

Quiz Questions

1. What is the name of the content provider for accessing media on the phone and on external storage devices?

2. True or false: The MediaStore.Images.Thumbnails class is for retrieving thumbnails for image files.

3. What permission is required for accessing information provided by the CallLog content provider?

4. True or false: The READ_HISTORY permission is required for accessing the browser history information of the Browser content provider.

5. What is the method call for adding words to the custom user dictionary of the UserDictionary provider?

6. True or false: The Contacts content provider was added in API Level 5.

Exercises

1. Using the Android documentation, determine all the tables associated with the ContactsContract content provider.

2. Create an application that is able to add words that a user enters into an EditText field to the UserDictionary content provider.

3. Create an application that is able to add an email address for a contact using the ContactsContract content provider. As we have said before, always run content provider code on test devices, not your personal devices.

References and More Information

Android SDK Reference regarding the android.provider package:

http://d.android.com/reference/android/provider/package-summary.html

Android SDK Reference regarding the AlarmClock content provider:

http://d.android.com/reference/android/provider/AlarmClock.html

Android SDK Reference regarding the Browser content provider:

http://d.android.com/reference/android/provider/Browser.html

Android SDK Reference regarding the CallLog content provider:

http://d.android.com/reference/android/provider/CallLog.html

Android SDK Reference regarding the Contacts content provider:

http://d.android.com/reference/android/provider/Contacts.html

Android SDK Reference regarding the ContactsContract content provider:

http://d.android.com/reference/android/provider/ContactsContract.html

Android SDK Reference regarding the MediaStore content provider:

http://d.android.com/reference/android/provider/MediaStore.html

Android SDK Reference regarding the Settings content provider:

http://d.android.com/reference/android/provider/Settings.html

Android SDK Reference regarding the SearchRecentSuggestions content provider:

http://d.android.com/reference/android/provider/SearchRecentSuggestions.html

Android SDK Reference regarding the UserDictionary content provider:

http://d.android.com/reference/android/provider/UserDictionary.html

Android API Guides: “Content Providers”:

http://d.android.com/guide/topics/providers/content-providers.html