How to control android contact aggregation - android

I have an app that exports contacts. Similar contacts get aggregated by an internal scoring mechanism, see Android contacts aggregation process
My issue is that the same contacts exported with Outlook for Android get a different aggregation.
To test this i have created one contact (Identicus) with name, given name, work phone and email 5 times. It gets aggregated for both apps.
I have created another two contacts (Hans Test and Hans Othertest) with the same given name and work phone, but different names.
When i export it the two contacts get aggregated, when Outlook exports it they are not aggregated.
With ContactsContract.RawContacts.AGGREGATION_MODE_DISABLED i can disable aggregation for all my contacts - but i want the identical contacts to be aggregated.
I checked https://developer.android.com/reference/android/provider/ContactsContract.Contacts.AggregationSuggestions to see if i could identify the contacts that were about to be aggregated and un-aggregate them by using an AggregationException, but the data seems to be not useful.
Does anybody have an idea why the aggregation works differently for Outlook and my app?
Or a hint how i could control this behavior?

AggregationExceptions is indeed the key here.
If you know the RawContact IDs of the contacts in question you can tell Android to aggregate them, or un-aggregate them by adding an AggregationException.
So if the RawContacts have ids 111 & 222 -
ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI)
.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_SEPARATE)
.withValue(AggregationExceptions.RAW_CONTACT_ID1, 111)
.withValue(AggregationExceptions.RAW_CONTACT_ID2, 222)
.build()
will keep those raws separated, and -
ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI)
.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER)
.withValue(AggregationExceptions.RAW_CONTACT_ID1, 111)
.withValue(AggregationExceptions.RAW_CONTACT_ID2, 222)
.build()
will keep those raws aggregated.
you'll of course need to apply the operations, via something like:
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);

Related

How apps like Truecaller, messenger maintains sync with native contacts application if any contact changes in the native contact app

I have observed if we update/delete a the contact in native contacts application same changes is reflected into the TrueCaller instantly.
I can think of two ways it get the changes:
It refresh the whole contact list by deleting all the existing entries in app and query all the entries from native contact using contact provider every time app comes to foreground.But this approach might involve querying the RawContact table and Data table on every launch. (This might be a costly in terms of computation)
Each raw contact maintains VERSION which increments if any change is made to the raw contact via native app. This can be leveraged for modifications but will require to maintain the old values in the truecaller(or similar app) so that we can compare.This approach will still require you query the RawContacts table.
The later approach requires to maintain the Version on the TrueCaller app database as well.
Following operations will be required to detect any changes :
Check the version of the raw contact in the TrueCaller if the version is less than that of raw contact in native contacts db, then, query the corresponding Data table for all the changes.
If any raw contact is deleted from native then it's either removed from the RawContact table or marked Deleted. Thus we need to check for Deleted flag or if any pervious RawContact entry is missing in the RawContact table.
Other cases like a new contact is added hence a new raw contact entry in the RawContact table will be found etc.
Is there any better way to import the contact in your app and maintain proper synchronization if any operation like addition, deletion or modification is performed in the native app?
Certainly, app like TrueCaller does a great job with this. Any article on import contacts etc any relevant source will be appreciated.
As apps such as TrueCaller are closed source, there's no way to give you a definite answer, however option 2 would be my course of action to manage the sync with the device contacts.
You can add to that ContentObserver on the ContactsContract ContentProvider which will call your code whenever something changes so you can run the suggested code at option 2, however note that contacts tend to change very very very frequently, so I would limit such background syncs if they are needed to once a day to prevent hogging the battery/CPU.

Should I rely on SOURCE_ID when reading Android Contacts?

