Tuesday, August 18, 2015

Working with android contacts

Detecting changes in Android contacts

Intro

In next few lines we will talk about detecting changes in Android contacts. Android Os provides a storage for contacts called Contacts Provider. Contact Provider allows you to manage an access to a structured set of data and standard interface that connects data in one process with code running from another process.

Structure

Android Contacts structure consists of many tables, here are few of them.

ContactsRaw table: (listing important rows)
  • _id - RAW_CONTACT_ID
  • account_name - e.g. email address
  • account_type - account type e.g. com.google.com
  • deleted

Table ContactsContract.RawContacts.CONTENT_URI contains original contacts. This table mainly stores raw_id. The contact details are stored in Contact Contract table.

Contacts table: (listing important rows)

  • _id - CONTACT_ID (this id refers one group of raw_contact_id )
  • lookup - link to all contact details and contact in this table

Table Contacts.CONTENT_URI consists of grouped RawContacts based on merging rules. Some of raw contacts can be automatically grouped together (more about that later). CONTACT_ID refers to the ID of group. You cannot add or edit the records directly, it is read-only table. Content Provider creates new contact if there has been created a new raw contact and cannot be grouped to any other existing contact. Content Provider also creates new contacts if raw contact is changed so much so it cannot be assigned to the actual contact. If application or syncAdapter creates a new raw contact which can be assigned to existing contact, new contact is not created.
This table also contains raw LOOKUP_KEY which is a permanent link to only one contact, even if the contact_id is changed (because of some merging) LOOKUP_KEY is still the same.

ContactContract table: (listing important rows)

  • _id - CONTACT_DETAIL_ID
  • lookup -
  • raw_contact_id - RAW_CONTACT_ID (assign to a Contacts Raw Table)
  • contact_id - CONTACT_ID (assign to a contact table)
  • version - if any record of contact has been changed, all versions are updated (all has same value)
  • data1 - data16 - data content
  • mime_type - type of record (email, number, address)
  • is_primary - if there is more instances of contact (one of them is primary, e.g. there is only one primary phone number)

Table ContactsContract.Data.CONTENT_URI contains contact detials. Each contact can have one or more records here. Example: if contact has got name, phone and email, this table will have 3 records. Contact_id is reference to Contact table. Contact_raw_id is refference to the original contact.

AggregationException table: (listing important rows)

  • RAW_CONTACT_ID1
  • RAW_CONTACT_ID2
  • AggregationExceptions.TYPE

AggregationException table contains pairs or raw contacts that should not be groupped together. Last row contains one of the following:

  • TYPE_AUTOMATIC (merging is automatic, content provider drives it)
  • TYPE_KEEP_SEPARATE (contacts are not merged, even content provider can find the match)
  • TYPE_KEEP_TOGETHER (contacts are merged and will be merged, until user wants/ this row is deleted)


Automatic merging

These are the rules for merging one or more raw contacts into the one contact:
  • name and surname is equal
  • name and surname is equal, but in different order
  • surname is the same, short first name the same
  • name or surname, and one phone/email
  • one of the contacts has no name/surname but it has same phone/email


Detecting changes

There are a few ways how to detect that the contact has been changed.

To detect that the contact has been changed you can use ContentObserver. This is an abstract method that contains two methods ( onChange(boolean selfChange), onChange(boolean selfChange, Uri uri)). The first method just triggers that something has changed. Second method returns also Uri of changed contact but it is available since API 16.

Or there is a column with a version number in the table with contact details(ContactsContract). So if you have a local db with stored versions for contacts, you can just compare the version numbers. If one contact has changed, all his versions in rows with contact details are changed.

The other way is to keep a timestamp of the last change. Then if we want to know which contact has been changed, we just use timestamp in a query. This last_change row is available since API 18.


Detecting new contacts

The best solution is, I guess, to save the last RAW_ID and every time you want to know if there is a new contact you should ask if there is contact with higher RAW_ID. There are situations for example: when changes of contact are so big, so the new raw contact id is created. In this case you have to also check the LOOKUP key. Lookup key is never changed (his first part before dash).

Detecting that contact was removed

Just query for specific contact by LOOKUP_KEY if it is still in database. This can be a little overhead because you have to do it for all the contacts you are interested in.

SyncAdapter and server synchronization

The Contacts Provider is specifically designed for handling synchronization of contacts data between a device and the online service. The purpose of the adapter is to sync your contacts, assign to your custom account with server. This plugin does stuff like checking the network availability, scheduling synchronization and restarting synchronization. This might be useful for creating your own application that contains contacts. You don't have to write it on your own.

Any problems ?

Lately I was doing some synchronization with contacts (including 3rd party ), and I faced one strange situation. After updating contact (replace content with other contact) it was removed and it created a new one(raw_contact_id has been changed). As I read documentation I found the reason that might be it (Content Provider also creates new contacts if raw contact is changed so much so it cannot be assigned to actual contact.) So I tried to reproduce this situation. I tried to modify the contact both ways: by simple queries and using a batch operation, but it was unsuccessful.

What would be great?

It would be very helpful if there was some kind of listener that says which contact (also 3rd party accounts) is new, deleted or what has been changed (all details). On the other hand, there could be some kind of a history and you can simply ask what has changed since some timestamp.

Helpful links:

explanation of: android-contact-id-vs-raw-contact-id
official documentation
nice tutorial for syncAdapter

To be honest, I wrote this article to be able to remember all these things about contacts. If something is not correct, don't hesitate and use comments below please.

Share: