2

I am integrating my app with android default Contacts application.I would like to show an option "xyz using MyApp" inside every Contacts Detail.I am able to see my app in Accounts Section with an option to sync Contacts but still my app not merging with existing contacts but instead creating a new contact and merging in it.

performSync() method

private static void addContact(ContentResolver contentResolver,int name, int phoneNumber) {
    Log.i("XYZ", "Adding contact: " + name);
    ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();

    //Create our RawContact
    ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
    builder.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, name);
    builder.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, "com.example.xyz.myapplication");
    builder.withValue(ContactsContract.RawContacts.SYNC1, phoneNumber);
    operationList.add(builder.build());

    //Create a Data record of common type 'StructuredName' for our RawContact
    builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
    builder.withValueBackReference(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID, 0);
    builder.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
    builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
    operationList.add(builder.build());

    //Create a Data record of custom type "vnd.android.cursor.item/vnd.com.example.xyz.myapplication.profile" to display a link to the Last.fm profile
    builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
    builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0);
    builder.withValue(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.com.example.xyz.myapplication.profile");
    builder.withValue(ContactsContract.Data.DATA1, phoneNumber);
    builder.withValue(ContactsContract.Data.DATA2, "Last.fm Profile");
    builder.withValue(ContactsContract.Data.DATA3, "View profile");
    operationList.add(builder.build());

    try {
        contentResolver.applyBatch(ContactsContract.AUTHORITY, operationList);
    } catch (Exception e) {
        Log.e("XYZ", "Something went wrong during creation! " + e);
        e.printStackTrace();
    }
}
Android Developer
  • 9,157
  • 18
  • 82
  • 139
  • Can you explain what changes you made on the contacts service part and update contact part? – Hemanth Oct 24 '18 at 13:12
  • @Hemanth I aggregate new contact with the existing contact using the code in accepted answer.. – Android Developer Oct 25 '18 at 04:14
  • Is it possible to update your github project, so that I can compare the changes. It would be helpful for others also. Thanks in advance. – Hemanth Oct 25 '18 at 11:39
  • @Hemanth The issue i was facing was that code in the question adds a new contact rather than updating an existing contact..and it's resolved by the code in accepted answer..Kindly let me know what issue you are facing so that I can help you! – Android Developer Nov 07 '18 at 06:56

2 Answers2

3

in your addContact code you're missing the part that tells Contacts DB to join your new raw-contact into the existing contact, so that contact will now contain your raw-contact and your app-specific line will be shown when opening that contact in the contacts app.

Check this answer on how to join a RawContact into an existing Contact: why won't contacts aggregate?

You'll probably need to pass in some RawContact ID to your addContact method so it'll be able to join the two together.

UPDATE

Instead of applying the aggregation operation together with your RawContact insert operation, let's try to separate into two applyBatch calls, also, let's aggregate your new raw-contact with ALL existing raw-contacts, not just one of them. Try the following code, make sure you pass to it the existing contact-id (not raw-contact id) and your new raw-contact-id.

private void joinIntoExistingContact(long existingContactId, long newRawContactId) {

    // get all existing raw-contact-ids that belong to the contact-id
    List<Long> existingRawIds = new ArrayList<>();
    Cursor cur = getContentResolver().query(RawContacts.CONTENT_URI, new String[] { RawContacts._ID }, RawContacts.CONTACT_ID + "=" + existingContactId, null, null);
    while (cur.moveToNext()) {
        existingRawIds.add(cur.getLong(0));
    }
    cur.close();
    Log.i("Join", "Found " + existingRawIds.size() + " raw-contact-ids");

    List<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();

    // go over all existing raw ids, and join with our new one
    for (Long existingRawId : existingRawIds) {
        Builder builder = ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
        builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
        builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, newRawContactId);
        builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, existingRawId);
        ops.add(builder.build());
    }

    contentResolver.applyBatch(ContactsContract.AUTHORITY, ops);
}

P.S.
don't open two duplicate questions, one is enough.

ANOTHER UPDATE

You seem to have some confusion about IDs.

There are Data IDs, RawContact IDs, and Contact IDs.

CommonDataKinds.Phone._ID will return a Data ID, identifying the specific row in the Data table in which that phone number is stored.

You can get a from the Phone table the other IDs as well, use: CommonDataKinds.Phone.RAW_CONTACT_ID CommonDataKinds.Phone.CONTACT_ID

You can read more here: https://stackoverflow.com/a/50084029/819355

