如何让本机地址簿显示通过 MY_APPLICATION 呼叫的选项?
How do I make the native address book display an option to call via MY_APPLICATION?
有几个应用程序(例如 Viber 和 Skype)可以将他们的联系人导出到本机联系人数据库中。
本地联系人应用随后出现,合并了一些联系人,对于这些合并的联系人,它显示多个选项,如 "Call"、"SMS"、"Call via skype"、"IM via Skype"、 "Video call via skype".
如何将联系人插入本机地址簿,使本机地址簿应用程序显示 "Call via My_Application" 之类的选项?
我知道如何以编程方式插入联系人,如果姓名与现有联系人相似,本机地址簿应用程序甚至会合并它们,但没有通过我的应用程序调用的选项。
谢谢。
您需要将调用操作添加到您的清单中,在您的 Activity
标签内。
<intent-filter>
<action android:name="android.intent.action.CALL_DIAL" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="tel" />
</intent-filter>
然后,在您的 activity 中处理传入数据。
好的,所以我最终弄明白了。
您将需要一个 SyncAdapter,而 SyncAdapter 又需要一个 AccountAuthenticator,有关这方面的更多详细信息,请参见此处:http://developer.android.com/training/sync-adapters/index.html
这里是熊最小提炼版,指出了一些东西:
创建一个 AccountAuthenticator 并将其存根
public class Authenticator extends AbstractAccountAuthenticator {
public Authenticator(Context context) {
super(context);
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
return null;
}
@Override
public Bundle confirmCredentials(
AccountAuthenticatorResponse response, Account account, Bundle options) {
return null;
}
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
throw new UnsupportedOperationException();
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle loginOptions) throws NetworkErrorException {
throw new UnsupportedOperationException();
}
@Override
public String getAuthTokenLabel(String authTokenType) {
// null means we don't support multiple authToken types
return null;
}
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) {
throw new UnsupportedOperationException();
}
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle loginOptions) {
return null;
}
}
创建一个 SyncAdapter 并将其存根
public class SyncAdapter extends AbstractThreadedSyncAdapter {
public SyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
}
}
使 AccountAuthenticator 可通过服务访问
public class AuthenticationService extends Service {
private Authenticator mAuthenticator;
@Override
public void onCreate() {
mAuthenticator = new Authenticator(this);
}
@Override
public IBinder onBind(Intent intent) {
return mAuthenticator.getIBinder();
}
}
使 SyncAdapter 可通过服务访问
public class SyncService extends Service {
private static final Object sSyncAdapterLock = new Object();
private static SyncAdapter sSyncAdapter = null;
@Override
public void onCreate() {
synchronized (sSyncAdapterLock) {
if (sSyncAdapter == null) {
sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
}
}
}
@Override
public IBinder onBind(Intent intent) {
return sSyncAdapter.getSyncAdapterBinder();
}
}
在清单中声明两个服务
<service
android:name="com.test.customcontact.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="com.test.customcontact.SyncService"
android:exported="true">
<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>
如您所见,我们还将在 res/xml 中提供 3 个资源:
- @xml/authenticator,
- @xml/syncadapter
@xml/contacts:
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.test.customcontact"
android:icon="@drawable/ic_launcher"
android:smallIcon="@drawable/ic_launcher"
android:label="@string/app_name"
/>
<?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.test.customcontact"
android:supportsUploading="false"
android:userVisible="true"
/>
<?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.sample.call"
android:icon="@drawable/ic_launcher"
android:summaryColumn="data2"
android:detailColumn="data3"/>
</ContactsSource>
在这些 XML 中,我们会对 android:accountType 感兴趣,它应该是应用程序包,android:mimeType 将是我们将用来存储将打开我们的应用程序的联系人的自定义 mime 类型
还要注意的是
现在输入代码:
创建系统账户(会出现在settings/accounts)ACCOUNT_TYPE与上面的XMLs一样
public static final String ACCOUNT_TYPE = "com.test.customcontact";
public static final String ACCOUNT_NAME = "sample";
private void addNewAccount() {
Account newAccount = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
AccountManager accountManager = (AccountManager) getSystemService(ACCOUNT_SERVICE);
accountManager.addAccountExplicitly(newAccount, null, null);
}
现在我们可以存储一个具有自定义 MIME 类型的联系人,从而存储一个自定义选项,通过该选项可以访问我们的应用程序
public void addContact(String name, String lastName) {
ContentResolver resolver = getContentResolver();
resolver.delete(ContactsContract.RawContacts.CONTENT_URI, ContactsContract.RawContacts.ACCOUNT_TYPE + " = ?", new String[] { ACCOUNT_TYPE });
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, ACCOUNT_NAME)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, ACCOUNT_TYPE)
.build());
//Uncomment below code if you want this contact to show up individually as well and not only if it gets matched with another contact
// ops.add(ContentProviderOperation.newInsert(ContactsContract.Settings.CONTENT_URI)
// .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, AccountGeneral.ACCOUNT_NAME)
// .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, AccountGeneral.ACCOUNT_TYPE)
// .withValue(ContactsContract.Settings.UNGROUPED_VISIBLE, 1)
// .build());
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, name)
.withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, lastName)
.build());
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, MIMETYPE)
.withValue(ContactsContract.Data.DATA1, 12345)
.withValue(ContactsContract.Data.DATA2, "Call via my app")
.withValue(ContactsContract.Data.DATA3, "Call via my app")
.build());
try {
resolver.applyBatch(ContactsContract.AUTHORITY, ops);
}
catch (Exception e) {
e.printStackTrace();
}
}
最后一步是创建一个 activity 来处理自定义 mimetype 操作 - 通过清单中的特殊意图过滤器建立连接
<activity
android:name="com.test.customcontact.ViewingActivity"
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.sample.call" />
</intent-filter>
</activity>
请注意, 中的 最后但同样重要的是所需的清单权限:
添加AndroidManifest.xml权限
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
有几个应用程序(例如 Viber 和 Skype)可以将他们的联系人导出到本机联系人数据库中。
本地联系人应用随后出现,合并了一些联系人,对于这些合并的联系人,它显示多个选项,如 "Call"、"SMS"、"Call via skype"、"IM via Skype"、 "Video call via skype".
如何将联系人插入本机地址簿,使本机地址簿应用程序显示 "Call via My_Application" 之类的选项?
我知道如何以编程方式插入联系人,如果姓名与现有联系人相似,本机地址簿应用程序甚至会合并它们,但没有通过我的应用程序调用的选项。
谢谢。
您需要将调用操作添加到您的清单中,在您的 Activity
标签内。
<intent-filter>
<action android:name="android.intent.action.CALL_DIAL" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="tel" />
</intent-filter>
然后,在您的 activity 中处理传入数据。
好的,所以我最终弄明白了。
您将需要一个 SyncAdapter,而 SyncAdapter 又需要一个 AccountAuthenticator,有关这方面的更多详细信息,请参见此处:http://developer.android.com/training/sync-adapters/index.html
这里是熊最小提炼版,指出了一些东西:
创建一个 AccountAuthenticator 并将其存根
public class Authenticator extends AbstractAccountAuthenticator { public Authenticator(Context context) { super(context); } @Override public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { return null; } @Override public Bundle confirmCredentials( AccountAuthenticatorResponse response, Account account, Bundle options) { return null; } @Override public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { throw new UnsupportedOperationException(); } @Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle loginOptions) throws NetworkErrorException { throw new UnsupportedOperationException(); } @Override public String getAuthTokenLabel(String authTokenType) { // null means we don't support multiple authToken types return null; } @Override public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) { throw new UnsupportedOperationException(); } @Override public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle loginOptions) { return null; } }
创建一个 SyncAdapter 并将其存根
public class SyncAdapter extends AbstractThreadedSyncAdapter { public SyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); } @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { } }
使 AccountAuthenticator 可通过服务访问
public class AuthenticationService extends Service { private Authenticator mAuthenticator; @Override public void onCreate() { mAuthenticator = new Authenticator(this); } @Override public IBinder onBind(Intent intent) { return mAuthenticator.getIBinder(); } }
使 SyncAdapter 可通过服务访问
public class SyncService extends Service { private static final Object sSyncAdapterLock = new Object(); private static SyncAdapter sSyncAdapter = null; @Override public void onCreate() { synchronized (sSyncAdapterLock) { if (sSyncAdapter == null) { sSyncAdapter = new SyncAdapter(getApplicationContext(), true); } } } @Override public IBinder onBind(Intent intent) { return sSyncAdapter.getSyncAdapterBinder(); } }
在清单中声明两个服务
<service android:name="com.test.customcontact.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="com.test.customcontact.SyncService" android:exported="true"> <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>
如您所见,我们还将在 res/xml 中提供 3 个资源:
- @xml/authenticator,
- @xml/syncadapter
@xml/contacts:
<?xml version="1.0" encoding="utf-8"?> <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" android:accountType="com.test.customcontact" android:icon="@drawable/ic_launcher" android:smallIcon="@drawable/ic_launcher" android:label="@string/app_name" /> <?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.test.customcontact" android:supportsUploading="false" android:userVisible="true" /> <?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.sample.call" android:icon="@drawable/ic_launcher" android:summaryColumn="data2" android:detailColumn="data3"/> </ContactsSource>
在这些 XML 中,我们会对 android:accountType 感兴趣,它应该是应用程序包,android:mimeType 将是我们将用来存储将打开我们的应用程序的联系人的自定义 mime 类型 还要注意的是
现在输入代码:
创建系统账户(会出现在settings/accounts)ACCOUNT_TYPE与上面的XMLs一样
public static final String ACCOUNT_TYPE = "com.test.customcontact"; public static final String ACCOUNT_NAME = "sample"; private void addNewAccount() { Account newAccount = new Account(ACCOUNT_NAME, ACCOUNT_TYPE); AccountManager accountManager = (AccountManager) getSystemService(ACCOUNT_SERVICE); accountManager.addAccountExplicitly(newAccount, null, null); }
现在我们可以存储一个具有自定义 MIME 类型的联系人,从而存储一个自定义选项,通过该选项可以访问我们的应用程序
public void addContact(String name, String lastName) { ContentResolver resolver = getContentResolver(); resolver.delete(ContactsContract.RawContacts.CONTENT_URI, ContactsContract.RawContacts.ACCOUNT_TYPE + " = ?", new String[] { ACCOUNT_TYPE }); ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, ACCOUNT_NAME) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, ACCOUNT_TYPE) .build()); //Uncomment below code if you want this contact to show up individually as well and not only if it gets matched with another contact // ops.add(ContentProviderOperation.newInsert(ContactsContract.Settings.CONTENT_URI) // .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, AccountGeneral.ACCOUNT_NAME) // .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, AccountGeneral.ACCOUNT_TYPE) // .withValue(ContactsContract.Settings.UNGROUPED_VISIBLE, 1) // .build()); ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) .withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, name) .withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, lastName) .build()); ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) .withValue(ContactsContract.Data.MIMETYPE, MIMETYPE) .withValue(ContactsContract.Data.DATA1, 12345) .withValue(ContactsContract.Data.DATA2, "Call via my app") .withValue(ContactsContract.Data.DATA3, "Call via my app") .build()); try { resolver.applyBatch(ContactsContract.AUTHORITY, ops); } catch (Exception e) { e.printStackTrace(); } }
最后一步是创建一个 activity 来处理自定义 mimetype 操作 - 通过清单中的特殊意图过滤器建立连接
<activity android:name="com.test.customcontact.ViewingActivity" 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.sample.call" /> </intent-filter> </activity>
请注意, 中的
添加AndroidManifest.xml权限
<uses-permission android:name="android.permission.WRITE_CONTACTS"/> <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.READ_PROFILE" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>