Contacts Provider
Contacts Provider Organization
Contacts --> people, Raw --> a summary of people, Data --> the detail of raw contacts
other auxiliary table
Raw contacts
- m x raw <--> a single acount type <--> multi-sync-service
Notes
- Raw conteacts name in Data
- Cauion: To use your own account data in a raw contact row.
register in Account Manager
sample_01 maintain contacts data for your web-based servic
service url :com.example.dataservice
usr's account:becky.sharp@dataservice.example.com
the user must first add the account "type" (com.example.dataservice
) and account "name" (becky.smart@dataservice.example.com
) before your app can add raw contact rows.
Sources of raw contacts data
Suppose Emily Dickinson opens a browser window, logs into Gmail as emily.dickinson@gmail.com, opens Contacts, and adds "Thomas Higginson". Later on, she logs into Gmail as emilyd@gmail.com and sends an email to "Thomas Higginson", which automatically adds him as a contact. She also follows "colonel_tom" (Thomas Higginson's Twitter ID) on Twitter. ==> The Contacts Provider creates three raw contacts as a result of this work:
Data
the data for a raw contact is stored in a ContactsContract.Data row that is linked to the raw contact's _ID value.
- Column names
raw_contact_id
MIMETYPE -->> define ContactsContract.CommonDataKinds
IS_PRIMARY if the user long-presses a phone number for a contact and selects Set default --> !zero - Generic column names 1~15
DATA1 --> index
DATE15 --> Binary Larg Object (BLOB) such as photo thumbnails
Type-specific column names
Caution: add your own custom data MIMETYPE in data table must ContactsContract.CommonDataKinds ++
Contacts
The CONTACT_ID
column of theraw contacts table ContactsContract.RawContacts contains _ID
values for the contacts row associated with each raw contacts row.
- The column LOOKUP_KEY that is a "permanent" link to the contact row
- _ID column may change
Data From Sync Adapters
Users enter contacts data directly into the device, but data also flows into the Contacts Provider from web services via sync adapters, which automate the transfer of data between the device and services. Sync adapters run in the background under the control of the system, and they call ContentResolver methods to manage data.
The User Profile
This data describes the device's user. access to the user profile requires the READ_PROFILE and WRITE_PROFILE permissions.
// Sets the columns to retrieve for the user profile
mProjection = new String[]{
Profile._ID,
Profile.DISPLAY_NAME_PRIMARY,
Profile.LOOKUP_KEY,
Profile.PHOTO_THUMBNAIL_URI
};
// Retrieves the profile from the Contacts Provider
mProfileCursor =getContentResolver().query(
Profile.CONTENT_URI,
mProjection ,
null,
null,
null);
Contacts Provider Metadata
Column | value | Meaning |
---|---|---|
RawContacts/DIRTY | 0/1(need sync to service) | |
RawContacts/VERSION | its related data changes ++ | |
Data/DATA_VERSION | its related data changes ++ | |
RawContacts/SOURCE_ID | ==service database reflect row id | |
Groups | 0/1 | visible or invsible in application UIs |
Settings/UNGROUPED_VISIBLE | 0/1 | don't belong to a group v/invsible |
SyncState | (all) | metadata |
Contacts Provider Access
Querying entities
For example, to display all the information for a person, you may want to retrieve all the ContactsContract.RawContacts rows for a single ContactsContract.Contacts row, or all the ContactsContract.CommonDataKinds.Email rows for a single ContactsContract.RawContacts row. To facilitate this, the Contacts Provider offers entity
constructs, which actlike database joins between tables.
Note: An entity usually doesn't contain all the columns of the parent and child table. If you attempt to work with a column name that isn't in the list of column name constants for the entity, you'll get an Exception.
snippet
...
/*
* Appends the entity path to the URI. In the case of the Contacts Provider, the
* expected URI is content://com.google.contacts/#/entity (# is the ID value).
*/
mContactUri = Uri.withAppendedPath(
mContactUri,
ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
// Initializes the loader identified by LOADER_ID.
getLoaderManager().initLoader(
LOADER_ID, // The identifier of the loader to initialize
null, // Arguments for the loader (in this case, none)
this); // The context of the activity
// Creates a new cursor adapter to attach to the list view
mCursorAdapter = new SimpleCursorAdapter(
this, // the context of the activity
R.layout.detail_list_item,
mCursor, // the backing cursor
mFromColumns, // the columns in the cursor that provide the data
mToViews, // the views in the view item that display the data
0); // flags
// Sets the ListView's backing adapter.
mRawContactList.setAdapter(mCursorAdapter);
...
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
/*
* Sets the columns to retrieve. RAW_CONTACT_ID is included
* to identify the raw contact associated with the data row.
* DATA1 contains the first column in the data row (usually the most important one).
* MIMETYPE indicates the type of data in the data row.
*/
String[] projection ={
ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
ContactsContract.Contacts.Entity.DATA1,
ContactsContract.Contacts.Entity.MIMETYPE
};
/*
* Sorts the retrieved cursor by raw contact id, to keep all data
* rows for a single raw contact collated together.
*/
String sortOrder =
ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
" ASC";
/*
* Returns a new CursorLoader. The arguments are similar to
* ContentResolver.query(), except for the Context argument, which
* supplies the location of the ContentResolver to use.
*/
return new CursorLoader(
getApplicationContext(), // The activity's context
mContactUri, // The entity content URI for a single contact
projection, // The columns to retrieve
null, // Retrieve all the raw contacts and their data rows.
null, //
sortOrder); // Sort by the raw contact ID.
}
Batch modification
Note: To modify a single raw contact, consider sending an intent to the device's contacts application rather than handling the modification in your app.
Yield points
A batch modification containing a large number of operations can block other processes, resulting in a bad overall user experience. A yield point is a ContentProviderOperation object that has its isYieldAllowed() value set to true. When the Contacts Provider encounters a yield point, it pauses its work to let other processes run and closes the current transaction. When the provider starts again, it continues with the next operation in the ArrayList and starts a new transaction.
Modification back references
insert a new raw contact row must insert contact's _ID to data RAW_CONTACT_ID ==>>
ContentProviderOperation.Builder.withValueBackReference().
key ==> a column in the table that you're modifying.
previousResult ==> The 0-based index of a value in the array of ContentProviderResult objects from applyBatch().
snippet-02
// Inserts the specified email and type as a Phone data row
op =ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
/*
* Sets the value of the raw contact id column to the new raw contact ID returned
* by the first operation in the batch.
*/
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
// Sets the data row's MIME type to Email
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
// Sets the email address and type
.withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
.withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);
/*
* Demonstrates a yield point. At the end of this insert, the batch operation's thread
* will yield priority to other threads. Use after every set of operations that affect a
* single contact, to avoid degrading performance.
*/
op.withYieldAllowed(true);
// Builds the operation and adds it to the array of operations
ops.add(op.build());
try {
getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
} catch (Exception e) {
// Display a warning
Context ctx = getApplicationContext();
CharSequence txt = getString(R.string.contactCreationFailure);
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(ctx, txt, duration);
toast.show();
// Log exception
Log.e(TAG, "Exception encountered while inserting contact: " + e);
}
}
optimistic concurrency control ??
- Retrieve the raw contact's VERSION column along with the other data you retrieve.
- Create a ContentProviderOperation.Builder object suitable for enforcing a constraint, using the method newAssertQuery(Uri). For the content URI, use RawContacts.CONTENT_URI with the raw contact's _ID appended to it.
- For the ContentProviderOperation.Builder object, call withValue() to compare the VERSION column to the version number you just retrieved.
- For the same ContentProviderOperation.Builder, call withExpectedCount() to ensure that only one row is tested by this assertion.
- Call build() to create the ContentProviderOperation object, then add this object as the first object in the ArrayList that you pass to applyBatch().
- Apply the batch transaction.
snippet-03 -- Create an "assert" ContentProviderOperation after querying for a single raw contact using a CursorLoader:
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// Gets the raw contact's _ID and VERSION values
mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
}
...
// Sets up a Uri for the assert operation
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID);
// Creates a builder for the assert operation
ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri);
// Adds the assertions to the assert operation: checks the version and count of rows tested
assertOp.withValue(SyncColumns.VERSION, mVersion);
assertOp.withExpectedCount(1);
// Creates an ArrayList to hold the ContentProviderOperation objects
ArrayList ops = new ArrayList<ContentProviderOperationg>;
ops.add(assertOp.build());
// You would add the rest of your batch operations to "ops" here
// Applies the batch. If the assert fails, an Exception is thrown
try {
ContentProviderResult[] results =getContentResolver().applyBatch(AUTHORITY, ops);
} catch (OperationApplicationException e) {
// Actions you want to take if the assert operation fails go here
}
Retrieval and modification with intents
Contacts Provider intents
Action | Data | MIME type | Notes |
---|---|---|---|
ACTION_PICK | Contacts.CONTENT_URI Phone.CONTENT_URI StructuredPostal.CONTENT_URI,Email.CONTENT_URI | ||
Insert.ACTION | |||
ACTION_EDIT | |||
ACTION_INSERT_OR_EDIT |
The device's contacts app doesn't allow you to delete a raw contact or any of its data with an intent. Instead, to delete a raw contact, use ContentResolver.delete() or ContentProviderOperation.newDelete().
snippet-04
// Creates a new intent for sending to the device's contacts application
Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);
// Sets the MIME type to the one expected by the insertion activity
insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
// Sets the new contact name
insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);
// Sets the new company and job title
insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);
/*
* Demonstrates adding data rows as an array list associated with the DATA key
*/
// Defines an array list to contain the ContentValues objects for each row
ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();
/*
* Defines the raw contact row
*/
// Sets up the row as a ContentValues object
ContentValues rawContactRow = new ContentValues();
// Adds the account type and name to the row
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType());
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
// Adds the row to the array
contactData.add(rawContactRow);
/*
* Sets up the phone number data row
*/
// Sets up the row as a ContentValues object
ContentValues phoneRow = new ContentValues();
// Specifies the MIME type for this data row (all data rows must be marked by their type)
phoneRow.put(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
);
// Adds the phone number and its type to the row
phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
// Adds the row to the array
contactData.add(phoneRow);
/*
* Sets up the email data row
*/
// Sets up the row as a ContentValues object
ContentValues emailRow = new ContentValues();
// Specifies the MIME type for this data row (all data rows must be marked by their type)
emailRow.put(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
);
// Adds the email address and its type to the row
emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);
// Adds the row to the array
contactData.add(emailRow);
/*
* Adds the array to the intent's extras. It must be a parcelable object in order to
* travel between processes. The device's contacts app expects its key to be
* Intents.Insert.DATA
*/
insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);
// Send out the intent to start the device's contacts app in its add contact activity.
startActivity(insertIntent);
Data integrity
- Always add a ContactsContract.CommonDataKinds.StructuredName row for every ContactsContract.RawContacts row you add.
A ContactsContract.RawContacts row without a ContactsContract.CommonDataKinds.StructuredName row in the ContactsContract.Data table may cause problems during aggregation.
- Always link new ContactsContract.Data rows to their parent ContactsContract.RawContacts row.
A ContactsContract.Data row that isn't linked to a ContactsContract.RawContacts won't be visible in the device's contacts application, and it might cause problems with sync adapters.
- Change data only for those raw contacts that you own.
Custom data rows
By creating and using your own custom MIME types, you can insert, edit, delete, and retrieve your own data rows in the ContactsContract.Data table. Your rows are limited to using the column defined in ContactsContract.DataColumns, although you can map your own type-specific column names to the default column names. In the device's contacts application, the data for your rows is displayed but can't be edited or deleted, and users can't add additional data. To allow users to modify your custom data rows, you must provide an editor activity in your own application.
To display your custom data, provide a contacts.xml file containing a <ContactsAccountType> element and one or more of its <ContactsDataKind> child elements. This is described in more detail in the section <ContactsDataKind> element.
Contacts Provider Sync Adapters
The Contacts Provider is specifically designed for handling synchronization of contacts data between a device and an online service.
the Android system provides a plug-in synchronization framework that automates the following tasks:
- Checking network availability.
- Scheduling and executing synchronization, based on user preferences.
- Restarting synchronizations that have stopped.
Sync adapter classes and files
implement AbstractThreadSyncAdapter --> system read manifest Xml file --> user add an acount for the sync
--> system starts managing the adapter
.
Note:
Using an account type as part of the sync adapter's identification
allows the system to detect and group together sync adapters that access different services from the same organization. For example, sync adapters for Google online services all have the same account type com.google. When users add a Google account to their devices, all of the installed sync adapters for Google services are listed together; each sync adapter listed syncs with a different content provider on the device.
- the Android system offers an authentication framework
AbstractAccountAuthenticator
If the service accepts the credentials, the authenticator can store the credentials for later use. Because of the plug-in authenticator framework,the AccountManager can provide access to any authtokens an authenticator supports and chooses to expose
, such as OAuth2 authtokens.
Social Stream Data
ContactsContract.StreamItems and ContactsContract.StreamItemPhotos tables
manage incoming data from social networks
- Social stream text
- Social stream photos
Social stream interactions
- Regular synchronization
- Trigger synch youself
- Trigger when need by registering a notification
Registering to handle social networking views
res/xml/contacts.xml
<ContactsAccountType
xmlns:android="http://schemas.android.com/apk/res/android"
inviteContactActivity="activity_name"
inviteContactActionLabel="invite_command_text"
viewContactNotifyService="view_notify_service"
viewGroupActivity="group_view_activity"
viewGroupActionLabel="group_action_text"
viewStreamItemActivity="viewstream_activity_name"
viewStreamItemPhotoActivity="viewphotostream_activity_name">
Additional Contacts Provider Features
- Contact groups
- Contact photos