このデベロッパー ガイドでは、仕事用プロファイルの連絡先データを使用するようにアプリを拡張する方法について説明します。Android の Contact API を使用したことがない場合は、Contacts Provider を参照して API をよく理解してください。
概要
仕事用プロファイルがインストールされたデバイスでは、仕事用プロファイルと個人用プロファイルの別々のローカル ディレクトリに連絡先が保存されます。デフォルトでは、アプリが個人用プロファイルで実行されている場合、仕事用の連絡先は表示されません。ただし、アプリは仕事用プロファイルの連絡先情報にアクセスできます。たとえば、検索結果に個人用と仕事用ディレクトリの両方の連絡先を表示する Google の Android 連絡帳アプリがあります。
多くの場合、ユーザーは個人用のデバイスやアプリを仕事に使いたいと考えています。仕事用プロファイルの連絡先を使用することで、アプリをユーザーの業務に使用することができます。
ユーザー エクスペリエンス
アプリが仕事用プロファイルの連絡先情報をどのように表示するかを検討してください。 アプリの性質と使用する理由によって最適なアプローチは異なりますが、次の点を考慮してください。
- アプリに仕事用プロファイルの連絡先をデフォルトで含めるべきか、それともユーザーがオプトインするべきか。
- 仕事用プロファイルの連絡先と個人用プロファイルの連絡先を混在または分離すると、ユーザーフローにどのような影響がありますか?
- 仕事用プロファイルの連絡先を誤ってタップした場合、どのような影響がありますか?
- 仕事用プロファイルの連絡先が利用できない場合、アプリのインターフェースはどのようになりますか?
アプリでは仕事用プロファイルの連絡先を明示する必要があります。ブリーフケースなどのおなじみの仕事用アイコンを使用して連絡先にバッジを付けることもできます。
たとえば、図 1 に示す Google 連絡帳アプリは、次のようにして、仕事用プロファイルと個人用プロファイルの連絡先をリストします。
- リストの仕事用と個人用のセクションを区切るサブヘッダーを挿入します。
- 仕事用の連絡先とブリーフケース アイコンのバッジ。
- タップすると、仕事用プロファイルに仕事用の連絡先が開きます。
デバイスを使用するユーザーが仕事用プロファイルをオフにすると、アプリは仕事用プロファイルや組織のリモート連絡先ディレクトリから連絡先情報を検索できなくなります。仕事用プロファイルの連絡先の使用方法によっては、これらの連絡先を暗黙的に除外できます。また、ユーザー インターフェース コントロールを無効にしなければならない場合もあります。
権限
アプリがすでにユーザーの連絡先と連携している場合は、アプリ マニフェスト ファイルでリクエストした READ_CONTACTS
(または WRITE_CONTACTS
)権限がアプリに付与されます。同じユーザーが個人用プロファイルと仕事用プロファイルを使用しているため、仕事用プロファイルから連絡先データにアクセスするためにそれ以上の権限は必要ありません。
IT 管理者は、仕事用プロファイルで個人用プロファイルと連絡先情報を共有することをブロックできます。IT 管理者がアクセスをブロックした場合、連絡先検索は空の結果として返されます。ユーザーが仕事用プロファイルをオフにした場合、アプリで特定のエラーを処理する必要はありません。ディレクトリ コンテンツ プロバイダは引き続き、ユーザーの仕事用連絡先ディレクトリに関する情報を返します(ディレクトリのセクションをご覧く��さい)。これらの権限をテストするには、開発とテスト セクションをご覧ください。
連絡先の検索
アプリが個人用プロファイルの連絡先を取得する際に使用するのと同じ API とプロセスを使用して、仕事用プロファイルから連絡先を取得できます。連絡先のエンタープライズ URI は、Android 7.0(API レベル 24)以降でサポートされています。URI を次のように調整する必要があります。
- コンテンツ プロバイダ URI を
Contacts.ENTERPRISE_CONTENT_FILTER_URI
に設定し、連絡先の名前をクエリ文字列として指定します。 - 検索対象の連絡先ディレクトリを設定します。たとえば、
ENTERPRISE_DEFAULT
は、仕事用プロファイルのローカル店舗にある連絡先を検索します。
URI の変更は、CursorLoader
などのコンテンツ プロバイダのメカニズムで使用できます。データアクセスはワーカー スレッドで行われるため、ユーザー インターフェースに連絡先データを読み込むのに最適です。わかりやすくするために、このガイドの例では ContentResolver.query()
を呼び出します。仕事用プロファイルのローカル連絡先ディレクトリで連絡先を見つける方法は以下のとおりです。
Kotlin
// First confirm the device user has given permission for the personal profile. // There isn't a separate work permission, but an IT admin can block access. val readContactsPermission = ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.READ_CONTACTS) if (readContactsPermission != PackageManager.PERMISSION_GRANTED) { return } // Fetch Jackie, James, & Jason (and anyone else whose names begin with "ja"). val nameQuery = Uri.encode("ja") // Build the URI to look up work profile contacts whose name matches. Query // the default work profile directory which is the locally-stored contacts. val contentFilterUri = ContactsContract.Contacts.ENTERPRISE_CONTENT_FILTER_URI .buildUpon() .appendPath(nameQuery) .appendQueryParameter( ContactsContract.DIRECTORY_PARAM_KEY, ContactsContract.Directory.ENTERPRISE_DEFAULT.toString() ) .build() // Query the content provider using the generated URI. var cursor = getContentResolver() .query( contentFilterUri, arrayOf( ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY ), null, null, null ) // Print any results found using the work profile contacts' display name. cursor?.use { while (it.moveToNext()) { Log.i(TAG, "Work profile contact: ${it.getString(2)}") } }
Java
// First confirm the device user has given permission for the personal profile. // There isn't a separate work permission, but an IT admin can block access. int readContactsPermission = ContextCompat.checkSelfPermission( getBaseContext(), Manifest.permission.READ_CONTACTS); if (readContactsPermission != PackageManager.PERMISSION_GRANTED) { return; } // Fetch Jackie, James, & Jason (and anyone else whose names begin with "ja"). String nameQuery = Uri.encode("ja"); // Build the URI to look up work profile contacts whose name matches. Query // the default work profile directory which is the locally stored contacts. Uri contentFilterUri = ContactsContract.Contacts.ENTERPRISE_CONTENT_FILTER_URI .buildUpon() .appendPath(nameQuery) .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(ContactsContract.Directory.ENTERPRISE_DEFAULT)) .build(); // Query the content provider using the generated URI. Cursor cursor = getContentResolver().query( contentFilterUri, new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY }, null, null, null); if (cursor == null) { return; } // Print any results found using the work profile contacts' display name. try { while (cursor.moveToNext()) { Log.i(TAG, "Work profile contact: " + cursor.getString(2)); } } finally { cursor.close(); }
ディレクトリ
多くの組織では、組織全体の連絡先情報が格納された Microsoft Exchange や LDAP などのリモート ディレクトリを使用しています。このアプリを使用すると、ユーザーは組織のディレクトリにある同僚とのコミュニケーションや共有が容易になります。通常、これらのディレクトリには何千件もの連絡先が含まれており、それらを検索するにはアプリにもアクティブなネットワーク接続が必要です。Directory
コンテンツ プロバイダを使用すると、ユーザーのアカウントで使用されているディレクトリを取得し、個々のディレクトリの詳細を調べることができます。
Directory.ENTERPRISE_CONTENT_URI
コンテンツ プロバイダにクエリを実行して、個人用プロファイルと仕事用プロファイルから一緒に返されたディレクトリを取得します。仕事用プロファイル ディレクトリの検索は、Android 7.0(API レベル 24)以降でサポートされています。アプリでは、連絡先ディレクトリを操作するための READ_CONTACTS
権限をユーザーが付与する必要があります。
Android では、さまざまな種類のローカル ディレクトリとリモート ディレクトリに連絡先情報が保存されています。そのため、Directory
クラスには、ディレクトリに関する詳細情報を取得するために呼び出すことができるメソッドが用意されています。
isEnterpriseDirectoryId()
- このメソッドを呼び出して、仕事用プロファイル アカウントのディレクトリかどうかを確認できます。
ENTERPRISE_CONTENT_URI
コンテンツ プロバイダは、個人用プロファイルと仕事用プロファイルの連絡先ディレクトリをまとめて返します。 isRemoteDirectoryId()
- このメソッドを呼び出して、ディレクトリがリモートかどうかを確認します。リモート ディレクトリは、企業の連絡先ストアの場合もあれば、ユーザーのソーシャル ネットワークである場合もあります。
次の例は、これらのメソッドを使用して仕事用プロファイル ディレクトリをフィルタリングする方法を示しています。
Kotlin
// First, confirm the device user has given READ_CONTACTS permission. // This permission is still needed for directory listings ... // Query the content provider to get directories for BOTH the personal and // work profiles. val cursor = getContentResolver() .query( ContactsContract.Directory.ENTERPRISE_CONTENT_URI, arrayOf(ContactsContract.Directory._ID, ContactsContract.Directory.PACKAGE_NAME), null, null, null ) // Print the package name of the work profile's local or remote contact directories. cursor?.use { while (it.moveToNext()) { val directoryId = it.getLong(0) if (ContactsContract.Directory.isEnterpriseDirectoryId(directoryId)) { Log.i(TAG, "Directory: ${it.getString(1)}") } } }
Java
// First, confirm the device user has given READ_CONTACTS permission. // This permission is still needed for directory listings ... // Query the content provider to get directories for BOTH the personal and // work profiles. Cursor cursor = getContentResolver().query( ContactsContract.Directory.ENTERPRISE_CONTENT_URI, new String[]{ ContactsContract.Directory._ID, ContactsContract.Directory.PACKAGE_NAME }, null, null, null); if (cursor == null) { return; } // Print the package name of the work profile's local or remote contact directories. try { while (cursor.moveToNext()) { long directoryId = cursor.getLong(0); if (ContactsContract.Directory.isEnterpriseDirectoryId(directoryId)) { Log.i(TAG, "Directory: " + cursor.getString(1)); } } } finally { cursor.close(); }
この例では、ディレクトリの ID とパッケージ名を取得します。ユーザーが連絡先ディレクトリのソースを選択できるユーザー インターフェースを表示するには、ディレクトリに関する詳細情報の取得が必要になることがあります。使用可能なその他のメタデータ フィールドについては、Directory
クラスのリファレンスをご覧ください。
電話番号検索
アプリは PhoneLookup.CONTENT_FILTER_URI
に対してクエリを実行し、電話番号の連絡先データを効率的に検索できます。この URI を PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI
に置き換えると、個人用プロファイルの連絡先プロバイダと仕事用プロファイルの連絡先プロバイダの両方からルックアップ結果を取得できます。この仕事用プロファイルのコンテンツ URI は、Android 5.0(API レベル 21)以降で使用できます。
次の例は、着信用のユーザー インターフェースを構成するために仕事用プロファイルのコンテンツ URI をクエリするアプリを示しています。
Kotlin
fun onCreateIncomingConnection( connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest ): Connection { var request = request // Get the telephone number from the incoming request URI. val phoneNumber = this.extractTelephoneNumber(request.address) var displayName = "Unknown caller" var isCallerInWorkProfile = false // Look up contact details for the caller in the personal and work profiles. val lookupUri = Uri.withAppendedPath( ContactsContract.PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(phoneNumber) ) val cursor = getContentResolver() .query( lookupUri, arrayOf( ContactsContract.PhoneLookup._ID, ContactsContract.PhoneLookup.DISPLAY_NAME, ContactsContract.PhoneLookup.CUSTOM_RINGTONE ), null, null, null ) // Use the first contact found and check if they're from the work profile. cursor?.use { if (it.moveToFirst() == true) { displayName = it.getString(1) isCallerInWorkProfile = ContactsContract.Contacts.isEnterpriseContactId(it.getLong(0)) } } // Return a configured connection object for the incoming call. val connection = MyAudioConnection() connection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED) // Our app's activity uses this value to decide whether to show a work badge. connection.setIsCallerInWorkProfile(isCallerInWorkProfile) // Configure the connection further ... return connection }
Java
public Connection onCreateIncomingConnection ( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) { // Get the telephone number from the incoming request URI. String phoneNumber = this.extractTelephoneNumber(request.getAddress()); String displayName = "Unknown caller"; boolean isCallerInWorkProfile = false; // Look up contact details for the caller in the personal and work profiles. Uri lookupUri = Uri.withAppendedPath( ContactsContract.PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(phoneNumber)); Cursor cursor = getContentResolver().query( lookupUri, new String[]{ ContactsContract.PhoneLookup._ID, ContactsContract.PhoneLookup.DISPLAY_NAME, ContactsContract.PhoneLookup.CUSTOM_RINGTONE }, null, null, null); // Use the first contact found and check if they're from the work profile. if (cursor != null) { try { if (cursor.moveToFirst() == true) { displayName = cursor.getString(1); isCallerInWorkProfile = ContactsContract.Contacts.isEnterpriseContactId(cursor.getLong(0)); } } finally { cursor.close(); } } // Return a configured connection object for the incoming call. MyConnection connection = new MyConnection(); connection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED); // Our app's activity uses this value to decide whether to show a work badge. connection.setIsCallerInWorkProfile(isCallerInWorkProfile); // Configure the connection further ... return connection; }
メール検索
アプリは Email.ENTERPRISE_CONTENT_LOOKUP_URI
をクエリすることで、メールアドレスの個人用または仕事用の連絡先データを取得できます。この URL を照会すると、まず個人の連絡先が完全一致するものを検索します。個人用の連絡先と一致しない場合、プロバイダは仕事用の連絡先で一致するものを検索します。この URI は Android 6.0(API レベル 23)以降で使用できます。
メールアドレスの連絡先情報を検索する方法は次のとおりです。
Kotlin
// Build the URI to look up contacts from the personal and work profiles that // are an exact (case-insensitive) match for the email address. val emailAddress = "somebody@example.com" val contentFilterUri = Uri.withAppendedPath( ContactsContract.CommonDataKinds.Email.ENTERPRISE_CONTENT_LOOKUP_URI, Uri.encode(emailAddress) ) // Query the content provider to first try to match personal contacts and, // if none are found, then try to match the work contacts. val cursor = contentResolver.query( contentFilterUri, arrayOf( ContactsContract.CommonDataKinds.Email.CONTACT_ID, ContactsContract.CommonDataKinds.Email.ADDRESS, ContactsContract.Contacts.DISPLAY_NAME ), null, null, null ) ?: return // Print the name of the matching contact. If we want to work-badge contacts, // we can call ContactsContract.Contacts.isEnterpriseContactId() with the ID. cursor.use { while (it.moveToNext()) { Log.i(TAG, "Matching contact: ${it.getString(2)}") } }
Java
// Build the URI to look up contacts from the personal and work profiles that // are an exact (case-insensitive) match for the email address. String emailAddress = "somebody@example.com"; Uri contentFilterUri = Uri.withAppendedPath( ContactsContract.CommonDataKinds.Email.ENTERPRISE_CONTENT_LOOKUP_URI, Uri.encode(emailAddress)); // Query the content provider to first try to match personal contacts and, // if none are found, then try to match the work contacts. Cursor cursor = getContentResolver().query( contentFilterUri, new String[]{ ContactsContract.CommonDataKinds.Email.CONTACT_ID, ContactsContract.CommonDataKinds.Email.ADDRESS, ContactsContract.Contacts.DISPLAY_NAME }, null, null, null); if (cursor == null) { return; } // Print the name of the matching contact. If we want to work-badge contacts, // we can call ContactsContract.Contacts.isEnterpriseContactId() with the ID. try { while (cursor.moveToNext()) { Log.i(TAG, "Matching contact: " + cursor.getString(2)); } } finally { cursor.close(); }
仕事用の連絡先を表示する
個人用プロファイルで実行されているアプリでは、仕事用プロファイルの連絡先カードを表示できます。Android 5.0 以降では ContactsContract.QuickContact.showQuickContact()
を呼び出して、仕事用プロファイルで連絡帳アプリを起動し、連絡先のカードを表示します。
仕事用プロファイルの正しい URI を生成するには、ContactsContract.Contacts.getLookupUri()
を呼び出して、連絡先 ID とルックアップ キーを渡す必要があります。次の例は、URI を取得してカードを表示する方法を示しています。
Kotlin
// Query the content provider using the ENTERPRISE_CONTENT_FILTER_URI address. // We use the _ID and LOOKUP_KEY columns to generate a work-profile URI. val cursor = getContentResolver() .query( contentFilterUri, arrayOf(ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY), null, null ) // Show the contact details card in the work profile's Contacts app. The URI // must be created with getLookupUri(). cursor?.use { if (it.moveToFirst() == true) { val uri = ContactsContract.Contacts.getLookupUri(it.getLong(0), it.getString(1)) ContactsContract.QuickContact.showQuickContact( activity, Rect(20, 20, 100, 100), uri, ContactsContract.QuickContact.MODE_LARGE, null ) } }
Java
// Query the content provider using the ENTERPRISE_CONTENT_FILTER_URI address. // We use the _ID and LOOKUP_KEY columns to generate a work-profile URI. Cursor cursor = getContentResolver().query( contentFilterUri, new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY, }, null, null, null); if (cursor == null) { return; } // Show the contact details card in the work profile's Contacts app. The URI // must be created with getLookupUri(). try { if (cursor.moveToFirst() == true) { Uri uri = ContactsContract.Contacts.getLookupUri( cursor.getLong(0), cursor.getString(1)); ContactsContract.QuickContact.showQuickContact( getActivity(), new Rect(20, 20, 100, 100), uri, ContactsContract.QuickContact.MODE_LARGE, null); } } finally { cursor.close(); }
提供状況
次の表は、個人用プロファイルの仕事用プロファイルの連絡先データをサポートする Android バージョンをまとめたものです。
Android バージョン | サポート |
---|---|
5.0(API レベル 21) | PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI を使用して、電話番号の仕事用連絡先の名前を検索できます。 |
6.0(API レベル 23) | Email.ENTERPRISE_CONTENT_LOOKUP_URI を使用して、メールアドレスの仕事用連絡先の名前を検索します。 |
7.0(API レベル 24) | Contacts.ENTERPRISE_CONTENT_FILTER_URI を使用して仕事用ディレクトリにある仕事用の連絡先名をクエリします。Directory.ENTERPRISE_CONTENT_URI を使用して、仕事用プロファイルと個人用プロファイルのすべてのディレクトリを一覧表示します。 |
開発とテスト
仕事用プロファイルを作成する手順は次のとおりです。
- Test DPC アプリをインストールします。
- [Set up Test DPC] アプリを開きます(Test DPC アプリのアイコンではありません)。
- 画面の手順に沿って管理対象プロファイルを設定します。
- 仕事用プロファイルで連絡先アプリを開き、サンプルの連絡先をいくつか追加します。
仕事用プロファイルの連絡先へのアクセスをブロックする IT 管理者のシミュレーションを行う手順は次のとおりです。
- 仕事用プロファイルで Test DPC アプリを開きます。
- [クロス プロファイル連絡先検索を無効にする] 設定または [クロス プロファイル発信者番号を無効にする] 設定を検索します。
- 設定をオンに切り替えます。
仕事用プロファイルでアプリをテストする方法について詳しくは、仕事用プロファイルとの互換性をテストするをご覧ください。
参考情報
連絡先や仕事用プロファイルについて詳しくは、以下のリソースをご覧ください。
- 仕事用プロファイルには、仕事用プロファイルに関する他のおすすめの方法が記載されています。
- 連絡先のリストを取得するでは、アプリ内の連絡先を一覧表示するために必要な手順を確認��ます。
- 連絡先プロバイダ: 連絡先データベースの構造を説明します。