marmor
  • 27,641
  • 11
  • 107
  • 150
  • what is raw1 and raw2 and your answer link and how can i get that? – Android Developer Oct 22 '18 at 07:30
  • you need to obtain an existing raw-id of one of the raw-contacts of the contact you want to add your data to - that can be `raw1`, your new raw-contact's id can be `raw2` (use can use `builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, 0)`) – marmor Oct 22 '18 at 07:42
  • I have updated question with the code i added after your answer in Update section and a link to github with my code – Android Developer Oct 22 '18 at 16:23
  • always showing "Found 0 raw-contact-ids"..i getting existing id through -'cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone._ID))'; – Android Developer Oct 23 '18 at 10:22
  • is it possible you have some confusion on the difference between raw-contact-id and contact-id? if that's an existing contact it must have one or more raw contacts belonging to it – marmor Oct 23 '18 at 10:30
  • I am using -getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null) to query contacts..can i get raw contact id through it?check my code for insertIconinContacts() in question – Android Developer Oct 23 '18 at 10:35
  • it's definitely NOT a contact-id, i'll add some info to my answer – marmor Oct 23 '18 at 11:44
  • +1 for your answer..but I am facing 2 issues..1.Its showing app row for most contacts multiple times inside contact detail screen...2.For some contacts it not able to find the existing Id and creating a new contact – Android Developer Oct 23 '18 at 12:52
  • @marmor Hi How to generate newRawContactId? – Hemanth Oct 25 '18 at 05:59
  • this is the id you're creating in your applyBatch call, you need to get the result from `applyBatch` (of type `ContentProviderResult[]`), then get the first index and extract the id from it, something like `ContentUris.parseId(results[0])` – marmor Oct 25 '18 at 07:13
  • @marmor I am experiencing issue in few devices.For certain contacts I am seeing separate contact being created and my app icon is shown both in the existing and separate contacts.That means the new contact being created with addContact() method(in question) doesn't merge completely with existing contact inside joinIntoExistingContact() method.I tried debugging and found joinIntoExistingContact() method is executing fine and applyBatch() call doesn't throw any exception still the new contact doesn't merge completely with existing contact. – Android Developer Jan 05 '19 at 08:20
  • @marmor I have posted question for that- https://stackoverflow.com/questions/54050778/separate-contact-being-created-despte-using-aggregationexceptions-type-keep-toge – Android Developer Jan 05 '19 at 09:58
1

Try this here is the working code for me

MainActivity

public class MainActivity extends AppCompatActivity {

    private ArrayList<String> mNames = new ArrayList<>();
    private ArrayList<String> mIDs = new ArrayList<>();
    private ArrayList<String> mNumbers = new ArrayList<>();

    @SuppressLint("StaticFieldLeak")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED &&
                ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
                        != PackageManager.PERMISSION_GRANTED) {

            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 123);

        } else {


//      Retrieve names from phone's contact list and save in mNames
            getContactDataBefore();

//      Apply changes to phone's contact list
            new AsyncTask<String, String, String>() {

                @Override
                protected String doInBackground(String... params) {
                    String name, number, id;
                    for (int i = 0; i < mIDs.size(); i++) {
//                    name = mNames.get(i);
                        id = mIDs.get(i);
                        number = mNumbers.get(i);
                        ContactsManager.addContact(MainActivity.this, new MyContact(id, number));
                    }
                    return null;
                }

                @Override
                protected void onPostExecute(String s) {
                    getContactDataAfter();
                }
            }.execute();
        }

    }

    private void getContactDataBefore() {
        int i = 0;

        // query all contact id's from device
        Cursor c1 = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
                new String[]{ContactsContract.Contacts._ID}, null, null, null);

        if ((c1 != null) && c1.moveToFirst()) {


            do {
                mIDs.add(c1.getString(c1.getColumnIndexOrThrow(ContactsContract.Contacts._ID)));

                i++;
            } while (c1.moveToNext() && i < c1.getCount());

            c1.close();

        }

        getPhoneNumber();
    }

    private void getPhoneNumber(){


        for (String data:mIDs){

            Cursor cursor = getContentResolver().query(
                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                    null,
                    ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = ?",
                    new String[]{data}, null);

            while (cursor.moveToNext())
            {
                mNumbers.add(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
            }

            cursor.close();
        }

    }
    /**
     * Method to fetch contacts after updation (for logging purposes)
     */
    private void getContactDataAfter() {
        Cursor c = getContentResolver()
                .query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);

        List<String> RIds = new ArrayList<>();
        mIDs = new ArrayList<>();
        mNumbers = new ArrayList<>();
        int i = 0;

        if (c != null && c.moveToFirst()) {
            do {
                mIDs.add(c.getString(c
                        .getColumnIndexOrThrow(ContactsContract.Contacts._ID)));
                mNames.add(c.getString(c
                        .getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME)));

                Cursor c2 = getContentResolver()
                        .query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                                new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER},
                                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?",
                                new String[]{mIDs.get(i)}, null);

                if (c2 != null && c2.moveToFirst()) {
                    do {
                        mNumbers.add(c2.getString(c2
                                .getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)));
                    } while (c2.moveToNext());
                    c2.close();
                }

                Cursor rawcontacts = getContentResolver()
                        .query(ContactsContract.RawContacts.CONTENT_URI,
                                new String[]{ContactsContract.RawContacts._ID},
                                ContactsContract.RawContacts.CONTACT_ID + "=?",
                                new String[]{mIDs.get(i)}, null);

                if (rawcontacts != null && rawcontacts.moveToFirst()) {
                    do {
                        RIds.add(rawcontacts.getString(rawcontacts
                                .getColumnIndexOrThrow(ContactsContract.RawContacts._ID)));
                    } while (rawcontacts.moveToNext());
                    rawcontacts.close();
                }

                i++;
            } while (c.moveToNext());
            c.close();
        }
    }
}

