如何让本机地址簿显示通过 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

这里是熊最小提炼版,指出了一些东西:

  1. 创建一个 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;
    }
    }
    
  2. 创建一个 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) {
    }
    }
    
  3. 使 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();
    }
    }
    
  4. 使 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();
    }
    }
    
  5. 在清单中声明两个服务

    <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>
    
  6. 如您所见,我们还将在 res/xml 中提供 3 个资源:

    1. @xml/authenticator,
    2. @xml/syncadapter
    3. @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 类型 还要注意的是

现在输入代码:

  1. 创建系统账户(会出现在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);
    }
    
  2. 现在我们可以存储一个具有自定义 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();
        }
    }
    
  3. 最后一步是创建一个 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>
    

请注意, 中的 最后但同样重要的是所需的清单权限:

  1. 添加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"/>