I am creating an android contacts app, so I regularly read user's contacts and store them in my app. To do this, I need to rely on some kind of ID so that I know which contact I should update (or add/delete) in my app and Contacts Provider supplies several of them:
CONTACT_ID is the aggregate contact id,
each aggregate contact consists of one or more Raw Contacts, each with its own RAW_CONTACT_ID,
and most important, each Raw Contact has a SOURCE_ID, which is supposed to be the server id, i.e. the id this contact has in this account's server.
I 've chosen to rely on SOURCE_ID, since this sounds like the most stable one. E.g. when user removes and re-adds the same account in their device, I wouldn't want this account's contacts to get different IDs, because I wouldn't be able to match them in my app.
However, only Gmail sync adapter seems to keep the promise documented below. Exchange sync adapters unfortunately do not, SOURCE_ID changes, and it's definitely not any server id, since it has a small number like 23:4.
Question: Any ideas how to overcome this problem? Am I using the right ID for the intended use? Does exchange adapter stores the "permanent server id" in some other field?
Documentation: The SOURCE_ID must be unique for each account type and should be stable across syncs:
Unique: Each raw contact for an account must have its own source id. If you don't enforce this, you'll cause problems in the contacts
application. Notice that two raw contacts for the same account type
may have the same source id. For example, the raw contact "Thomas
Higginson" for the account emily.dickinson#gmail.com is allowed to
have the same source id as the raw contact "Thomas Higginson" for the
account emilyd#gmail.com.
Stable: Source ids are a permanent part of the online service's data for the raw contact. For example, if the user clears Contacts Storage
from the Apps settings and re-syncs, the restored raw contacts should
have the same source ids as before. If you don't enforce this,
shortcuts will stop working.
LOOKUP_KEY is what you're looking for.
LOOKUP_KEY
An opaque value that contains hints on how to find the
contact if its row id changed as a result of a sync or aggregation.
You should use a pair of <CONTACT_ID, LOOKUP_KEY> to keep track of contacts.
In normal use, use the CONTACT_ID value, but if your code gets a hint that the CONTACT_ID has changed (either missing, or unexpected contact name), you can use the LOOKUP_KEY to find the new contact-id.
Or you can use Contacts.getLookupUri() to get a URI you can always use to quickly find a contact no matter what its CONTACT_ID or LOOKUP_KEY actual values are.
First thing, it's not a good idea to store the Id in your application and hoping that the Id won't change over time and be consistent. You are right about 'SOURCE_ID' column that it's more consistent compared to the other two (CONTACT_ID is the most brittle while 'RAW_CONTACT_ID' persists at least until user logs out of an account and logs in again).
We had a syncable account with contacts and we used to keep the unique id in one of the general purpose columns in the 'raw_contacts' table (SYNC1 - SYNC10). So although Google suggests that account providers use the database columns in a certain manner but it's upto the provider totally.
General rule to follow is, never use those id's for long term persistence and if you do so, expect them to change. Also since you are making a contact App, you obviously need some kind of reference key. In such case, don't go by the rule that all account providers will put their key in the same column. It's brittle but that's the way it is.
Edit - You should use ContactsColumns.LOOKUP_KEY (previous answer also cited the same). As per the Google Documentation -
LOOKUP_KEY
Added in API level 5 String LOOKUP_KEY An opaque value that contains
hints on how to find the contact if its row id changed as a result of
a sync or aggregation.
Constant Value: "lookup"
https://developer.android.com/reference/android/provider/ContactsContract.ContactsColumns.html#LOOKUP_KEY
You can fetch the lookup key if you have the contact Id using API's provided. Look here - https://developer.android.com/reference/android/provider/ContactsContract.Contacts.html

Android filter contacts in application

I made an app that extracts contacts and shows them. My problem is that is pick all contacts from all accounts (skype,viber etc.) This thing can be filtered ??
The Android contacts store aggregates all contacts from all accounts (Which does vary by vendor implementation e.g. HTC stores XML data in the notes field of the contact row).
The aggregated information is available via ContactsContract.Contacts API and gives the most complete version of all contact data in the contacts store with aggregation conflicts and resolutions already dealt with. If you simply want to access information about contacts that meet a certain requirement e.g. they must have a phone number, then I would suggest this API as it allows for SQLite-esque WHERE clauses.
If you want to access raw contact information separated by account that they came from with no aggregation applied, then use the ContactsContract.RawContacts API (Documentation). This would typically be more appropriate when you're editing contact information as an insert or update query will trigger re-aggregation of the device's contact stores.

How to marge between 2 or more contacts on android

In my application that i write i have 2 or more contacts that i need to marge.
So i want to use the android feature "join" between those contacts to make this marge between than.
For example, I have contact "AAA" with an email account and a phone number, and I've noticed that some apps like whatsapp, Facebook and Skype are creating new contact entries for AAA, instead of merging the existing one.
How can i do it ?
I don't find any example on the web or anything how to do it.
There is an aggregate mode(RawContacts.AGGREGATION_MODE). Have a look at this Android not adding all contacts with duplicate fields

How contacts content provider in android structures its data?

My task is to retreive name, phone, email, company and note from all contacts on android device. I have never worked with content providers before. I read content provider documentation, tutorials, saw code samples, but I'm still not quite sure: does android stores information about name, phone, email, company and note in different tables?
So do I have to query name, phone, email, company and note separately for each contact? 5 queries per contact * number of contact = most efficient way? Or do I have alternative with fewer number of queries? Why not to store all contacts and all contact fields in the same table so that it would be possible to get all data with single SELECT query (its analog in android content providers framework)?
So my main question again: do I have to make 5 queries per contact or there is more effient way?
does android stores information about name, phone, email, company and
note in different tables?
Almost. Name is stored in Contacts.DISPLAY_NAME.
phone and emails are in Data table but to interpret column names you need to use
ContactsContract.CommonDataKinds.Phone class.
Update: The original article I referred to was removed.
Here are the relevant links:
Android documentation overall
The columns reference
If you are interested in what goes on behind the curtain, google implemented their contact database (as of 4.2) so that phone, email are stored all in the same table called data. This table has columns like data1, data2 .... data15, which allows storing diverse data in that table as long as attribute of that data item do not exceed 15.
My guess it is for performance reasons - much faster to select all the data about one contact in one query() call.
Name is another story. It is stored in two tables, "raw_contacts"(column display_name) and also "data" (column "data1")

Categories

Resources