AuthenticatorActivity

public class AuthenticatorActivity extends AccountAuthenticatorActivity {

    private AccountManager mAccountManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_authenticator);


        Intent res = new Intent();
        res.putExtra(AccountManager.KEY_ACCOUNT_NAME, Constants.ACCOUNT_NAME);
        res.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
        res.putExtra(AccountManager.KEY_AUTHTOKEN, Constants.ACCOUNT_TOKEN);
        Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE);
        mAccountManager = AccountManager.get(this);
        mAccountManager.addAccountExplicitly(account, null, null);
//      mAccountManager.setAuthToken(account, Constants.AUTHTOKEN_TYPE_FULL_ACCESS, Constants.ACCOUNT_TOKEN);
        ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
        setAccountAuthenticatorResult(res.getExtras());
        setResult(RESULT_OK, res);
        finish();
    }
}

SyncAdapter

public class SyncAdapter extends AbstractThreadedSyncAdapter {

    private Context mContext;

    public SyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
        mContext = context;
    }

    @Override
    public void onPerformSync(Account account, Bundle extras, String authority,
                              ContentProviderClient provider, SyncResult syncResult) {
    }
}

SyncService

public class SyncService extends Service {

    private static final Object sSyncAdapterLock = new Object();
    private static SyncAdapter mSyncAdapter = null;

    @Override
    public void onCreate() {
        synchronized (sSyncAdapterLock){
            if(mSyncAdapter == null){
                mSyncAdapter = new SyncAdapter(getApplicationContext(),true);
            }
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mSyncAdapter.getSyncAdapterBinder();
    }
}

AuthenticationService

public class AuthenticationService extends Service {

    private static final String TAG = "AuthenticationService";
    private Authenticator mAuthenticator;

    @Override
    public void onCreate() {
        if (android.util.Log.isLoggable(TAG, android.util.Log.VERBOSE)) {
            android.util.Log.v(TAG, "SyncAdapter Authentication Service started.");
        }
        mAuthenticator = new Authenticator(this);
    }

    @Override
    public void onDestroy() {
        if (android.util.Log.isLoggable(TAG, android.util.Log.VERBOSE)) {
            Log.v(TAG, "SyncAdapter Authentication Service stopped.");
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "getBinder()...  returning the AccountAuthenticator binder for intent "
                    + intent);
        }
        return mAuthenticator.getIBinder();
    }
}

Authenticator

public class Authenticator extends AbstractAccountAuthenticator {

    private final Context mContext;

    public Authenticator(Context context) {
        super(context);
        mContext = context;
    }

    @Override
    public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
        return null;
    }

    @Override
    public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
        final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);

        final Bundle bundle = new Bundle();
        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
        return bundle;
    }

    @Override
    public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
        return null;
    }

    @Override
    public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
        final Bundle result = new Bundle();
        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
        result.putString(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
        return result;
    }

    @Override
    public String getAuthTokenLabel(String authTokenType) {
        return null;
    }

    @Override
    public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
        return null;
    }

    @Override
    public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
        return null;
    }
}

Constants

public class Constants {

