There are a couple of apps like Viber and Skype that export their contacts into the native contact database.
有一些应用程序,如Viber和Skype,将其联系人导出到本机联系人数据库。
The native contact app then comes along, merges some contacts and for these merged contacts it displays multiple options like "Call", "SMS", "Call via skype" ,"IM via Skype", "Video call via skype".
然后出现原生联系人应用程序,合并一些联系人,对于这些合并的联系人,它显示多个选项,如“呼叫”,“短信”,“通过Skype呼叫”,“通过Skype通过IM”,“通过Skype拨打视频”。
How can I insert a contact into the native address book in such a way that the native address book app will display an option like "Call via My_Application" ?
如何将联系人插入到本机地址簿中,使本机地址簿应用程序显示“通过My_Application调用”等选项?
I know how to insert a contact programatically, if the name is similar with an existing contact the native address book app even merges them, but there is no option to call via my app.
我知道如何以编程方式插入联系人,如果名称与现有联系人类似,本地地址簿应用程序甚至会合并它们,但是没有选项可以通过我的应用程序进行调用。
Thanks.
谢谢。
2 个解决方案
#1
3
Ok, so I figured it out eventually.
好的,所以我最终弄明白了。
You will need a SyncAdapter which in tern needs an AccountAuthenticator, more details on this can be found here: http://developer.android.com/training/sync-adapters/index.html
你需要一个SyncAdapter,它需要一个AccountAuthenticator,更多细节可以在这里找到:http://developer.android.com/training/sync-adapters/index.html
And here is the bear minimum distilled version with some stuff pointed out:
这里是熊最小蒸馏版本,指出了一些东西:
-
Create an AccountAuthenticator and just stub it out
创建一个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; } }
-
Create a SyncAdapter and just stub it out
创建一个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) { } }
-
Make the AccountAuthenticator accessible through a service
通过服务访问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(); } }
-
Make the SyncAdapter accessible through a service
通过服务访问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(); } }
-
Declare the two services in the manifest
在清单中声明两个服务
<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>
-
As you can see we will also have 3 resources in res/xml :
如您所见,我们还将在res / xml中拥有3个资源:
- @xml/authenticator,
- @ XML /认证,
- @xml/syncadapter
- @ XML / syncadapter
-
@xml/contacts:
@ XML /联系方式:
<?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>
In these XMLs we will be interested in android:accountType which should be the app package and android:mimeType which will be the custom mime type we will use to store the contacts that will open our app Also note that
在这些XML中,我们将对android:accountType感兴趣,它应该是应用程序包和android:mimeType,这将是我们将用于存储将打开我们的应用程序的联系人的自定义mime类型还要注意
Now for the code:
现在代码:
-
Create a system account (will appear in settings/accounts) ACCOUNT_TYPE is the same as in the above XMLs
创建系统帐户(将显示在设置/帐户中)ACCOUNT_TYPE与上述XML中的相同
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); }
-
Now we can store a contact that will have a custom mime type and thus a custom option via which our app can be accessed
现在我们可以存储一个具有自定义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(); } }
-
The final step is to create an activity to handle the custom mimetype action - the connection is made via a special intent filter in the manifest
最后一步是创建一个活动来处理自定义mimetype操作 - 连接是通过清单中的特殊intent过滤器进行的
<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>
Notice that <data android:mimeType is the same as <ContactsDataKind android:mimeType in the contacts.XML
请注意,
Last but not least the manifest permissions required:
最后但并非最不重要的是所需的清单权限
-
Add AndroidManifest.xml permissions
添加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"/>
#2
1
You need to add Call action to your manifest, inside you Activity
tag.
您需要在活动标记内向清单添加“呼叫”操作。
<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>
And then, handle incoming data inside your activity.
然后,处理活动中的传入数据。
#1
3
Ok, so I figured it out eventually.
好的,所以我最终弄明白了。
You will need a SyncAdapter which in tern needs an AccountAuthenticator, more details on this can be found here: http://developer.android.com/training/sync-adapters/index.html
你需要一个SyncAdapter,它需要一个AccountAuthenticator,更多细节可以在这里找到:http://developer.android.com/training/sync-adapters/index.html
And here is the bear minimum distilled version with some stuff pointed out:
这里是熊最小蒸馏版本,指出了一些东西:
-
Create an AccountAuthenticator and just stub it out
创建一个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; } }
-
Create a SyncAdapter and just stub it out
创建一个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) { } }
-
Make the AccountAuthenticator accessible through a service
通过服务访问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(); } }
-
Make the SyncAdapter accessible through a service
通过服务访问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(); } }
-
Declare the two services in the manifest
在清单中声明两个服务
<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>
-
As you can see we will also have 3 resources in res/xml :
如您所见,我们还将在res / xml中拥有3个资源:
- @xml/authenticator,
- @ XML /认证,
- @xml/syncadapter
- @ XML / syncadapter
-
@xml/contacts:
@ XML /联系方式:
<?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>
In these XMLs we will be interested in android:accountType which should be the app package and android:mimeType which will be the custom mime type we will use to store the contacts that will open our app Also note that
在这些XML中,我们将对android:accountType感兴趣,它应该是应用程序包和android:mimeType,这将是我们将用于存储将打开我们的应用程序的联系人的自定义mime类型还要注意
Now for the code:
现在代码:
-
Create a system account (will appear in settings/accounts) ACCOUNT_TYPE is the same as in the above XMLs
创建系统帐户(将显示在设置/帐户中)ACCOUNT_TYPE与上述XML中的相同
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); }
-
Now we can store a contact that will have a custom mime type and thus a custom option via which our app can be accessed
现在我们可以存储一个具有自定义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(); } }
-
The final step is to create an activity to handle the custom mimetype action - the connection is made via a special intent filter in the manifest
最后一步是创建一个活动来处理自定义mimetype操作 - 连接是通过清单中的特殊intent过滤器进行的
<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>
Notice that <data android:mimeType is the same as <ContactsDataKind android:mimeType in the contacts.XML
请注意,
Last but not least the manifest permissions required:
最后但并非最不重要的是所需的清单权限
-
Add AndroidManifest.xml permissions
添加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"/>
#2
1
You need to add Call action to your manifest, inside you Activity
tag.
您需要在活动标记内向清单添加“呼叫”操作。
<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>
And then, handle incoming data inside your activity.
然后,处理活动中的传入数据。