新联系人创建而不是更新现有联系人
New contact creating rather than updating existing contact
我正在将我的应用程序与 android 默认联系人集成 application.I 想在每个联系人中显示一个选项 "xyz using MyApp" Detail.I 可以在帐户中看到我的应用程序带有同步联系人选项的部分,但我的应用程序仍然没有与现有联系人合并,而是创建一个新联系人并合并到其中。
performSync() 方法
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();
}
}
在您的 addContact
代码中,您缺少告诉 Contacts DB
将您的新原始联系人加入现有联系人的部分,因此该联系人现在将包含您的原始联系人和您的在联系人应用程序中打开该联系人时,将显示特定于应用程序的行。
查看此答案,了解如何将 RawContact
加入现有联系人:why won't contacts aggregate?
您可能需要将一些 RawContact ID
传递给您的 addContact 方法,以便它能够将两者连接在一起。
更新
与其将聚合操作与 RawContact
插入操作一起应用,不如尝试分成两个 applyBatch
调用,同时,让我们将新的原始联系人与 聚合所有 个现有的原始联系人,而不仅仅是其中一个。
试试下面的代码,确保你传递给它现有的contact-id(不是原始联系人id)和你的新原始联系人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.
别开两个duplicate questions,一个就够了
另一个更新
您似乎对 ID 有点困惑。
有Data
个ID,RawContact
个ID,Contact
个ID。
CommonDataKinds.Phone._ID
将 return 一个 Data
ID,标识数据 table 中存储该 phone 数字的特定行。
您也可以从 Phone
table 其他 ID 中获取一个,使用:
CommonDataKinds.Phone.RAW_CONTACT_ID
CommonDataKinds.Phone.CONTACT_ID
您可以在这里阅读更多内容:
试试这个,这是适合我的工作代码
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>
输出
更新
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);
}
}
}
}
我正在将我的应用程序与 android 默认联系人集成 application.I 想在每个联系人中显示一个选项 "xyz using MyApp" Detail.I 可以在帐户中看到我的应用程序带有同步联系人选项的部分,但我的应用程序仍然没有与现有联系人合并,而是创建一个新联系人并合并到其中。
performSync() 方法
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();
}
}
在您的 addContact
代码中,您缺少告诉 Contacts DB
将您的新原始联系人加入现有联系人的部分,因此该联系人现在将包含您的原始联系人和您的在联系人应用程序中打开该联系人时,将显示特定于应用程序的行。
查看此答案,了解如何将 RawContact
加入现有联系人:why won't contacts aggregate?
您可能需要将一些 RawContact ID
传递给您的 addContact 方法,以便它能够将两者连接在一起。
更新
与其将聚合操作与 RawContact
插入操作一起应用,不如尝试分成两个 applyBatch
调用,同时,让我们将新的原始联系人与 聚合所有 个现有的原始联系人,而不仅仅是其中一个。
试试下面的代码,确保你传递给它现有的contact-id(不是原始联系人id)和你的新原始联系人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.
别开两个duplicate questions,一个就够了
另一个更新
您似乎对 ID 有点困惑。
有Data
个ID,RawContact
个ID,Contact
个ID。
CommonDataKinds.Phone._ID
将 return 一个 Data
ID,标识数据 table 中存储该 phone 数字的特定行。
您也可以从 Phone
table 其他 ID 中获取一个,使用:
CommonDataKinds.Phone.RAW_CONTACT_ID
CommonDataKinds.Phone.CONTACT_ID
您可以在这里阅读更多内容:
试试这个,这是适合我的工作代码
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>
输出
更新
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);
}
}
}
}