我正在尝试让我的Android应用程序的自定义联系人显示在来自Play商店的Google(最新)联系人应用程序中,但无法让我的用户的帐户出现在副驾驶架上Contacts app快速帐户切换器列表的抽屉。我需要做些什么才能让我的帐户出现在此列表中?
我已实施以下内容:
我的Android Manifest有以下条目:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.contactsynctest">
<uses-permission android:name="android.permission.GET_ACCOUNTS"></uses-permission>
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS"></uses-permission>
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
<uses-permission android:name="android.permission.WRITE_CONTACTS"></uses-permission>
<uses-permission android:name="android.permission.CONTROL_KEYBOARD"></uses-permission>
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"></uses-permission>
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"></uses-permission>
<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">
<activity
android:name="com.test.contactsynctest.ContactSyncActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.test.contactsynctest.TestSyncService"
android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter_contacts"/>
</service>
<service
android:name="com.test.contactsynctest.AuthenticatorService"
android:exported="true">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator"/>
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator"/>
</service>
</application>
</manifest>
AccountAuthenticator
package com.test.contactsynctest;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle;
import android.provider.ContactsContract;
public class AccountAuthenticator extends AbstractAccountAuthenticator {
private static boolean removalAllowed = false;
private Context context;
public static final String ACCOUNT_TYPE = "com.test.contactsynctest";
public static final String USERID = "bob@test.com";
public AccountAuthenticator(Context context) {
super(context);
this.context = context;
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse response,
String accountType, String authTokenType,
String[] requiredFeatures, Bundle options)
throws NetworkErrorException {
Account account = new Account(USERID, ACCOUNT_TYPE);
AccountManager.get(context).addAccountExplicitly(account, null, null);
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
Bundle b = new Bundle();
b.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
b.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
return b;
}
// The following are required but all return null or a default value
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
return null;
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle options)
throws NetworkErrorException {
return null;
}
@Override
public String getAuthTokenLabel(String authTokenType) {
return null;
}
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response,
Account account, String[] features) throws NetworkErrorException {
return null;
}
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse arg0,
Account arg1, Bundle arg2) {
return null;
}
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle options) {
return null;
}
@Override
public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, Account account) {
Bundle b = new Bundle();
b.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed);
return b;
}
}
AuthenticatorService
package com.test.contactsynctest;
import android.accounts.AccountManager;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class AuthenticatorService extends Service {
private static AccountAuthenticator auth = null;
public AuthenticatorService() {
super();
}
@Override
public IBinder onBind(Intent intent) {
if (intent.getAction().equals(AccountManager.ACTION_AUTHENTICATOR_INTENT)) {
if (auth == null) {
auth = new AccountAuthenticator(this);
}
return auth.getIBinder();
}
return null;
}
}
ContactSyncActivity
package com.test.contactsynctest;
import android.Manifest;
import android.accounts.AccountManager;
import android.content.ContentProviderOperation;
import android.content.OperationApplicationException;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Calendar;
public class ContactSyncActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contacts_export);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
writeContact("Sally" + Calendar.getInstance().getTime().toString(), "123-456-789");
Toast.makeText(ContactSyncActivity.this, "Contact created", Toast.LENGTH_LONG).show();
}
});
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_CONTACTS}, 2);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_contacts_export, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
AccountManager.get(this).addAccount(AccountAuthenticator.ACCOUNT_TYPE, null, null, null, null, null, null);
Toast.makeText(ContactSyncActivity.this, "Account registered", Toast.LENGTH_LONG).show();
return true;
}
return super.onOptionsItemSelected(item);
}
private void writeContact(String displayName, String number) {
ArrayList contentProviderOperations = new ArrayList();
//insert raw contact using RawContacts.CONTENT_URI
contentProviderOperations.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, AccountAuthenticator.ACCOUNT_TYPE).withValue(ContactsContract.RawContacts.ACCOUNT_NAME, AccountAuthenticator.USERID).build());
//insert contact display name using Data.CONTENT_URI
contentProviderOperations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID, 0).withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName).build());
//insert mobile number using Data.CONTENT_URI
contentProviderOperations.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, number).withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE).build());
try {
getApplicationContext().getContentResolver().
applyBatch(ContactsContract.AUTHORITY, contentProviderOperations);
} catch (RemoteException e) {
e.printStackTrace();
} catch (OperationApplicationException e) {
e.printStackTrace();
}
}
}
TestSyncService
package com.test.contactsynctest;
import android.accounts.Account;
import android.app.Service;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.os.Bundle;
import android.os.IBinder;
public class TestSyncService extends Service {
private static SyncAdapterImpl syncAdapter = null;
@Override
public IBinder onBind(Intent intent) {
if (syncAdapter == null) {
syncAdapter = new SyncAdapterImpl(this);
}
return syncAdapter.getSyncAdapterBinder();
}
private static class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
public SyncAdapterImpl(Context context) {
super(context, true);
}
@Override
public void onPerformSync(Account account, Bundle extra, String authority, ContentProviderClient provider, SyncResult result) {
// nothing needed here for this test
}
}
}
在我的xml目录中,我有以下文件
帐户认证器
syncadapter_contacts.xml
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.test.contactsynctest"
android:contentAuthority="com.android.contacts"
android:supportsUploading="true"
/>
account_preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen>
</PreferenceScreen>
我测试过其他一些应用,看到Android 7和Android 8之间的行为不一致 - 例如,Android 7.x上的雅虎应用程序没有出现在左侧抽屉快速帐户切换器列表中,而确切相同版本的雅虎应用程序和在Android 8.0上运行的联系人应用程序确实在列表中显示了雅虎帐户。所有Gmail帐户似乎始终显示在“通讯录”应用帐户切换器中,但其他帐户会有所不同。