ITEEDU

Accessing Content Providers

If you want to make your data public, you can create (or call) a content provider. This is an object that can store and retrieve data accessible by all applications. This is the only way to share data across packages; there is no common storage area that all packages can share. Android ships with a number of content providers for common data types (audio, video, images, personal contact information, and so on). You can see some of Android's native content providers in the provider package.

How a content provider actually stores its data under the covers is up to the implementation of the content provider, but all content providers must implement a common convention to query for data, and a common convention to return results. However, a content provider can implement custom helper functions to make data storage/retrieval simpler for the specific data that it exposes.

This document covers two topics related to Content Providers:

Using a Content Provider to Store and Retrieve Data

This section describes how to store and retrieve data using a content provider implemented by you or anyone else. Android exposes a number of content providers for a wide range of data types, from music and image files to phone numbers. You can see a list of content providers exposed through the convenience classes in the android.provider package.

Android's content providers are loosely linked to their clients. Each content provider exposes a unique string (a URI) identifying the type of data that it will handle, and the client must use that string to store or retrieve data of that type. We'll explain this more in Querying for Data.

This section describes the following activities:

Querying for Data

Each contact provider exposes a unique public URI (wrapped by Uri) that is used by a client to query/add/update/delete data on that content provider. This URI has two forms: one to indicate all values of that type (e.g., all personal contacts), and one form to indicate a specific record of that type (e.g., Joe Smith's contact information).

  • content://contacts/people/ is the URI that would return a list of all contact names on the device.
  • content://contacts/people/23 is the URI string that would return a single result row, the contact with ID = 23. .

An application sends a query to the device that specifies a general type of item (all phone numbers), or a specific item (Bob's phone number), to retrieve. Android then returns a Cursor over a recordset of results, with a specific set of columns. Let's look at a hypothetical query string and a result set (the results have been trimmed a bit for clarity):

query = content://contacts/people/

Results:

_ID _COUNT NUMBER NUMBER_KEY LABEL NAME TYPE
13 4 (425) 555 6677 425 555 6677 California office Bully Pulpit Work
44 4 (212) 555-1234 212 555 1234 NY apartment Alan Vain Home
45 4 (212) 555-6657 212 555 6657 Downtown office Alan Vain Work
53 4 201.555.4433 201 555 4433 Love Nest Rex Cars Home

Note that the query string isn't a standard SQL query string, but instead a URI string that describes the type of data to return. This URI consists of three parts: the string "content://"; a segment that describes what kind of data to retrieve; and finally an optional ID of a specific item of the specified content type. Here are a few more example query strings:

  • content://media/internal/images is the URI string that would return a list of all the internal images on the device.
  • content://media/external/images is the URI string that would return a list of all the images on the "primary" external storage (e.g., the SD card).
  • content://contacts/people/ is the URI that would return a list of all contact names on the device.
  • content://contacts/people/23 is the URI string that would return a single result row, the contact with ID = 23.

Although there is a general form, these query URIs are somewhat arbitrary and confusing. Therefore, Android provides a list of helper classes in the android.provider package that define these query strings so you should not need to know the actual URI value for different data types. These helper classes define a string (actually, a Uri object) called CONTENT_URI for a specific data type.

Typically you will use the defined CONTENT_URI object to make a query, instead of writing the full URI yourself. So, each of the example query strings listed above (except for the last one that specifies the record ID) can be acquired with the following Uri references:

  • MediaStore.Images.Media.INTERNAL_CONTENT_URI
  • MediaStore.Images.Media.EXTERNAL_CONTENT_URI
  • Contacts.People.CONTENT_URI

To query a specific record ID (e.g., content://contacts/people/23), you'll use the same CONTENT_URI, but must append the specific ID value that you want. This is one of the few times you should need to examine or modify the URI string. So, for example, if you were looking for record 23 in the people contacts, you might run a query as shown here:

// Get the base URI for contact with _ID=23.
// This is same as Uri.parse("content://contacts/people/23");
Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);
// Query for this record.
Cursor cur = managedQuery(myPerson, null, null, null);

Tip: You can also append a string to a Uri, using withAppendedPath(Uri, String).

This query returns a cursor over a database query result set. What columns are returned, what they're called, and what they are named are discussed next. For now, though, know that you can specify that only certain columns be returned, the sort order, and a SQL WHERE clause.

You should use the Activity.managedQuery() method to retrieve a managed cursor. A managed cursor handles all the niceties such as unloading itself when the application pauses, and requerying itself when the application restarts. You can ask Android to manage an unmanaged cursor for you by calling Activity.startManagingCursor().

Let's look at an example query to retrieve a list of contact names and their primary phone numbers.

// An array specifying which columns to return. 
string[] projection = new string[] {
    People._ID,
    People.NAME,
    People.NUMBER,
};

// Get the base URI for People table in Contacts content provider.
// ie. content://contacts/people/
Uri mContacts = People.CONTENT_URI;  
       
// Best way to retrieve a query; returns a managed query. 
Cursor managedCursor = managedQuery( mContacts,
                        projection, //Which columns to return. 
                        null,       // WHERE clause--we won't specify.
                        People.NAME + " ASC"); // Order-by clause.

This query will retrieve data from the people table of the Contacts content provider. It will retrieve the name, primary phone number, and unique record ID for each contact.

What the query returns

A query returns a set of zero or more database records. The column names, order, and type are specific to the content provider, but every query includes a column called _id, which is the ID of the item in that row. If a query can return binary data, such as a bitmap or audio file, it will have a column with any name that holds a content:// URI that you can use to get this data (more information on how to get the file will be given later). Here is a tiresome example result set for the previous query:

_id name number
44 Alan Vain 212 555 1234
13 Bully Pulpit 425 555 6677
53 Rex Cars 201 555 4433

This result set demonstrates what is returned when we specified a subset of columns to return. The optional subset list is passed in the projection parameter of the query. A content manager should list which columns it supports either by implementing a set of interfaces describing each column (see Contacts.People.Phones, which extends BaseColumns, PhonesColumns, and PeopleColumns), or by listing the column names as constants. Note that you need to know the data type of a column exposed by a content provider in order to be able to read it; the field reading method is specific to the data type, and a column's data type is not exposed programmatically.

The retrieved data is exposed by a Cursor object that can be used to iterate backward or forward through the result set. You can use this cursor to read, modify, or delete rows. Adding new rows requires a different object described later.

Note that by convention, every recordset includes a field named _id, which is the ID of a specific record, and a _count field, which is a count of records in the current result set. These field names are defined by BaseColumns.

Querying for Files

The previous query result demonstrates how a file is returned in a data set. The file field is typically (but not required to be) a string path to the file. However, the caller should never try to read and open the file directly (permissions problems for one thing can make this fail). Instead, you should call ContentResolver.openInputStream() / ContentResolver.openOutputStream(), or one of the helper functions from a content provider.

Reading Retrieved Data

The Cursor object retrieved by the query provides access to a recordset of results. If you have queried for a specific record by ID, this set will contain only one value; otherwise, it can contain multiple values. You can read data from specific fields in the record, but you must know the data type of the field, because reading data requires a specialized method for each type of data. (If you call the string reading method on most types of columns, Android will give you the String representation of the data.) The Cursor lets you request the column name from the index, or the index number from the column name.

If you are reading binary data, such as an image file, you should call ContentResolver.openOutputStream() on the string content:// URI stored in a column name.

The following snippet demonstrates reading the name and phone number from our phone number query:

private void getColumnData(Cursor cur){ 
    if (cur.moveToFirst()) {

        String name; 
        String phoneNumber; 
        int nameColumn = cur.getColumnIndex(People.NAME); 
        int phoneColumn = cur.getColumnIndex(People.NUMBER);
        String imagePath; 
    
        do {
            // Get the field values
            name = cur.getString(nameColumn);
            phoneNumber = cur.getString(phoneColumn);
           
	    // Do something with the values. 
            ... 

        } while (cur.moveToNext());

    }
}

Modifying Data

To batch update a group of records (for example, to change "NY" to "New York" in all contact fields), call the ContentResolver.update() method with the columns and values to change.

Adding a New Record

To add a new record, call ContentResolver.insert() with the URI of the type of item to add, and a Map of any values you want to set immediately on the new record. This will return the full URI of the new record, including record number, which you can then use to query and get a Cursor over the new record.

ContentValues values = new ContentValues();
Uri phoneUri = null;
Uri emailUri = null;

values.put(Contacts.People.NAME, "New Contact");
//1 = the new contact is added to favorites
//0 = the new contact is not added to favorites
values.put(Contacts.People.STARRED,1);

//Add Phone Numbers
Uri uri = getContentResolver().insert(Contacts.People.CONTENT_URI, values);

//The best way to add Contacts data like Phone, email, IM is to 
//get the CONTENT_URI of the contact just inserted from People's table,
//and use withAppendedPath to construct the new Uri to insert into.
phoneUri = Uri.withAppendedPath(uri, Contacts.People.Phones.CONTENT_DIRECTORY);

values.clear();
values.put(Contacts.Phones.TYPE, Phones.TYPE_MOBILE);
values.put(Contacts.Phones.NUMBER, "1233214567");
getContentResolver().insert(phoneUri, values);

//Add Email
emailUri = Uri.withAppendedPath(uri, ContactMethods.CONTENT_DIRECTORY);

values.clear();
//ContactMethods.KIND is used to distinguish different kinds of
//contact data like email, im, etc. 
values.put(ContactMethods.KIND, Contacts.KIND_EMAIL);
values.put(ContactMethods.DATA, "test@example.com");
values.put(ContactMethods.TYPE, ContactMethods.TYPE_HOME);
getContentResolver().insert(emailUri, values);
       

To save a file, you can call ContentResolver().openOutputStream() with the URI as shown in the following snippet:

// Save the name and description in a map. Key is the content provider's
// column name, value is the value to save in that record field.
ContentValues values = new ContentValues(3);
values.put(MediaStore.Images.Media.DISPLAY_NAME, "road_trip_1");
values.put(MediaStore.Images.Media.DESCRIPTION, "Day 1, trip to Los Angeles");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

// Add a new record without the bitmap, but with the values.
// It returns the URI of the new record.
Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

try {
    // Now get a handle to the file for that record, and save the data into it.
    // sourceBitmap is a Bitmap object representing the file to save to the database.
    OutputStream outStream = getContentResolver().openOutputStream(uri);
    sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream);
    outStream.close();
} catch (Exception e) {
    Log.e(TAG, "exception while writing image", e);
}

Deleting a Record

To delete a single record, call ContentResolver.delete() with the URI of a specific row.

To delete multiple rows, call ContentResolver.delete() with the URI of the type of record to delete (for example, android.provider.Contacts.People.CONTENT_URI) and a SQL WHERE clause defining which rows to delete (Warning: be sure to include a valid WHERE clause if deleting a general type using ContentResolver.delete(), or else you risk deleting more records than you intended!).

Creating a Content Provider

Here is how to create your own content provider to act as a public source for reading and writing a new data type:

  1. Extend ContentProvider.
  2. Define a public static final Uri named CONTENT_URI. This is the string that represents the full "content://" URI that your content provider handles. You must define a unique string for this value; the best solution is to use the fully-qualified class name of your content provider (lowercase). So, for example:
    public static final Uri CONTENT_URI = Uri.parse( "content://com.google.codelab.rssprovider");
  3. Create your system for storing data. Most content providers store their data using Android's file storage methods or SQLite databases, but you can store your data any way you want, so long as you follow the calling and return value conventions.
  4. Define the column names that you will return to your clients. If you are using an underlying database, these column names are typically identical to the SQL database column names they represent. In any case, you should include an integer column named _id to define a specific record number. If using the SQLite database, this should be type INTEGER PRIMARY KEY AUTOINCREMENT. The AUTOINCREMENT descriptor is optional, but by default, SQLite autoincrements an ID counter field to the next number above the largest existing number in the table. If you delete the last row, the next row added will have the same ID as the deleted row. To avoid this by having SQLite increment to the next largest value whether deleted or not, then assign your ID column the following type: INTEGER PRIMARY KEY AUTOINCREMENT. (Note You should have a unique _id field whether or not you have another field (such as a URL) that is also unique among all records.) Android provides the SQLiteOpenHelper class to help you create and manage versions of your database.
  5. If you are exposing byte data, such as a bitmap file, the field that stores this data should actually be a string field with a content:// URI for that specific file. This is the field that clients will call to retrieve this data. The content provider for that content type (it can be the same content provider or another content provider — for example, if you're storing a photo you would use the media content provider) should implement a field named _data for that record. The _data field lists the exact file path on the device for that file. This field is not intended to be read by the client, but by the ContentResolver. The client will call ContentResolver.openOutputStream() on the user-facing field holding the URI for the item (for example, the column named photo might have a value content://media/images/4453). The ContentResolver will request the _data field for that record, and because it has higher permissions than a client, it should be able to access that file directly and return a read wrapper for that file to the client.
  6. Declare public static Strings that clients can use to specify which columns to return, or to specify field values from the cursor. Carefully document the data type of each field. Remember that file fields, such as audio or bitmap fields, are typically returned as string path values
  7. Return a Cursor object over a recordset in reply to a query. This means implementing the query(), update(), insert(), and delete() methods. As a courtesy, you might want to call ContentResolver.notifyChange() to notify listeners about updated information.
  8. Add a <provider> tag to AndroidManifest.xml, and use its authorities attribute to define the authority part of the content type it should handle. For example, if your content type is content://com.example.autos/auto to request a list of all autos, then authorities would be com.example.autos. Set the multiprocess attribute to true if data does not need to be synchronized between multiple running versions of the content provider.
  9. If you are handling a new data type, you must define a new MIME type to return for your implementation of android.ContentProvider.getType(url). This type corresponds to the content:// URI submitted to getType(), which will be one of the content types handled by the provider. The MIME type for each content type has two forms: one for a specific record, and one for multiple records. Use the Uri methods to help determine what is being requested. Here is the general format for each:
    • vnd.android.cursor.item/vnd.yourcompanyname.contenttype for a single row. For example, a request for train record 122 using
      content://com.example.transportationprovider/trains/122
      might return the MIME type vnd.android.cursor.item/vnd.example.rail
    • vnd.android.cursor.dir/vnd.yourcompanyname.contenttype for multiple rows. For example, a request for all train records using
      content://com.example.transportationprovider/trains
      might return the MIME type vnd.android.cursor.dir/vnd.example.rail

For an example of a private content provider implementation, see the NodePadProvider class in the notepad sample application that ships with the SDK.

Here is a recap of the important parts of a content URI:

Elements of a content URI

  1. Standard required prefix. Never modified.
  2. Authority part. For third-party applications, this should be a fully-qualified class to ensure uniqueness. This corresponds to the value in the <provider> element's authorities attribute: <provider class="TransportationProvider" authorities="com.example.transportationprovider" />
  3. The path that the content provider uses to determine what kind of data is being requested. This can be zero or more segments: if the content provider exposes only one type of data (only trains, for example), this can be absent. If it provides several types, including subtypes, this can be several elements long: e.g., "land/bus, land/train, sea/ship, and sea/submarine" to give four possibilities.
  4. A specific record being requested, if any. This is the _id value of a specific record being requested. If all records of a specific type are being requested, omit this and the trailing slash: content://com.example.transportationprovider/trains