Contacts Provider is an Android built-in content provider. It manages contacts in the system. Android’s Contacts app also accesses contact information through the contacts provider. This article will introduce how to use the contacts provider to access the contact information in the system.
The complete code for this chapter can be found in and .
Table of Contents
Overview
Contacts Provider mainly manages three tables:
- ContactsContract.Contacts : Each row represents a summary of a person, based on multiple RowContacts rows.
- ContactsContract.RawContacts : Each row represents a contact, based on user account and type.
- ContactsContract.Data : Each row contains a type of data for a contact, such as an email address or phone number.
The hierarchical relationship of these three tables is as follows:
When we add a new contact, we actually create a new row in the RawContacts table. Then, the detailed information of this contact, such as last name, first name, email, phone number, each information will be created a new row in the Data
table. Therefore, RawContacts and Data tables are a one-to-many relationship.
In Android, we can add multiple accounts, for example, one account is waynestalk@gmail.com, and the other account is hello@gmail.com. Therefore the RawContacts table has the following two fields to indicate its source:
- ACCOUNT_NAME : account name, such as waynestalk@gmail.com.
- ACCOUNT_TYPE : account type, such as Google account is com.google.
So in fact, when we access the contact list, we mostly access the RawContacts table. What is the use of the Contacts table? Conceptually, each row in the Contacts table represents a person. However, the same person may have multiple contact information. When the user adds a contact data to the RawContacts table, the Contacts Provider will determine whether the new contact data can be associated with a person in the Contacts table. If so, the Contacts table will associate them. If not, it will create a new row in the Contacts table. Therefore, Contacts and RawContacts tables are a one-to-many relationship.
So what is the basis for ContactsProvider to determine whether the new added contact information can be associated with a person in the Contacts table? There are some rough introductions on the official website , but the rules are not clearly stated. In addition, this article has done some analysis, which may help you understand. So in fact, we can only read the Contacts table, but not add or modify it. In addition, when synchronizing the contacts of all accounts, this associated action will also occur.
Retrieving Contacts
First of all, we must first add READ_CONSTACTS permission in AndroidManifest.xml, as follows.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.READ_CONTACTS" /> </manifest>
Then, in the activity, request the READ_CONSTACTS permission.
ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.READ_CONTACTS), PERMISSION_REQUEST_CODE )
In the following code, we use ContentResolver.query() to get the contact list from RawContacts table. Its parameters are described as follows:
- uri: Specifies to access the RowContacts table.
- projection: Specifies to access the _ID and DISPLAY_NAME_PRIMARY columns in the RowContacts table.
- selection: Same as WHERE of SELECT.
- selectionArgs: Replaces the ? in the selection parameter.
- sortOrder: Same as ORDER BY of SELECT.
So, using query() is very similar to SQL. The difference is that in SQL, we specify the name of the table, but in query(), we specify the content URI.
You can find more columns in ContactsContract.RawContacts.
class RawContactListViewModel : ViewModel() { private val projection = arrayOf( ContactsContract.RawContacts._ID, ContactsContract.RawContacts.DISPLAY_NAME_PRIMARY, ) val rawContacts: MutableLiveData<List<RawAccount>> = MutableLiveData() fun loadRawContacts(contentResolver: ContentResolver, pattern: String? = null) { viewModelScope.launch(Dispatchers.IO) { val cursor = contentResolver.query( ContactsContract.RawContacts.CONTENT_URI, projection, pattern?.let { "${ContactsContract.RawContacts.DISPLAY_NAME_PRIMARY} LIKE ?" }, pattern?.let { arrayOf("%$it%") }, "${ContactsContract.RawContacts.ACCOUNT_NAME} ASC", ) ?: return@launch if (cursor.count == 0) { cursor.close() rawContacts.postValue(emptyList()) return@launch } val idIndex = cursor.getColumnIndex(ContactsContract.RawContacts._ID) val nameIndex = cursor.getColumnIndex(ContactsContract.RawContacts.DISPLAY_NAME_PRIMARY) val list = mutableListOf<RawAccount>() while (cursor.moveToNext()) { val id = cursor.getLong(idIndex) val name = cursor.getString(nameIndex) list.add(RawAccount(id, name)) } cursor.close() rawContacts.postValue(list) } } }
Retrieving Contact Details
All contact details are stored in ContactsContract.Data. Each raw only stores one type of data. You can know what kind of data this is by ContactsContract.DataColumns.MIMETYPE column. In the following code, we specify to retrieve all the email data of a RawContact in the selection parameter. We can get the mime type string of email from ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE.
You can find more data types in ContactsContract.CommonDataKinds .
class ContactViewModel : ViewModel() { var rawContactId: Long = 0 val name: MutableLiveData<String?> = MutableLiveData() val emailList: MutableLiveData<List<ContactEmail>> = MutableLiveData() val result: MutableLiveData<Result<String>> = MutableLiveData() private fun loadEmail(contentResolver: ContentResolver): List<ContactEmail> { val cursor = contentResolver.query( ContactsContract.Data.CONTENT_URI, arrayOf( ContactsContract.CommonDataKinds.Email._ID, ContactsContract.CommonDataKinds.Email.ADDRESS, ContactsContract.CommonDataKinds.Email.TYPE ), "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = '${ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE}'", arrayOf("$rawContactId"), "${ContactsContract.CommonDataKinds.Email.TYPE} ASC" ) ?: return emptyList() if (cursor.count == 0) { cursor.close() return emptyList() } val idIndex = cursor.getColumnIndex(ContactsContract.Data._ID) val emailAddressIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS) val emailTypeIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.TYPE) val list = mutableListOf<ContactEmail>() while (cursor.moveToNext()) { val id = cursor.getLong(idIndex) val emailAddress = cursor.getString(emailAddressIndex) val emailType = cursor.getInt(emailTypeIndex) list.add(ContactEmail(id, emailAddress, emailType)) } cursor.close() return list } }
Editing Contact Details
We can also modify the contact details. First, we need to add WRITE_CONTACTS permission in AndroidManifest.xml.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.WRITE_CONTACTS" /> </manifest>
Then, in the activity, request the WRITE_CONTACTS permission.
ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.WRITE_CONTACTS), PERMISSION_REQUEST_CODE )
Adding Contact Details
In the following code, we add an email data for a RawContact.
class ContactViewModel : ViewModel() { var rawContactId: Long = 0 val result: MutableLiveData<Result<String>> = MutableLiveData() fun addEmail(contentResolver: ContentResolver, email: String, type: Int) { viewModelScope.launch(Dispatchers.IO) { try { val values = ContentValues() values.put(Data.RAW_CONTACT_ID, rawContactId) values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE) values.put(Email.TYPE, type) values.put(Email.ADDRESS, email) val uri = contentResolver.insert(Data.CONTENT_URI, values) result.postValue( if (uri != null) Result.success(uri.toString()) else Result.failure(Exception("Error on inserting email")) ) } catch (e: Exception) { result.postValue(Result.failure(e)) } } } }
Modifying Contact Details
In the following code, we modify an email data for a RawContact.
class ContactViewModel : ViewModel() { var rawContactId: Long = 0 val result: MutableLiveData<Result<String>> = MutableLiveData() fun saveEmail(contentResolver: ContentResolver, email: String, type: Int) { viewModelScope.launch(Dispatchers.IO) { try { val contactEmail = emailList.value?.find { it.type == type } ?: return@launch val values = ContentValues() values.put(Email.ADDRESS, email) val count = contentResolver.update( Data.CONTENT_URI, values, "${Data._ID} = ?", arrayOf("${contactEmail.id}") ) result.postValue( if (count == 1) Result.success("Updated email") else Result.failure(Exception("Error on updating email")) ) } catch (e: Exception) { result.postValue(Result.failure(e)) } } } }
Deleting Contact Details
In the following code, we delete an email data from a RawContact.
class ContactViewModel : ViewModel() { var rawContactId: Long = 0 val result: MutableLiveData<Result<String>> = MutableLiveData() fun removeEmail(contentResolver: ContentResolver, type: Int) { viewModelScope.launch(Dispatchers.IO) { try { val email = emailList.value?.find { it.type == type } ?: return@launch val count = contentResolver.delete( Data.CONTENT_URI, "${Data._ID} = ?", arrayOf("${email.id}") ) result.postValue( if (count == 1) Result.success("Removed email") else Result.failure(Exception("Error on removing email")) ) } catch (e: Exception) { result.postValue(Result.failure(e)) } } } }
Using Intent to Add and Modify Contacts
So far, we have directly added or modified the Data table. However, we can see that there are actually quite a lot of data types in the Data table. So, if we want to fully support all data types, it’s not easy. Another recommended way is to add and modify Contacts data through the Android Contacts app. But then, we cannot customize the UI.
Adding Contacts
Create an Intent with ContactsContract.Intents.Insert.ACTION. Then, set the data we want to add in the intent.
You can find more columns in ContactsContract.Intents.Insert.
fun addContact(): Intent { return Intent(ContactsContract.Intents.Insert.ACTION).apply { type = ContactsContract.RawContacts.CONTENT_TYPE // Sets the special extended data for navigation putExtra("finishActivityOnSaveCompleted", true) // Insert an email address putExtra(ContactsContract.Intents.Insert.EMAIL, "waynestalk@gmail.com") putExtra( ContactsContract.Intents.Insert.EMAIL_TYPE, ContactsContract.CommonDataKinds.Email.TYPE_WORK ) // Insert a phone number putExtra(ContactsContract.Intents.Insert.PHONE, "123456789") putExtra( ContactsContract.Intents.Insert.PHONE_TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_WORK ) } } val intent = addContact() startActivity(intent)
Modifying Contacts
When modifying, what is different is the content URI. We need to call ContactsContract.Contacts.getLookupUri() and pass in ContactsContract.Contacts._ID and ContactsContract.ContactsColumns.LOOKUP_KEY to get the URI for the specified Contact. These two parameter can be obtained in the Contacts table instead of the RawContacts table.
fun editContact(account: Account): Intent { return Intent(Intent.ACTION_EDIT).apply { val contactUri = ContactsContract.Contacts.getLookupUri(account.id, account.lookupKey) setDataAndType(contactUri, ContactsContract.Contacts.CONTENT_ITEM_TYPE) // Sets the special extended data for navigation putExtra("finishActivityOnSaveCompleted", true) // Insert an email address putExtra(ContactsContract.Intents.Insert.EMAIL, "waynestalk@gmail.com") putExtra( ContactsContract.Intents.Insert.EMAIL_TYPE, ContactsContract.CommonDataKinds.Email.TYPE_WORK ) // Insert a phone number putExtra(ContactsContract.Intents.Insert.PHONE, "123456789") putExtra( ContactsContract.Intents.Insert.PHONE_TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_WORK ) } } val intent = editContact(account) startActivity(intent)
Conclusion
Adding and modifying contact data is not so easy, it is recommended to use Intent to add and modify. Fortunately, in most cases, we just need to get the contact details. This article only briefly introduces how to retrieve a contact’s email. It also has many different types of data, which are left for readers to discover them.
References
- Contacts Provider, Google Developers.
- Ali Chousein, Android contacts: CONTACT_ID vs RAW_CONTACT_ID.