Content providers manage access to a structured set of data. They encapsulate the data, and provide mechanisms for defining data security. Content providers are the standard interface that connects data in one process with code running in another process.
When you want to access data in a content provider, you use theContentResolver object in your application's Context to communicate with the provider as a client. The ContentResolverobject communicates with the provider object, an instance of a class that implements ContentProvider. The provider object receives data requests from clients, performs the requested action, and returns the results.
You don't need to develop your own provider if you don't intend to share your data with other applications. However, you do need your own provider to provide custom search suggestions in your own application. You also need your own provider if you want to copy and paste complex data or files from your application to other applications.
Android itself includes content providers that manage data such as audio, video, images, and personal contact information. You can see some of them listed in the reference documentation for the android.providerpackage. With some restrictions, these providers are accessible to any Android application.
The following topics describe content providers in more detail:
-
Content Provider Basics
- How to access data in a content provider when the data is organized in tables. Creating a Content Provider
- How to create your own content provider. Calendar Provider
- How to access the Calendar Provider that is part of the Android platform. Contacts Provider
- How to access the Contacts Provider that is part of the Android platform.
Content Provider Basics
IN THIS DOCUMENT
- Overview
- Retrieving Data from the Provider
- Content Provider Permissions
- Inserting, Updating, and Deleting Data
- Provider Data Types
- Alternative Forms of Provider Access
- Contract Classes
- MIME Type Reference
KEY CLASSES
RELATED SAMPLES
SEE ALSO
A content provider manages access to a central repository of data. A provider is part of an Android application, which often provides its own UI for working with the data. However, content providers are primarily intended to be used by other applications, which access the provider using a provider client object. Together, providers and provider clients offer a consistent, standard interface to data that also handles inter-process communication and secure data access.
This topic describes the basics of the following:
- How content providers work.
- The API you use retrieve data from a content provider.
- The API you use to insert, update, or delete data in a content provider.
- Other API features that facilitate working with providers.
Overview
A content provider presents data to external applications as one or more tables that are similar to the tables found in a relational database. A row represents an instance of some type of data the provider collects, and each column in the row represents an individual piece of data collected for an instance.
For example, one of the built-in providers in the Android platform is the user dictionary, which stores the spellings of non-standard words that the user wants to keep. Table 1 illustrates what the data might look like in this provider's table:
Table 1: Sample user dictionary table.
| word | app id | frequency | locale | _ID |
|---|---|---|---|---|
| mapreduce | user1 | 100 | en_US | 1 |
| precompiler | user14 | 200 | fr_FR | 2 |
| applet | user2 | 225 | fr_CA | 3 |
| const | user1 | 255 | pt_BR | 4 |
| int | user5 | 100 | en_UK | 5 |
In table 1, each row represents an instance of a word that might not be found in a standard dictionary. Each column represents some data for that word, such as the locale in which it was first encountered. The column headers are column names that are stored in the provider. To refer to a row's locale, you refer to its localecolumn. For this provider, the _ID column serves as a "primary key" column that the provider automatically maintains.
Note: A provider isn't required to have a primary key, and it isn't required to use _ID as the column name of a primary key if one is present. However, if you want to bind data from a provider to a ListView, one of the column names has to be _ID. This requirement is explained in more detail in the section Displaying query results.
Accessing a provider
An application accesses the data from a content provider with a ContentResolver client object. This object has methods that call identically-named methods in the provider object, an instance of one of the concrete subclasses of ContentProvider. The ContentResolver methods provide the basic "CRUD" (create, retrieve, update, and delete) functions of persistent storage.
The ContentResolver object in the client application's process and the ContentProvider object in the application that owns the provider automatically handle inter-process communication. ContentProvider also acts as an abstraction layer between its repository of data and the external appearance of data as tables.
Note: To access a provider, your application usually has to request specific permissions in its manifest file. This is described in more detail in the section Content Provider Permissions
For example, to get a list of the words and their locales from the User Dictionary Provider, you callContentResolver.query(). The query() method calls the ContentProvider.query() method defined by the User Dictionary Provider. The following lines of code show a ContentResolver.query() call:
// Queries the user dictionary and returns results mCursor = getContentResolver().query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table mProjection, // The columns to return for each row mSelectionClause // Selection criteria mSelectionArgs, // Selection criteria mSortOrder); // The sort order for the returned rows
Table 2 shows how the arguments to query(Uri,projection,selection,selectionArgs,sortOrder) match an SQL SELECT statement:
Table 2: Query() compared to SQL query.
| query() argument | SELECT keyword/parameter | Notes |
|---|---|---|
Uri | FROM table_name | Uri maps to the table in the provider named table_name. |
projection | col,col,col,... | projection is an array of columns that should be included for each row retrieved. |
selection | WHERE col = value | selection specifies the criteria for selecting rows. |
selectionArgs | (No exact equivalent. Selection arguments replace? placeholders in the selection clause.) | |
sortOrder | ORDER BYcol,col,... | sortOrder specifies the order in which rows appear in the returnedCursor. |
Content URIs
A content URI is a URI that identifies data in a provider. Content URIs include the symbolic name of the entire provider (its authority) and a name that points to a table (a path). When you call a client method to access a table in a provider, the content URI for the table is one of the arguments.
In the preceding lines of code, the constant CONTENT_URI contains the content URI of the user dictionary's "words" table. The ContentResolver object parses out the URI's authority, and uses it to "resolve" the provider by comparing the authority to a system table of known providers. The ContentResolver can then dispatch the query arguments to the correct provider.
The ContentProvider uses the path part of the content URI to choose the table to access. A provider usually has a path for each table it exposes.
In the previous lines of code, the full URI for the "words" table is:
content://user_dictionary/words
where the user_dictionary string is the provider's authority, and words string is the table's path. The stringcontent:// (the scheme) is always present, and identifies this as a content URI.
Many providers allow you to access a single row in a table by appending an ID value to the end of the URI. For example, to retrieve a row whose _ID is 4 from user dictionary, you can use this content URI:
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
You often use id values when you've retrieved a set of rows and then want to update or delete one of them.
Note: The Uri and Uri.Builder classes contain convenience methods for constructing well-formed Uri objects from strings. The ContentUris contains convenience methods for appending id values to a URI. The previous snippet uses withAppendedId() to append an id to the UserDictionary content URI.
Retrieving Data from the Provider
This section describes how to retrieve data from a provider, using the User Dictionary Provider as an example.
For the sake of clarity, the code snippets in this section call ContentResolver.query() on the "UI thread"". In actual code, however, you should do queries asynchronously on a separate thread. One way to do this is to use the CursorLoader class, which is described in more detail in the Loaders guide. Also, the lines of code are snippets only; they don't show a complete application.
To retrieve data from a provider, follow these basic steps:
- Request the read access permission for the provider.
- Define the code that sends a query to the provider.
Requesting read access permission
To retrieve data from a provider, your application needs "read access permission" for the provider. You can't request this permission at run-time; instead, you have to specify that you need this permission in your manifest, using the <uses-permission> element and the exact permission name defined by the provider. When you specify this element in your manifest, you are in effect "requesting" this permission for your application. When users install your application, they implicitly grant this request.
To find the exact name of the read access permission for the provider you're using, as well as the names for other access permissions used by the provider, look in the provider's documentation.
The role of permissions in accessing providers is described in more detail in the section Content Provider Permissions.
The User Dictionary Provider defines the permission android.permission.READ_USER_DICTIONARY in its manifest file, so an application that wants to read from the provider must request this permission.
Constructing the query
The next step in retrieving data a provider is to construct a query. This first snippet defines some variables for accessing the User Dictionary Provider:
// A "projection" defines the columns that will be returned for each row String[] mProjection = { UserDictionary.Words._ID, // Contract class constant for the _ID column name UserDictionary.Words.WORD, // Contract class constant for the word column name UserDictionary.Words.LOCALE // Contract class constant for the locale column name }; // Defines a string to contain the selection clause String mSelectionClause = null; // Initializes an array to contain selection arguments String[] mSelectionArgs = {""};
The next snippet shows how to use ContentResolver.query(), using the User Dictionary Provider as an example. A provider client query is similar to an SQL query, and it contains a set of columns to return, a set of selection criteria, and a sort order.
The set of columns that the query should return is called a projection (the variable mProjection).
The expression that specifies the rows to retrieve is split into a selection clause and selection arguments. The selection clause is a combination of logical and Boolean expressions, column names, and values (the variablemSelectionClause). If you specify the replaceable parameter ? instead of a value, the query method retrieves the value from the selection arguments array (the variable mSelectionArgs).
In the next snippet, if the user doesn't enter a word, the selection clause is set to null, and the query returns all the words in the provider. If the user enters a word, the selection clause is set to UserDictionary.Words.WORD + " = ?" and the first element of selection arguments array is set to the word the user enters.
/* * This defines a one-element String array to contain the selection argument. */ String[] mSelectionArgs = {""}; // Gets a word from the UI mSearchString = mSearchWord.getText().toString(); // Remember to insert code here to check for invalid or malicious input. // If the word is the empty string, gets everything if (TextUtils.isEmpty(mSearchString)) { // Setting the selection clause to null will return all words mSelectionClause = null; mSelectionArgs[0] = ""; } else { // Constructs a selection clause that matches the word that the user entered. mSelectionClause = UserDictionary.Words.WORD + " = ?"; // Moves the user's input string to the selection arguments. mSelectionArgs[0] = mSearchString; } // Does a query against the table and returns a Cursor object mCursor = getContentResolver().query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table mProjection, // The columns to return for each row mSelectionClause // Either null, or the word the user entered mSelectionArgs, // Either empty, or the string the user entered mSortOrder); // The sort order for the returned rows // Some providers return null if an error occurs, others throw an exception if (null == mCursor) { /* * Insert code here to handle the error. Be sure not to use the cursor! You may want to * call android.util.Log.e() to log this error. * */ // If the Cursor is empty, the provider found no matches } else if (mCursor.getCount() < 1) { /* * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily * an error. You may want to offer the user the option to insert a new row, or re-type the * search term. */ } else { // Insert code here to do something with the results }
This query is analogous to the SQL statement:
SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
In this SQL statement, the actual column names are used instead of contract class constants.
Protecting against malicious input
If the data managed by the content provider is in an SQL database, including external untrusted data into raw SQL statements can lead to SQL injection.
Consider this selection clause:
// Constructs a selection clause by concatenating the user's input to the column name String mSelectionClause = "var = " + mUserInput;
If you do this, you're allowing the user to concatenate malicious SQL onto your SQL statement. For example, the user could enter "nothing; DROP TABLE *;" for mUserInput, which would result in the selection clause var = nothing; DROP TABLE *;. Since the selection clause is treated as an SQL statement, this might cause the provider to erase all of the tables in the underlying SQLite database (unless the provider is set up to catch SQL injection attempts).
To avoid this problem, use a selection clause that uses ? as a replaceable parameter and a separate array of selection arguments. When you do this, the user input is bound directly to the query rather than being interpreted as part of an SQL statement. Because it's not treated as SQL, the user input can't inject malicious SQL. Instead of using concatenation to include the user input, use this selection clause:
// Constructs a selection clause with a replaceable parameter String mSelectionClause = "var = ?";
Set up the array of selection arguments like this:
// Defines an array to contain the selection arguments String[] selectionArgs = {""};
Put a value in the selection arguments array like this:
// Sets the selection argument to the user's input selectionArgs[0] = mUserInput;
A selection clause that uses ? as a replaceable parameter and an array of selection arguments array are preferred way to specify a selection, even if the provider isn't based on an SQL database.
Displaying query results
The ContentResolver.query() client method always returns a Cursor containing the columns specified by the query's projection for the rows that match the query's selection criteria. A Cursor object provides random read access to the rows and columns it contains. Using Cursor methods, you can iterate over the rows in the results, determine the data type of each column, get the data out of a column, and examine other properties of the results. Some Cursor implementations automatically update the object when the provider's data changes, or trigger methods in an observer object when the Cursor changes, or both.
Note: A provider may restrict access to columns based on the nature of the object making the query. For example, the Contacts Provider restricts access for some columns to sync adapters, so it won't return them to an activity or service.
If no rows match the selection criteria, the provider returns a Cursor object for which Cursor.getCount() is 0 (an empty cursor).
If an internal error occurs, the results of the query depend on the particular provider. It may choose to returnnull, or it may throw an Exception.
Since a Cursor is a "list" of rows, a good way to display the contents of a Cursor is to link it to a ListView via aSimpleCursorAdapter.
The following snippet continues the code from the previous snippet. It creates a SimpleCursorAdapter object containing the Cursor retrieved by the query, and sets this object to be the adapter for a ListView:
// Defines a list of columns to retrieve from the Cursor and load into an output row String[] mWordListColumns = { UserDictionary.Words.WORD, // Contract class constant containing the word column name UserDictionary.Words.LOCALE // Contract class constant containing the locale column name }; // Defines a list of View IDs that will receive the Cursor columns for each row int[] mWordListItems = { R.id.dictWord, R.id.locale}; // Creates a new SimpleCursorAdapter mCursorAdapter = new SimpleCursorAdapter( getApplicationContext(), // The application's Context object R.layout.wordlistrow, // A layout in XML for one row in the ListView mCursor, // The result from the query mWordListColumns, // A string array of column names in the cursor mWordListItems, // An integer array of view IDs in the row layout 0); // Flags (usually none are needed) // Sets the adapter for the ListView mWordList.setAdapter(mCursorAdapter);
Note: To back a ListView with a Cursor, the cursor must contain a column named _ID. Because of this, the query shown previously retrieves the _ID column for the "words" table, even though the ListView doesn't display it. This restriction also explains why most providers have a _ID column for each of their tables.
Getting data from query results
Rather than simply displaying query results, you can use them for other tasks. For example, you can retrieve spellings from the user dictionary and then look them up in other providers. To do this, you iterate over the rows in the Cursor:
// Determine the column index of the column named "word" int index = mCursor.getColumnIndex(UserDictionary.Words.WORD); /* * Only executes if the cursor is valid. The User Dictionary Provider returns null if * an internal error occurs. Other providers may throw an Exception instead of returning null. */ if (mCursor != null) { /* * Moves to the next row in the cursor. Before the first movement in the cursor, the * "row pointer" is -1, and if you try to retrieve data at that position you will get an * exception. */ while (mCursor.moveToNext()) { // Gets the value from the column. newWord = mCursor.getString(index); // Insert code here to process the retrieved word. ... // end of while loop } } else { // Insert code here to report an error if the cursor is null or the provider threw an exception. }
Cursor implementations contain several "get" methods for retrieving different types of data from the object. For example, the previous snippet uses getString(). They also have a getType() method that returns a value indicating the data type of the column.
Content Provider Permissions
A provider's application can specify permissions that other applications must have in order to access the provider's data. These permissions ensure that the user knows what data an application will try to access. Based on the provider's requirements, other applications request the permissions they need in order to access the provider. End users see the requested permissions when they install the application.
If a provider's application doesn't specify any permissions, then other applications have no access to the provider's data. However, components in the provider's application always have full read and write access, regardless of the specified permissions.
As noted previously, the User Dictionary Provider requires the android.permission.READ_USER_DICTIONARYpermission to retrieve data from it. The provider has the separate android.permission.WRITE_USER_DICTIONARYpermission for inserting, updating, or deleting data.
To get the permissions needed to access a provider, an application requests them with a <uses-permission>element in its manifest file. When the Android Package Manager installs the application, a user must approve all of the permissions the application requests. If the user approves all of them, Package Manager continues the installation; if the user doesn't approve them, Package Manager aborts the installation.
The following <uses-permission> element requests read access to the User Dictionary Provider:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY">
The impact of permissions on provider access is explained in more detail in the Security and Permissions guide.
Inserting, Updating, and Deleting Data
In the same way that you retrieve data from a provider, you also use the interaction between a provider client and the provider's ContentProvider to modify data. You call a method of ContentResolver with arguments that are passed to the corresponding method of ContentProvider. The provider and provider client automatically handle security and inter-process communication.
Inserting data
To insert data into a provider, you call the ContentResolver.insert() method. This method inserts a new row into the provider and returns a content URI for that row. This snippet shows how to insert a new word into the User Dictionary Provider:
// Defines a new Uri object that receives the result of the insertion Uri mNewUri; ... // Defines an object to contain the new values to insert ContentValues mNewValues = new ContentValues(); /* * Sets the values of each column and inserts the word. The arguments to the "put" * method are "column name" and "value" */ mNewValues.put(UserDictionary.Words.APP_ID, "example.user"); mNewValues.put(UserDictionary.Words.LOCALE, "en_US"); mNewValues.put(UserDictionary.Words.WORD, "insert"); mNewValues.put(UserDictionary.Words.FREQUENCY, "100"); mNewUri = getContentResolver().insert( UserDictionary.Word.CONTENT_URI, // the user dictionary content URI mNewValues // the values to insert );
The data for the new row goes into a single ContentValues object, which is similar in form to a one-row cursor. The columns in this object don't need to have the same data type, and if you don't want to specify a value at all, you can set a column to null using ContentValues.putNull().
The snippet doesn't add the _ID column, because this column is maintained automatically. The provider assigns a unique value of _ID to every row that is added. Providers usually use this value as the table's primary key.
The content URI returned in newUri identifies the newly-added row, with the following format:
content://user_dictionary/words/<id_value>
The <id_value> is the contents of _ID for the new row. Most providers can detect this form of content URI automatically and then perform the requested operation on that particular row.
To get the value of _ID from the returned Uri, call ContentUris.parseId().
Updating data
To update a row, you use a ContentValues object with the updated values just as you do with an insertion, and selection criteria just as you do with a query. The client method you use is ContentResolver.update(). You only need to add values to the ContentValues object for columns you're updating. If you want to clear the contents of a column, set the value to null.
The following snippet changes all the rows whose locale has the language "en" to a have a locale of null. The return value is the number of rows that were updated:
// Defines an object to contain the updated values ContentValues mUpdateValues = new ContentValues(); // Defines selection criteria for the rows you want to update String mSelectionClause = UserDictionary.Words.LOCALE + "LIKE ?"; String[] mSelectionArgs = {"en_%"}; // Defines a variable to contain the number of updated rows int mRowsUpdated = 0; ... /* * Sets the updated value and updates the selected words. */ mUpdateValues.putNull(UserDictionary.Words.LOCALE); mRowsUpdated = getContentResolver().update( UserDictionary.Words.CONTENT_URI, // the user dictionary content URI mUpdateValues // the columns to update mSelectionClause // the column to select on mSelectionArgs // the value to compare to );
You should also sanitize user input when you call ContentResolver.update(). To learn more about this, read the section Protecting against malicious input.
Deleting data
Deleting rows is similar to retrieving row data: you specify selection criteria for the rows you want to delete and the client method returns the number of deleted rows. The following snippet deletes rows whose appid matches "user". The method returns the number of deleted rows.
// Defines selection criteria for the rows you want to delete String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?"; String[] mSelectionArgs = {"user"}; // Defines a variable to contain the number of rows deleted int mRowsDeleted = 0; ... // Deletes the words that match the selection criteria mRowsDeleted = getContentResolver().delete( UserDictionary.Words.CONTENT_URI, // the user dictionary content URI mSelectionClause // the column to select on mSelectionArgs // the value to compare to );
You should also sanitize user input when you call ContentResolver.delete(). To learn more about this, read the section Protecting against malicious input.
Provider Data Types
Content providers can offer many different data types. The User Dictionary Provider offers only text, but providers can also offer the following formats:
- integer
- long integer (long)
- floating point
- long floating point (double)
Another data type that providers often use is Binary Large OBject (BLOB) implemented as a 64KB byte array. You can see the available data types by looking at the Cursor class "get" methods.
The data type for each column in a provider is usually listed in its documentation. The data types for the User Dictionary Provider are listed in the reference documentation for its contract class UserDictionary.Words(contract classes are described in the section Contract Classes). You can also determine the data type by callingCursor.getType().
Providers also maintain MIME data type information for each content URI they define. You can use the MIME type information to find out if your application can handle data that the provider offers, or to choose a type of handling based on the MIME type. You usually need the MIME type when you are working with a provider that contains complex data structures or files. For example, the ContactsContract.Data table in the Contacts Provider uses MIME types to label the type of contact data stored in each row. To get the MIME type corresponding to a content URI, call ContentResolver.getType().
The section MIME Type Reference describes the syntax of both standard and custom MIME types.
Alternative Forms of Provider Access
Three alternative forms of provider access are important in application development:
- Batch access: You can create a batch of access calls with methods in the
ContentProviderOperationclass, and then apply them withContentResolver.applyBatch(). - Asynchronous queries: You should do queries in a separate thread. One way to do this is to use a
CursorLoaderobject. The examples in the Loaders guide demonstrate how to do this. - Data access via intents: Although you can't send an intent directly to a provider, you can send an intent to the provider's application, which is usually the best-equipped to modify the provider's data.
Batch access and modification via intents are described in the following sections.
Batch access
Batch access to a provider is useful for inserting a large number of rows, or for inserting rows in multiple tables in the same method call, or in general for performing a set of operations across process boundaries as a transaction (an atomic operation).
To access a provider in "batch mode", you create an array of ContentProviderOperation objects and then dispatch them to a content provider with ContentResolver.applyBatch(). You pass the content provider'sauthority to this method, rather than a particular content URI. This allows each ContentProviderOperationobject in the array to work against a different table. A call to ContentResolver.applyBatch() returns an array of results.
The description of the ContactsContract.RawContacts contract class includes a code snippet that demonstrates batch insertion. The Contact Manager sample application contains an example of batch access in itsContactAdder.java source file.
Displaying data using a helper app
If your application does have access permissions, you still may want to use an intent to display data in another application. For example, the Calendar application accepts an ACTION_VIEWintent, which displays a particular date or event. This allows you to display calendar information without having to create your own UI. To learn more about this feature, see the Calendar Provider guide.
The application to which you send the intent doesn't have to be the application associated with the provider. For example, you can retrieve a contact from the Contact Provider, then send anACTION_VIEW intent containing the content URI for the contact's image to an image viewer.
Data access via intents
Intents can provide indirect access to a content provider. You allow the user to access data in a provider even if your application doesn't have access permissions, either by getting a result intent back from an application that has permissions, or by activating an application that has permissions and letting the user do work in it.
Getting access with temporary permissions
You can access data in a content provider, even if you don't have the proper access permissions, by sending an intent to an application that does have the permissions and receiving back a result intent containing "URI" permissions. These are permissions for a specific content URI that last until the activity that receives them is finished. The application that has permanent permissions grants temporary permissions by setting a flag in the result intent:
- Read permission:
FLAG_GRANT_READ_URI_PERMISSION - Write permission:
FLAG_GRANT_WRITE_URI_PERMISSION
Note: These flags don't give general read or write access to the provider whose authority is contained in the content URI. The access is only for the URI itself.
A provider defines URI permissions for content URIs in its manifest, using the android:grantUriPermissionattribute of the <provider> element, as well as the <grant-uri-permission> child element of the <provider>element. The URI permissions mechanism is explained in more detail in the Security and Permissions guide, in the section "URI Permissions".
For example, you can retrieve data for a contact in the Contacts Provider, even if you don't have theREAD_CONTACTS permission. You might want to do this in an application that sends e-greetings to a contact on his or her birthday. Instead of requesting READ_CONTACTS, which gives you access to all of the user's contacts and all of their information, you prefer to let the user control which contacts are used by your application. To do this, you use the following process:
- Your application sends an intent containing the action
ACTION_PICKand the "contacts" MIME typeCONTENT_ITEM_TYPE, using the methodstartActivityForResult(). - Because this intent matches the intent filter for the People app's "selection" activity, the activity will come to the foreground.
- In the selection activity, the user selects a contact to update. When this happens, the selection activity calls
setResult(resultcode, intent)to set up a intent to give back to your application. The intent contains the content URI of the contact the user selected, and the "extras" flagsFLAG_GRANT_READ_URI_PERMISSION. These flags grant URI permission to your app to read data for the contact pointed to by the content URI. The selection activity then callsfinish()to return control to your application. - Your activity returns to the foreground, and the system calls your activity's
onActivityResult()method. This method receives the result intent created by the selection activity in the People app. - With the content URI from the result intent, you can read the contact's data from the Contacts Provider, even though you didn't request permanent read access permission to the provider in your manifest. You can then get the contact's birthday information or his or her email address and then send the e-greeting.
Using another application
A simple way to allow the user to modify data to which you don't have access permissions is to activate an application that has permissions and let the user do the work there.
For example, the Calendar application accepts an ACTION_INSERT intent, which allows you to activate the application's insert UI. You can pass "extras" data in this intent, which the application uses to pre-populate the UI. Because recurring events have a complex syntax, the preferred way of inserting events into the Calendar Provider is to activate the Calendar app with an ACTION_INSERT and then let the user insert the event there.
Contract Classes
A contract class defines constants that help applications work with the content URIs, column names, intent actions, and other features of a content provider. Contract classes are not included automatically with a provider; the provider's developer has to define them and then make them available to other developers. Many of the providers included with the Android platform have corresponding contract classes in the packageandroid.provider.
For example, the User Dictionary Provider has a contract class UserDictionary containing content URI and column name constants. The content URI for the "words" table is defined in the constantUserDictionary.Words.CONTENT_URI. The UserDictionary.Words class also contains column name constants, which are used in the example snippets in this guide. For example, a query projection can be defined as:
String[] mProjection = { UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.LOCALE };
Another contract class is ContactsContract for the Contacts Provider. The reference documentation for this class includes example code snippets. One of its subclasses, ContactsContract.Intents.Insert, is a contract class that contains constants for intents and intent data.
MIME Type Reference
Content providers can return standard MIME media types, or custom MIME type strings, or both.
MIME types have the format
type/subtype
For example, the well-known MIME type text/html has the text type and the html subtype. If the provider returns this type for a URI, it means that a query using that URI will return text containing HTML tags.
Custom MIME type strings, also called "vendor-specific" MIME types, have more complex type and subtypevalues. The type value is always
vnd.android.cursor.dir
for multiple rows, or
vnd.android.cursor.item
for a single row.
The subtype is provider-specific. The Android built-in providers usually have a simple subtype. For example, the when the Contacts application creates a row for a telephone number, it sets the following MIME type in the row:
vnd.android.cursor.item/phone_v2
Notice that the subtype value is simply phone_v2.
Other provider developers may create their own pattern of subtypes based on the provider's authority and table names. For example, consider a provider that contains train timetables. The provider's authority iscom.example.trains, and it contains the tables Line1, Line2, and Line3. In response to the content URI
content://com.example.trains/Line1
for table Line1, the provider returns the MIME type
vnd.android.cursor.dir/vnd.example.line1
In response to the content URI
content://com.example.trains/Line2/5
for row 5 in table Line2, the provider returns the MIME type
vnd.android.cursor.item/vnd.example.line2
Most content providers define contract class constants for the MIME types they use. The Contacts Provider contract class ContactsContract.RawContacts, for example, defines the constant CONTENT_ITEM_TYPE for the MIME type of a single raw contact row.
Content URIs for single rows are described in the section Content URIs.
Creating a Content Provider
A content provider manages access to a central repository of data. You implement a provider as one or more classes in an Android application, along with elements in the manifest file. One of your classes implements a subclass ContentProvider, which is the interface between your provider and other applications. Although content providers are meant to make data available to other applications, you may of course have activities in your application that allow the user to query and modify the data managed by your provider.
The rest of this topic is a basic list of steps for building a content provider and a list of APIs to use.
Before You Start Building
Before you start building a provider, do the following:
- Decide if you need a content provider. You need to build a content provider if you want to provide one or more of the following features:
- You want to offer complex data or files to other applications.
- You want to allow users to copy complex data from your app into other apps.
- You want to provide custom search suggestions using the search framework.
You don't need a provider to use an SQLite database if the use is entirely within your own application.
- If you haven't done so already, read the topic Content Provider Basics to learn more about providers.
Next, follow these steps to build your provider:
- Design the raw storage for your data. A content provider offers data in two ways:
-
File data
- Data that normally goes into files, such as photos, audio, or videos. Store the files in your application's private space. In response to a request for a file from another application, your provider can offer a handle to the file. "Structured" data
- Data that normally goes into a database, array, or similar structure. Store the data in a form that's compatible with tables of rows and columns. A row represents an entity, such as a person or an item in inventory. A column represents some data for the entity, such a person's name or an item's price. A common way to store this type of data is in an SQLite database, but you can use any type of persistent storage. To learn more about the storage types available in the Android system, see the section Designing Data Storage.
- Define a concrete implementation of the
ContentProviderclass and its required methods. This class is the interface between your data and the rest of the Android system. For more information about this class, see the section Implementing the ContentProvider Class. - Define the provider's authority string, its content URIs, and column names. If you want the provider's application to handle intents, also define intent actions, extras data, and flags. Also define the permissions that you will require for applications that want to access your data. You should consider defining all of these values as constants in a separate contract class; later, you can expose this class to other developers. For more information about content URIs, see the section Designing Content URIs. For more information about intents, see the section Intents and Data Access.
- Add other optional pieces, such as sample data or an implementation of
AbstractThreadedSyncAdapterthat can synchronize data between the provider and cloud-based data.
Designing Data Storage
A content provider is the interface to data saved in a structured format. Before you create the interface, you must decide how to store the data. You can store the data in any form you like, and then design the interface to read and write the data as necessary.
These are some of the data storage technologies that are available in Android:
- The Android system includes an SQLite database API that Android's own providers use to store table-oriented data. The
SQLiteOpenHelperclass helps you create databases, and theSQLiteDatabaseclass is the base class for accessing databases.Remember that you don't have to use a database to implement your repository. A provider appears externally as a set of tables, similar to a relational database, but this is not a requirement for the provider's internal implementation.
- For storing file data, Android has a variety of file-oriented APIs. To learn more about file storage, read the topic Data Storage. If you're designing a provider that offers media-related data such as music or videos, you can have a provider that combines table data and files.
- For working with network-based data, use classes in
java.netandandroid.net. You can also synchronize network-based data to a local data store such as a database, and then offer the data as tables or files. TheSample Sync Adapter sample application demonstrates this type of synchronization.
Data design considerations
Here are some tips for designing your provider's data structure:
- Table data should always have a "primary key" column that the provider maintains as a unique numeric value for each row. You can use this value to link the row to related rows in other tables (using it as a "foreign key"). Although you can use any name for this column, using
BaseColumns._IDis the best choice, because linking the results of a provider query to aListViewrequires one of the retrieved columns to have the name_ID. - If you want to provide bitmap images or other very large pieces of file-oriented data, store the data in a file and then provide it indirectly rather than storing it directly in a table. If you do this, you need to tell users of your provider that they need to use a
ContentResolverfile method to access the data. - Use the Binary Large OBject (BLOB) data type to store data that varies in size or has a varying structure. For example, you can use a BLOB column to store a protocol buffer or JSON structure.
You can also use a BLOB to implement a schema-independent table. In this type of table, you define a primary key column, a MIME type column, and one or more generic columns as BLOB. The meaning of the data in the BLOB columns is indicated by the value in the MIME type column. This allows you to store different row types in the same table. The Contacts Provider's "data" table
ContactsContract.Datais an example of a schema-independent table.
Designing Content URIs
A content URI is a URI that identifies data in a provider. Content URIs include the symbolic name of the entire provider (its authority) and a name that points to a table or file (a path). The optional id part points to an individual row in a table. Every data access method of ContentProvider has a content URI as an argument; this allows you to determine the table, row, or file to access.
The basics of content URIs are described in the topic Content Provider Basics.
Designing an authority
A provider usually has a single authority, which serves as its Android-internal name. To avoid conflicts with other providers, you should use Internet domain ownership (in reverse) as the basis of your provider authority. Because this recommendation is also true for Android package names, you can define your provider authority as an extension of the name of the package containing the provider. For example, if your Android package name iscom.example.<appname>, you should give your provider the authority com.example.<appname>.provider.
Designing a path structure
Developers usually create content URIs from the authority by appending paths that point to individual tables. For example, if you have two tables table1 and table2, you combine the authority from the previous example to yield the content URIs com.example.<appname>.provider/table1 and com.example.<appname>.provider/table2. Paths aren't limited to a single segment, and there doesn't have to be a table for each level of the path.
Handling content URI IDs
By convention, providers offer access to a single row in a table by accepting a content URI with an ID value for the row at the end of the URI. Also by convention, providers match the ID value to the table's _ID column, and perform the requested access against the row that matches.
This convention facilitates a common design pattern for apps accessing a provider. The app does a query against the provider and displays the resulting Cursor in a ListView using a CursorAdapter. The definition ofCursorAdapter requires one of the columns in the Cursor to be _ID
The user then picks one of the displayed rows from the UI in order to look at or modify the data. The app gets the corresponding row from the Cursor backing the ListView, gets the _ID value for this row, appends it to the content URI, and sends the access request to the provider. The provider can then do the query or modification against the exact row the user picked.
Content URI patterns
To help you choose which action to take for an incoming content URI, the provider API includes the convenience class UriMatcher, which maps content URI "patterns" to integer values. You can use the integer values in aswitch statement that chooses the desired action for the content URI or URIs that match a particular pattern.
A content URI pattern matches content URIs using wildcard characters:
*: Matches a string of any valid characters of any length.#: Matches a string of numeric characters of any length.
As an example of designing and coding content URI handling, consider a provider with the authoritycom.example.app.provider that recognizes the following content URIs pointing to tables:
content://com.example.app.provider/table1: A table calledtable1.content://com.example.app.provider/table2/dataset1: A table calleddataset1.content://com.example.app.provider/table2/dataset2: A table calleddataset2.content://com.example.app.provider/table3: A table calledtable3.
The provider also recognizes these content URIs if they have a row ID appended to them, as for examplecontent://com.example.app.provider/table3/1 for the row identified by 1 in table3.
The following content URI patterns would be possible:
- Matches any content URI in the provider.
-
Matches a content URI for the tables
dataset1anddataset2, but doesn't match content URIs fortable1ortable3.
content://com.example.app.provider/*
content://com.example.app.provider/table2/*:
content://com.example.app.provider/table3/#: Matches a content URI for single rows in
table3, such as
content://com.example.app.provider/table3/6 for the row identified by
6.
The following code snippet shows how the methods in UriMatcher work. This code handles URIs for an entire table differently from URIs for a single row, by using the content URI pattern content://<authority>/<path> for tables, and content://<authority>/<path>/<id> for single rows.
The method addURI() maps an authority and path to an integer value. The method match() returns the integer value for a URI. A switch statement chooses between querying the entire table, and querying for a single record:
public class ExampleProvider extends ContentProvider { ... // Creates a UriMatcher object. private static final UriMatcher sUriMatcher; ... /* * The calls to addURI() go here, for all of the content URI patterns that the provider * should recognize. For this snippet, only the calls for table 3 are shown. */ ... /* * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used * in the path */ sUriMatcher.addURI("com.example.app.provider", "table3", 1); /* * Sets the code for a single row to 2. In this case, the "#" wildcard is * used. "content://com.example.app.provider/table3/3" matches, but * "content://com.example.app.provider/table3 doesn't. */ sUriMatcher.addURI("com.example.app.provider", "table3/#", 2); ... // Implements ContentProvider.query() public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ... /* * Choose the table to query and a sort order based on the code returned for the incoming * URI. Here, too, only the statements for table 3 are shown. */ switch (sUriMatcher.match(uri)) { // If the incoming URI was for all of table3 case 1: if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; break; // If the incoming URI was for a single row case 2: /* * Because this URI was for a single row, the _ID value part is * present. Get the last path segment from the URI; this is the _ID value. * Then, append the value to the WHERE clause for the query */ selection = selection + "_ID = " uri.getLastPathSegment(); break; default: ... // If the URI is not recognized, you should do some error handling here. } // call the code to actually do the query }
Another class, ContentUris, provides convenience methods for working with the id part of content URIs. The classes Uri and Uri.Builder include convenience methods for parsing existing Uri objects and building new ones.
Implementing the ContentProvider Class
The ContentProvider instance manages access to a structured set of data by handling requests from other applications. All forms of access eventually call ContentResolver, which then calls a concrete method ofContentProvider to get access.
Required methods
The abstract class ContentProvider defines six abstract methods that you must implement as part of your own concrete subclass. All of these methods except onCreate() are called by a client application that is attempting to access your content provider:
-
Retrieve data from your provider. Use the arguments to select the table to query, the rows and columns to return, and the sort order of the result. Return the data as a
Cursorobject. - Insert a new row into your provider. Use the arguments to select the destination table and to get the column values to use. Return a content URI for the newly-inserted row.
- Update existing rows in your provider. Use the arguments to select the table and rows to update and to get the updated column values. Return the number of rows updated.
- Delete rows from your provider. Use the arguments to select the table and the rows to delete. Return the number of rows deleted.
- Return the MIME type corresponding to a content URI. This method is described in more detail in the section Implementing Content Provider MIME Types.
-
Initialize your provider. The Android system calls this method immediately after it creates your provider. Notice that your provider is not created until a
ContentResolverobject tries to access it.
query()
insert()
update()
delete()
getType()
onCreate()
Notice that these methods have the same signature as the identically-named ContentResolver methods.
Your implementation of these methods should account for the following:
- All of these methods except
onCreate()can be called by multiple threads at once, so they must be thread-safe. To learn more about multiple threads, see the topic Processes and Threads. - Avoid doing lengthy operations in
onCreate(). Defer initialization tasks until they are actually needed. The section Implementing the onCreate() method discusses this in more detail. - Although you must implement these methods, your code does not have to do anything except return the expected data type. For example, you may want to prevent other applications from inserting data into some tables. To do this, you can ignore the call to
insert()and return 0.
Implementing the query() method
The ContentProvider.query() method must return a Cursor object, or if it fails, throw an Exception. If you are using an SQLite database as your data storage, you can simply return the Cursor returned by one of the query()methods of the SQLiteDatabase class. If the query does not match any rows, you should return a Cursor instance whose getCount() method returns 0. You should return null only if an internal error occurred during the query process.
If you aren't using an SQLite database as your data storage, use one of the concrete subclasses of Cursor. For example, the MatrixCursor class implements a cursor in which each row is an array of Object. With this class, use addRow() to add a new row.
Remember that the Android system must be able to communicate the Exception across process boundaries. Android can do this for the following exceptions that may be useful in handling query errors:
IllegalArgumentException(You may choose to throw this if your provider receives an invalid content URI)NullPointerException
Implementing the insert() method
The insert() method adds a new row to the appropriate table, using the values in the ContentValues argument. If a column name is not in the ContentValues argument, you may want to provide a default value for it either in your provider code or in your database schema.
This method should return the content URI for the new row. To construct this, append the new row's _ID (or other primary key) value to the table's content URI, using withAppendedId().
Implementing the delete() method
The delete() method does not have to physically delete rows from your data storage. If you are using a sync adapter with your provider, you should consider marking a deleted row with a "delete" flag rather than removing the row entirely. The sync adapter can check for deleted rows and remove them from the server before deleting them from the provider.
Implementing the update() method
The update() method takes the same ContentValues argument used by insert(), and the same selection andselectionArgs arguments used by delete() and ContentProvider.query(). This may allow you to re-use code between these methods.
Implementing the onCreate() method
The Android system calls onCreate() when it starts up the provider. You should perform only fast-running initialization tasks in this method, and defer database creation and data loading until the provider actually receives a request for the data. If you do lengthy tasks in onCreate(), you will slow down your provider's startup. In turn, this will slow down the response from the provider to other applications.
For example, if you are using an SQLite database you can create a new SQLiteOpenHelper object inContentProvider.onCreate(), and then create the SQL tables the first time you open the database. To facilitate this, the first time you call getWritableDatabase(), it automatically calls the SQLiteOpenHelper.onCreate()method.
The following two snippets demonstrate the interaction between ContentProvider.onCreate() andSQLiteOpenHelper.onCreate(). The first snippet is the implementation of ContentProvider.onCreate():
public class ExampleProvider extends ContentProvider /* * Defines a handle to the database helper object. The MainDatabaseHelper class is defined * in a following snippet. */ private MainDatabaseHelper mOpenHelper; // Defines the database name private static final String DBNAME = "mydb"; // Holds the database object private SQLiteDatabase db; public boolean onCreate() { /* * Creates a new helper object. This method always returns quickly. * Notice that the database itself isn't created or opened * until SQLiteOpenHelper.getWritableDatabase is called */ mOpenHelper = new MainDatabaseHelper( getContext(), // the application context DBNAME, // the name of the database) null, // uses the default SQLite cursor 1 // the version number ); return true; } ... // Implements the provider's insert method public Cursor insert(Uri uri, ContentValues values) { // Insert code here to determine which table to open, handle error-checking, and so forth ... /* * Gets a writeable database. This will trigger its creation if it doesn't already exist. * */ db = mOpenHelper.getWritableDatabase(); } }
The next snippet is the implementation of SQLiteOpenHelper.onCreate(), including a helper class:
... // A string that defines the SQL statement for creating a table private static final String SQL_CREATE_MAIN = "CREATE TABLE " + "main " + // Table's name "(" + // The columns in the table " _ID INTEGER PRIMARY KEY, " + " WORD TEXT" " FREQUENCY INTEGER " + " LOCALE TEXT )"; ... /** * Helper class that actually creates and manages the provider's underlying data repository. */ protected static final class MainDatabaseHelper extends SQLiteOpenHelper { /* * Instantiates an open helper for the provider's SQLite data repository * Do not do database creation and upgrade here. */ MainDatabaseHelper(Context context) { super(context, DBNAME, null, 1); } /* * Creates the data repository. This is called when the provider attempts to open the * repository and SQLite reports that it doesn't exist. */ public void onCreate(SQLiteDatabase db) { // Creates the main table db.execSQL(SQL_CREATE_MAIN); } }
Implementing ContentProvider MIME Types
The ContentProvider class has two methods for returning MIME types:
- One of the required methods that you must implement for any provider.
- A method that you're expected to implement if your provider offers files.
getType()
getStreamTypes()
MIME types for tables
The getType() method returns a String in MIME format that describes the type of data returned by the content URI argument. The Uri argument can be a pattern rather than a specific URI; in this case, you should return the type of data associated with content URIs that match the pattern.
For common types of data such as as text, HTML, or JPEG, getType() should return the standard MIME type for that data. A full list of these standard types is available on the IANA MIME Media Types website.
For content URIs that point to a row or rows of table data, getType() should return a MIME type in Android's vendor-specific MIME format:
- Type part:
vnd - Subtype part:
- If the URI pattern is for a single row:
android.cursor.item/ - If the URI pattern is for more than one row:
android.cursor.dir/
- If the URI pattern is for a single row:
- Provider-specific part:
vnd.<name>.<type>You supply the
<name>and<type>. The<name>value should be globally unique, and the<type>value should be unique to the corresponding URI pattern. A good choice for<name>is your company's name or some part of your application's Android package name. A good choice for the<type>is a string that identifies the table associated with the URI.
For example, if a provider's authority is com.example.app.provider, and it exposes a table named table1, the MIME type for multiple rows in table1 is:
vnd.android.cursor.dir/vnd.com.example.provider.table1
For a single row of table1, the MIME type is:
vnd.android.cursor.item/vnd.com.example.provider.table1
MIME types for files
If your provider offers files, implement getStreamTypes(). The method returns a String array of MIME types for the files your provider can return for a given content URI. You should filter the MIME types you offer by the MIME type filter argument, so that you return only those MIME types that the client wants to handle.
For example, consider a provider that offers photo images as files in .jpg, .png, and .gif format. If an application calls ContentResolver.getStreamTypes() with the filter string image/* (something that is an "image"), then the ContentProvider.getStreamTypes() method should return the array:
{ "image/jpeg", "image/png", "image/gif"}
If the app is only interested in .jpg files, then it can call ContentResolver.getStreamTypes() with the filter string*\/jpeg, and ContentProvider.getStreamTypes() should return:
{"image/jpeg"}
If your provider doesn't offer any of the MIME types requested in the filter string, getStreamTypes() should returnnull.
Implementing a Contract Class
A contract class is a public final class that contains constant definitions for the URIs, column names, MIME types, and other meta-data that pertain to the provider. The class establishes a contract between the provider and other applications by ensuring that the provider can be correctly accessed even if there are changes to the actual values of URIs, column names, and so forth.
A contract class also helps developers because it usually has mnemonic names for its constants, so developers are less likely to use incorrect values for column names or URIs. Since it's a class, it can contain Javadoc documentation. Integrated development environments such as Eclipse can auto-complete constant names from the contract class and display Javadoc for the constants.
Developers can't access the contract class's class file from your application, but they can statically compile it into their application from a .jar file you provide.
The ContactsContract class and its nested classes are examples of contract classes.
Implementing Content Provider Permissions
Permissions and access for all aspects of the Android system are described in detail in the topic Security and Permissions. The topic Data Storage also described the security and permissions in effect for various types of storage. In brief, the important points are:
- By default, data files stored on the device's internal storage are private to your application and provider.
SQLiteDatabasedatabases you create are private to your application and provider.- By default, data files that you save to external storage are public and world-readable. You can't use a content provider to restrict access to files in external storage, because other applications can use other API calls to read and write them.
- The method calls for opening or creating files or SQLite databases on your device's internal storage can potentially give both read and write access to all other applications. If you use an internal file or database as your provider's repository, and you give it "world-readable" or "world-writeable" access, the permissions you set for your provider in its manifest won't protect your data. The default access for files and databases in internal storage is "private", and for your provider's repository you shouldn't change this.
If you want to use content provider permissions to control access to your data, then you should store your data in internal files, SQLite databases, or the "cloud" (for example, on a remote server), and you should keep files and databases private to your application.
Implementing permissions
All applications can read from or write to your provider, even if the underlying data is private, because by default your provider does not have permissions set. To change this, set permissions for your provider in your manifest file, using attributes or child elements of the <provider> element. You can set permissions that apply to the entire provider, or to certain tables, or even to certain records, or all three.
You define permissions for your provider with one or more <permission> elements in your manifest file. To make the permission unique to your provider, use Java-style scoping for the android:name attribute. For example, name the read permission com.example.app.provider.permission.READ_PROVIDER.
The following list describes the scope of provider permissions, starting with the permissions that apply to the entire provider and then becoming more fine-grained. More fine-grained permissions take precedence over ones with larger scope:
-
Single read-write provider-level permission
-
One permission that controls both read and write access to the entire provider, specified with the
android:permissionattribute of the<provider>element.
Separate read and write provider-level permission
-
A read permission and a write permission for the entire provider. You specify them with the
android:readPermissionandandroid:writePermissionattributes of the<provider>element. They take precedence over the permission required byandroid:permission.
Path-level permission
-
Read, write, or read/write permission for a content URI in your provider. You specify each URI you want to control with a
<path-permission>child element of the<provider>element. For each content URI you specify, you can specify a read/write permission, a read permission, or a write permission, or all three. The read and write permissions take precedence over the read/write permission. Also, path-level permission takes precedence over provider-level permissions.
Temporary permission
-
A permission level that grants temporary access to an application, even if the application doesn't have the permissions that are normally required. The temporary access feature reduces the number of permissions an application has to request in its manifest. When you turn on temporary permissions, the only applications that need "permanent" permissions for your provider are ones that continually access all your data.
Consider the permissions you need to implement an email provider and app, when you want to allow an outside image viewer application to display photo attachments from your provider. To give the image viewer the necessary access without requiring permissions, set up temporary permissions for content URIs for photos. Design your email app so that when the user wants to display a photo, the app sends an intent containing the photo's content URI and permission flags to the image viewer. The image viewer can then query your email provider to retrieve the photo, even though the viewer doesn't have the normal read permission for your provider.
To turn on temporary permissions, either set the
android:grantUriPermissionsattribute of the<provider>element, or add one or more<grant-uri-permission>child elements to your<provider>element. If you use temporary permissions, you have to callContext.revokeUriPermission()whenever you remove support for a content URI from your provider, and the content URI is associated with a temporary permission.The attribute's value determines how much of your provider is made accessible. If the attribute is set to
true, then the system will grant temporary permission to your entire provider, overriding any other permissions that are required by your provider-level or path-level permissions.If this flag is set to
false, then you must add<grant-uri-permission>child elements to your<provider>element. Each child element specifies the content URI or URIs for which temporary access is granted.To delegate temporary access to an application, an intent must contain the
FLAG_GRANT_READ_URI_PERMISSIONor theFLAG_GRANT_WRITE_URI_PERMISSIONflags, or both. These are set with thesetFlags()method.If the
android:grantUriPermissionsattribute is not present, it's assumed to befalse.
The <provider> Element
Like Activity and Service components, a subclass of ContentProvider must be defined in the manifest file for its application, using the <provider> element. The Android system gets the following information from the element:
-
Authority (
- Symbolic names that identify the entire provider within the system. This attribute is described in more detail in the section Designing Content URIs. Provider class name (
-
The class that implements
ContentProvider. This class is described in more detail in the section Implementing the ContentProvider Class.
Permissions
-
Attributes that specify the permissions that other applications must have in order to access the provider's data:
android:grantUriPermssions: Temporary permission flag.android:permission: Single provider-wide read/write permission.android:readPermission: Provider-wide read permission.android:writePermission: Provider-wide write permission.
Permissions and their corresponding attributes are described in more detail in the section Implementing Content Provider Permissions.
Startup and control attributes
-
These attributes determine how and when the Android system starts the provider, the process characteristics of the provider, and other run-time settings:
android:enabled: Flag allowing the system to start the provider.android:exported: Flag allowing other applications to use this provider.android:initOrder: The order in which this provider should be started, relative to other providers in the same process.android:multiProcess: Flag allowing the system to start the provider in the same process as the calling client.android:process: The name of the process in which the provider should run.android:syncable: Flag indicating that the provider's data is to be sync'ed with data on a server.
The attributes are fully documented in the dev guide topic for the
<provider>element.
Informational attributes
-
An optional icon and label for the provider:
android:icon: A drawable resource containing an icon for the provider. The icon appears next to the provider's label in the list of apps in Settings > Apps > All.android:label: An informational label describing the provider or its data, or both. The label appears in the list of apps in Settings > Apps > All.
The attributes are fully documented in the dev guide topic for the
<provider>element.
android:authorities)
android:name )
Intents and Data Access
Applications can access a content provider indirectly with an Intent. The application does not call any of the methods of ContentResolver or ContentProvider. Instead, it sends an intent that starts an activity, which is often part of the provider's own application. The destination activity is in charge of retrieving and displaying the data in its UI. Depending on the action in the intent, the destination activity may also prompt the user to make modifications to the provider's data. An intent may also contain "extras" data that the destination activity displays in the UI; the user then has the option of changing this data before using it to modify the data in the provider.
You may want to use intent access to help ensure data integrity. Your provider may depend on having data inserted, updated, and deleted according to strictly defined business logic. If this is the case, allowing other applications to directly modify your data may lead to invalid data. If you want developers to use intent access, be sure to document it thoroughly. Explain to them why intent access using your own application's UI is better than trying to modify the data with their code.
Handling an incoming intent that wishes to modify your provider's data is no different from handling other intents. You can learn more about using intents by reading the topic Intents and Intent Filters.
508

被折叠的 条评论
为什么被折叠?



