Applications can access data within other applications on the Android system through content provider interfaces and can 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.
Warning
Always run content provider code on test devices, not on your personal devices. It is very easy to accidentally wipe out all of the Contacts database 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.
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 17.1 lists some useful content providers in this package.
Now let’s look at some of the most popular and official content providers in more detail.
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 older Activity
class method managedQuery()
for performing cursor queries that block the UI thread and this method 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
.
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 under android.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, data columns that can be requested.
Table 17.2 lists some commonly used classes you can find under android.provider.MediaStore
.
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 (http://introductiontoandroid.blogspot.com).
The following code demonstrates how to request data from a content provider. A query is made to the MediaStore
to retrieve both 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 track only the 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 the last three parameters 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.
Your application needs a special permission to access the information provided by the MediaStore
content provider. You can declare the <uses-permission>
tag by adding the following to your AndroidManifest.xml
file:
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"/>
To further support the new app permissions model introduced in Android Marshmallow API Level 23 and newer, you can check if the permission has been accepted by the user of your application code, and if not, request the appropriate permission. Here is a simple implementation:
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(Activity.this,
PERMISSIONS_EXTERNAL_STORAGE, REQUEST_EXTERNAL_STORAGE);
} else {
// permission already accepted, continue as usual
}
The preceding code checks if the permission has been granted, and if not, requests the permission by displaying a request dialog asking for the user to accept the permission. You can learn more about permissions and the new permission model in Chapter 5, “Defining the Manifest,” or you can see the full implementation in the SimpleContentProvider
application that accompanies this chapter, found on the books website.
Although it’s a tad confusing, there is no MediaStore
provider permission. Instead, applications that access the MediaStore
use the READ_EXTERNAL_STORAGE
permission.
Tip
You can find all available permissions in the class android.Manifest.permission
.
Android has a content provider for accessing 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 by using 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.
Your application needs a special permission to access the information provided by the CallLog
content provider. You can add the appropriate permissions by adding the following to your AndroidManifest.xml
file:
<uses-permission
android:name="android.permission.READ_CALL_LOG" />
To support the new apps permission model of Android Marshmallow 6.0 API Level 23 and newer, you need to ensure the READ_CALL_LOG
permission has been accepted, or request it if the permission has not yet been accepted. Here is a code snippet showing how to perform the check:
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MenuActivity.this,
PERMISSIONS_CALL_LOG, REQUEST_CALL_LOG);
} else {
// permission already accepted, continue as usual
}
Although the values are cached within the CallLog
content provider, the data is similar to what you might find in the ContactsContract p
rovider.
Introduced officially in Android 4.0 (API Level 14), the CalendarContract
content 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 access and manipulate other calendar data, 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
, as shown here:
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, see http://d.android.com/guide/topics/providers/calendar-provider.html and http://d.android.com/reference/android/provider/CalendarContract.html.
Another useful content provider is the UserDictionary
provider. You can use this content provider to predict a user’s 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.
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.
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 the android.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.
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.
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.
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.
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.
Tip
The ContactsContract
content provider was further enhanced in more recent versions of Android to incorporate substantive social networking features. Some of the new features include managing the device user’s identity and favorite methods of communication with specific contacts, as well as an INVITE_CONTACT Intent
type for applications used 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.ContactsContract
class.
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));
}
First, we can see here 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 17.3 shows some of the commonly used classes that can help you work with the ContactsContract
content provider.
New additions have been made to the ContactsContract
content provider in Android 5.0 (API Level 21), providing insight to contact snippets that match particular searches. You now have the ability to determine the search filters a contact has matched using ContactsContract.SearchSnippets
. This is an extremely useful feature because, prior to this addition, there was no standard way for an application to determine which contacts had matched a particular search filter.
For more information on the ContactsContract
provider, see the Android SDK documentation: http://d.android.com/reference/android/provider/ContactsContract.html.
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.
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
.
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.
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 " +
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 that 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.
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, however: you should use these examples only on a test device, so you don’t accidentally delete all of your contact data from your device.
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).
Often, you want to select specific rows to delete by adding selection filters that will 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 this 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);
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 unofficial 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.
Your application can leverage the data available within other Android applications, if they expose that data as content providers. Content providers such as MediaStore
, 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.
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. What is the method call for adding words to the custom user dictionary of the UserDictionary
provider?
5. True or false: The Contacts
content provider was added in API Level 5.
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.
Android API Guides: “Content Providers”:
http://d.android.com/guide/topics/providers/content-providers.html
Android API Guides: “Content Provider Testing”:
http://d.android.com/tools/testing/contentprovider_testing.html
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 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
18.191.78.136