這份開發人員指南將說明如何強化應用程式的使用聯絡人功能 工作資料夾中的資料如果您尚未使用 Android 的 Contact API 前,請參閱「聯絡人供應商」一文,熟悉 並嚴謹測試及提升 API 的公平性後 我們才能放心地推出 API
總覽
設有工作資料夾的裝置會將聯絡人儲存在不同的本機中 工作和個人資料夾的目錄根據預設 這個個人資料夾不會顯示工作聯絡人,不過,應用程式 存取工作資料夾中的聯絡人資訊。例如: 這是 Google 的 Android 聯絡人應用程式,當中顯示 搜尋結果中的工作目錄聯絡人。
使用者通常會希望使用個人裝置和應用程式工作。使用 工作資料夾聯絡人,您的應用程式可能會成為使用者工作日的一部分。
使用者體驗
請思考應用程式如何顯示工作資料夾中的聯絡資訊。 最佳做法取決於應用程式的性質和使用者開發的原因 但請考慮以下幾點:
- 應用程式是否預設包含工作資料夾聯絡人,或應讓使用者 選用功能?
- 混合或分開工作和個人資料夾聯絡人對 如何影響使用者的體驗?
- 不小心輕觸工作資料夾聯絡人會有什麼影響?
- 工作資料夾中的聯絡人登出後,會對應用程式介面造成什麼影響 嗎?
應用程式應明確顯示工作資料夾聯絡人。或許可以徽章 並使用熟悉的工作圖示 (例如公事包) 進行聯繫。
例如,Google 聯絡人應用程式 (如圖 1 所示) 會執行以下動作: 列出工作資料夾和個人資料夾的聯絡人:
- 在清單中插入子標題,藉此區分工作和個人區段。
- 標記與公事包圖示代表聯絡人。
- 輕觸時在工作資料夾中開啟工作聯絡人。
如果裝置使用者關閉工作資料夾,您的應用程式就無法 透過工作資料夾或機構的遠端查詢聯絡資訊 聯絡人目錄視工作資料夾聯絡人的方式而定,你可以 將您的聯絡人設為靜音,或者您可能需要停用使用者介面 控制項
權限
如果應用程式已有使用者的聯絡人,您就會知道
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
。
查詢這個網址會先搜尋個人聯絡人,以便執行完全比對。如果
供應商未比對到任何個人聯絡人,之後供應商會搜尋
是可能就要比對的工作聯絡人Android 6.0 (API 級別 23) 中有這個 URI
或更高版本。
查詢電子郵件地址聯絡資訊的方法如下:
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(); }
顯示工作聯絡人
在個人資料夾中執行的應用程式可在工作資料夾中顯示聯絡人卡片。
致電
ContactsContract.QuickContact.showQuickContact()
英吋
Android 5.0 以上版本:啟動工作資料夾中的「聯絡人」應用程式,並顯示
聯絡人卡片。
如要產生工作資料夾的正確 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 列出工作資料夾和個人資料夾中的所有目錄。 |
開發和測試
如要建立工作資料夾,請按照下列步驟操作:
- 安裝測試 DPC 應用程式。
- 開啟「設定測試 DPC」應用程式 (而非「測試 DPC」應用程式圖示)。
- 按照畫面上的指示設定受管理的設定檔。
- 在工作資料夾中開啟「聯絡人」應用程式,並新增一些聯絡人範例。
如要模擬 IT 管理員封鎖工作資料夾聯絡人存取權,請按照下列步驟操作:
- 在工作資料夾中開啟「測試 DPC」應用程式。
- 搜尋「停用跨設定檔聯絡人搜尋功能」設定,或是 停用跨設定檔來電顯示設定。
- 將設定切換為「開啟」。
如要進一步瞭解如何使用工作資料夾測試應用程式,請參閱「測試應用程式: 與工作資料夾的相容性。
其他資源
如要進一步瞭解聯絡人或工作資料夾,請參閱以下資源: