7

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@address.com")
                .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();
    }
}
Community
  • 1
  • 1
Matt
  • 985
  • 2
  • 9
  • 22

2 Answers2

8

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);
Samuel
  • 16,923
  • 6
  • 62
  • 75
  • So would I need to apply this every time I add a contact then? The program I am working on will be adding a large number of contacts (some or all of which may already exist), so would that mean that for each contact I add I would have to go lookup the IDs of all the contacts with the same name and add this exception? – Matt Feb 23 '12 at 19:50
  • As far as I know it's pretty grim. I believe they're either aggregated automatically, or you have to force them. – Samuel Feb 23 '12 at 20:25
  • It just seems silly to have to go around and manually do what the phone is expected to do. I'm thinking there has to be something wrong with the way I am adding contacts. – Matt Feb 24 '12 at 15:01
  • NB: you can only aggregate two raw contacts at a time. So, if the user does some edit action that results in 4 raw contacts, you'll have to do this aggregate step 3 times. – Peri Hartman Nov 21 '16 at 16:46
1

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?
android developer
  • 114,585
  • 152
  • 739
  • 1,270