    public static final String ACCOUNT_TYPE = "com.example.ajay.contacts_4";
    public static final String ACCOUNT_NAME = "Nilesh_Rathod";
    public static final String ACCOUNT_TOKEN = "733N";
}

ContactsManager

public class ContactsManager {

    private static String MIMETYPE = "vnd.android.cursor.item/com.example.ajay.contacts_4";


    public static void addContact(Context context, MyContact contact) {
        ContentResolver resolver = context.getContentResolver();
        boolean mHasAccount = isAlreadyRegistered(resolver, contact.Id);

        if (mHasAccount) {
            Log.I("Account is Exist");
        } else {

            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();

            // insert account name and account type
            ops.add(ContentProviderOperation
                    .newInsert(addCallerIsSyncAdapterParameter(ContactsContract.RawContacts.CONTENT_URI, true))
                    .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, Constants.ACCOUNT_NAME)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE)
                    .withValue(ContactsContract.RawContacts.AGGREGATION_MODE,
                            ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT)
                    .build());

            // insert contact number
            ops.add(ContentProviderOperation
                    .newInsert(addCallerIsSyncAdapterParameter(ContactsContract.Data.CONTENT_URI, true))
                    .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                    .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
                    .withValue(CommonDataKinds.Phone.NUMBER, contact.number)
                    .build());


            // insert mime-type data
            ops.add(ContentProviderOperation
                    .newInsert(addCallerIsSyncAdapterParameter(ContactsContract.Data.CONTENT_URI, true))
                    .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                    .withValue(ContactsContract.Data.MIMETYPE, MIMETYPE)
                    .withValue(ContactsContract.Data.DATA1, 12345)
                    .withValue(ContactsContract.Data.DATA2, "Nilesh")
                    .withValue(ContactsContract.Data.DATA3, "ContactsDemo")
                    .build());


        }
    }

    /**
     * Check if contact is already registered with app
     */
    public static boolean isAlreadyRegistered(ContentResolver resolver, String id) {

        boolean isRegistered = false;
        List<String> str = new ArrayList<>();

        //query raw contact id's from the contact id
        Cursor c = resolver.query(RawContacts.CONTENT_URI, new String[]{RawContacts._ID},
                RawContacts.CONTACT_ID + "=?",
                new String[]{id}, null);

        //fetch all raw contact id's and save them in a list of string
        if (c != null && c.moveToFirst()) {
            do {
                str.add(c.getString(c.getColumnIndexOrThrow(RawContacts._ID)));
            } while (c.moveToNext());
            c.close();
        }

        //query account types and check the account type for each raw contact id
        for (int i = 0; i < str.size(); i++) {
            Cursor c1 = resolver.query(RawContacts.CONTENT_URI, new String[]{RawContacts.ACCOUNT_TYPE},
                    RawContacts._ID + "=?",
                    new String[]{str.get(i)}, null);

            if (c1 != null) {
                c1.moveToFirst();
                String accType = c1.getString(c1.getColumnIndexOrThrow(RawContacts.ACCOUNT_TYPE));
                if (accType != null && accType.equals("com.example.ajay.contacts_4")) {
                    isRegistered = true;
                    break;
                }
                c1.close();
            }
        }

        return isRegistered;
    }

    /**
     * Check for sync call
     */
    private static Uri addCallerIsSyncAdapterParameter(Uri uri, boolean isSyncOperation) {
        if (isSyncOperation) {
            return uri.buildUpon()
                    .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
                    .build();
        }
        return uri;
    }


}

authenticator.xml

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.example.ajay.contacts_4"
    android:icon="@drawable/icon"
    android:smallIcon="@drawable/icon"
    android:label="@string/app_name" />

contacts.xml

<?xml version="1.0" encoding="utf-8"?>
<ContactsSource
    xmlns:android="http://schemas.android.com/apk/res/android">
    <ContactsDataKind
        android:mimeType="vnd.android.cursor.item/com.example.ajay.contacts_4"
        android:icon="@drawable/icon"
        android:summaryColumn="data2"
        android:detailColumn="data3" />
</ContactsSource>

syncadapter.xml

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:contentAuthority="com.android.contacts"
    android:accountType="com.example.ajay.contacts_4"
    android:supportsUploading="false"
    android:userVisible="true" />

manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="neel.com.contactssyncingapp">

    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <service android:name=".utils.AuthenticationService" >
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator" />
            </intent-filter>

            <meta-data
                android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/authenticator" />
        </service>

        <service android:name=".sync.SyncService" >
            <intent-filter>
                <action android:name="android.content.SyncAdapter" />
            </intent-filter>

            <meta-data
                android:name="android.content.SyncAdapter"
                android:resource="@xml/syncadapter" />
            <meta-data
                android:name="android.provider.CONTACTS_STRUCTURE"
                android:resource="@xml/contacts" />
        </service>

        <activity android:name=".activity.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>
        </activity>


        <activity
            android:name=".activity.ContactActivity"
            android:label="ContactActivity"
            android:screenOrientation="portrait"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="vnd.android.cursor.item/com.example.ajay.contacts_4" />
            </intent-filter>
        </activity>


        <activity android:name=".activity.AuthenticatorActivity" />
    </application>

</manifest>

OUTPUT

enter image description here

UPDATE

public class ContactActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_contact);


        Uri intentData = getIntent().getData();
        if (!Uri.EMPTY.equals(intentData))
        {
            Cursor cursor = getContentResolver().query(intentData, null, null, null, null);
            if (cursor.moveToNext())
            {
                String username = cursor.getString(cursor.getColumnIndex("data2"));
                String number = cursor.getString(cursor.getColumnIndex("data3"));

                Log.e("USER_NAME",username);
                Log.e("USER_NUMBER",number);
            }
        }
    }
}
AskNilesh
  • 67,701
  • 16
  • 123
  • 163
  • Thanks..but for some contacts it is aggregating multiple times while for some contact it doesn't aggregate at all..I don't have regesteration condition as i want to aggregate with every contact..And shouldn't addContact() be called from inside onPerformSync()..u doing nothing inside onPerformSync()? – Android Developer Oct 23 '18 at 07:49
  • @BhuvneshVarma where is `ContactActivity` activity in your manifest file – AskNilesh Oct 23 '18 at 08:51
  • @BhuvneshVarma you need to create a activity like `ContactActivity` in above manifest file to handle event when user click you app name in contact – AskNilesh Oct 23 '18 at 08:52
  • ContactActivity is the activity which should be open on click?I'll handle click later..first need to aggregate app properly with contacts? – Android Developer Oct 23 '18 at 08:53
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/182336/discussion-between-nilesh-rathod-and-bhuvnesh-varma). – AskNilesh Oct 23 '18 at 08:54
  • +1 for your answer..using AggregationException helps me to resolve that...how can I pass phone number to contact detail activity on clicking on an item? – Android Developer Oct 23 '18 at 12:57
  • @BhuvneshVarma check in my answer i have added that code (created that ContactActivity to open activity from contact ) let me know if any help reqired – AskNilesh Oct 23 '18 at 12:58
  • @BhuvneshVarma check updated answer i have added code for `ontactActivity` – AskNilesh Oct 23 '18 at 13:08
  • @BhuvneshVarma have checked it?? – AskNilesh Oct 24 '18 at 06:14
  • 1
    @BhuvneshVarma Can you update your github project so that I can also check what I'm missing? – Hemanth Oct 24 '18 at 13:09
  • 1
    @BhuvneshVarma can u please update your code in github so it can help me to address mt mistake? – Goku Oct 29 '18 at 06:50
  • @Prem kindly let me know what issue you are facing? – Android Developer Oct 30 '18 at 05:01
  • @BhuvneshVarma uisng above both answer and your [AppContactSyncSample](https://github.com/bhuvnesh123/AppContactSyncSample) i'm able to create sync adapter but in contact list my app icon is not displaying i have done same thing mention in both above answers and your all comments but i can't find issue can u please share your code with us thank you for your response – Goku Oct 30 '18 at 05:05
  • @Prem Is your code creating a new contact on synching rather than aggregating with the existing contact?Have you called `contentResolver.applyBatch(ContactsContract.AUTHORITY, ops);` and what result is it returning? – Android Developer Oct 31 '18 at 10:30
  • @Prem I understand that your code is for aggregating contact..but check if it is creating a new contact rather than aggregating..Also check what result applyBatch() is returning? – Android Developer Oct 31 '18 at 10:39
  • @Prem check what result applyBatch() is returning?debug code and try to find out the issue..If possible share your code in a new question so that i can check..The issue i was facing was that code in the question adds a new contact rather than updating an existing contact..and it's resolved by the code in accepted answer..your issue might be different – Android Developer Nov 07 '18 at 06:46
  • @Hemanth did you still required solution, because i found the solution for this – AskNilesh Mar 11 '19 at 09:46