Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you get contacts to aggregate properly when programmatically adding them?

I saw this question and answer, but adding the phone information (and even email) still does not cause the contact information to aggregate properly (when I check the People app, I can see multiple entries under the same name).

Here is the code I use to test it.

//get the account
Account acct = null;
Account[] accounts = AccountManager.get(getContext()).getAccounts(); 
for (Account acc : accounts){
    acct = acc;
}//assuming there's only one account in there (in my case I know there is)

//loop a few times, creating a new contact each time. In theory, if they have the same name they should aggregate
for(int i=0; i<3; i++){
    ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
    ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, acct.type)
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, acct.name)
                .withValue(ContactsContract.RawContacts.AGGREGATION_MODE, ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT) 
                .build());
    ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "ContactName")
                .build());
    ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, "1234567890")
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, 1)
                .build());
    ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Email.DATA, "[email protected]")
                .withValue(ContactsContract.CommonDataKinds.Email.TYPE, 1)
                .build());

    try{        
        getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
    }
    catch (Exception e) {
        Log.e("Contacts", "Something went wrong during creation! " + e);
        e.printStackTrace();
    }
}
like image 250
Matt Avatar asked Feb 23 '12 18:02

Matt


2 Answers

If they aren't aggregating automatically, you can aggregate them manually by adding a row to the AggregationExceptions table. Make sure you notice in the docs that insert is not allowed. You have to do an update instead. That's caught me twice now. The following code should aggregate the two raw contacts with id's 1 and 2:

ContentValues cv = new ContentValues();
cv.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
cv.put(AggregationExceptions.RAW_CONTACT_ID1, 1);
cv.put(AggregationExceptions.RAW_CONTACT_ID2, 2);
getContentResolver().update(AggregationExceptions.CONTENT_URI, cv, null, null);
like image 149
Samuel Avatar answered Oct 20 '22 06:10

Samuel


Here's a modified sample including the linking, and with different data for each, to prove that it works :

    Account acct = null;
    Account[] accounts = AccountManager.get(this).getAccounts();
    for (Account acc : accounts) {
        acct = acc;
    }
    //loop a few times, creating a new contact each time. In theory, if they have the same name they should aggregate
    final ArrayList<Uri> newlyCreatedContactsUris = new ArrayList<>();
    for (int i = 0; i < 2; i++) {
        ArrayList<ContentProviderOperation> ops = new ArrayList<>();
        ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, acct == null ? null : acct.type)
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, acct == null ? null : acct.name)
                .withValue(ContactsContract.RawContacts.AGGREGATION_MODE, ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT)
                .build());
        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "ContactName" + i)
                .build());
        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, Integer.toString(123456789 * (i + 1)))
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, 1)
                .build());
        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Email.DATA, "email" + i + "@address.com")
                .withValue(ContactsContract.CommonDataKinds.Email.TYPE, 1)
                .build());

        try {
            final ContentProviderResult[] contentProviderResults = getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
            newlyCreatedContactsUris.add(contentProviderResults[0].uri);
            Log.d("AppLog", "done creating new contacts data");
        } catch (Exception e) {
            Log.e("AppLog", "Something went wrong during creation! " + e);
            e.printStackTrace();
        }
    }
    //Note: seems we can only link 2 contacts data together, not more
    ArrayList<ContentProviderOperation> mergeOps = new ArrayList<>();
    mergeOps.add(ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI)
            .withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER)
            .withValue(AggregationExceptions.RAW_CONTACT_ID1, newlyCreatedContactsUris.get(0).getLastPathSegment())
            .withValue(AggregationExceptions.RAW_CONTACT_ID2, newlyCreatedContactsUris.get(1).getLastPathSegment())
            .build());
    try {
        final ContentProviderResult[] contentProviderResults2 = getApplicationContext().getContentResolver().applyBatch(ContactsContract.AUTHORITY, mergeOps);
        Log.d("AppLog", "done merging");
    } catch (RemoteException e) {
        e.printStackTrace();
    } catch (OperationApplicationException e) {
        e.printStackTrace();
    }

And the result:

enter image description here

What I'm not sure about is:

  1. How to get existing contacts data and then decide which to merge ? I've noticed that the built in contacts app can merge contacts, but sometimes doesn't put them merged when the main contact will take the name of the merged one. How would I do it?
  2. How come in the contacts app, there is no option to un-link, as opposed to doing the same thing using the UI ?
  3. How it decides which information to replace, which to add, etc... ?
  4. What are the rules to automatically merge contacts? Seems identical contact name is enough, but it can do it in wrong cases (because different persons can have the same name, even including the last name).
  5. Is it really the correct way to use "getLastPathSegment" for the raw contact id?
like image 44
android developer Avatar answered Oct 20 '22 07:10

android developer