问题
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();
}
}
回答1:
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
回答2:
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
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);
}
}
}
}
来源:https://stackoverflow.com/questions/52905223/new-contact-creating-rather-than-updating-existing-contact