產品新訊

聯絡人選擇工具:以隱私權為優先考量的聯絡人分享功能

4 分鐘閱讀
Roxanna Aliabadi Walker
產品經理

隱私權和使用者控制權仍是 Android 體驗的核心。就像相片挑選工具讓媒體分享功能安全又容易實作一樣,我們現在也為聯絡人選取功能帶來同等程度的隱私權、簡便性和優質使用者體驗。

聯絡人隱私權的新標準

過去,如要存取特定使用者的聯絡人,應用程式必須取得廣泛的 READ_CONTACTS 權限。雖然這種做法可行,但通常會授予應用程式過多的資料存取權。Android 17 推出的全新 Android 聯絡人選擇工具提供標準化、安全且可搜尋的聯絡人選擇介面,改變了這種動態。

這項功能可讓使用者只授予應用程式存取所選聯絡人的權限,符合 Android 對資料資訊公開和盡量減少權限足跡的承諾。

picker.png
selection.png

運作方式

開發人員可以使用 Intent.ACTION_PICK_CONTACTS 意圖整合聯絡人選擇工具。更新後的 API 提供多項強大功能:

  • 精細資料要求:應用程式可以指定需要的確切欄位,例如電話號碼或電子郵件地址,不必接收整個聯絡人記錄。
  • 支援多重選取:選擇工具支援單一和多個聯絡人選取作業,讓開發人員能更靈活地運用群組邀請等功能。
  • 選取限制:開發人員可以自訂使用者一次可選取的聯絡人數上限。
  • 臨時存取權:選取後,系統會傳回工作階段 URI,提供所要求資料的臨時讀取權限,確保存取權不會持續超過必要時間。
  • 存取其他設定檔: 使用這項新意圖時,介面會允許使用者從其他使用者設定檔 (例如工作資料夾、複製的設定檔或私人空間) 選取內容。
  • 效能最佳化: 聯絡人選擇工具會傳回單一 Uri,可供查詢集合結果,因此不必像 ACTION_PICK 一樣個別查詢聯絡人 Uri。這項效率可利用單一 Binder 交易,進一步減少系統負擔。

回溯相容性和實作方式

如果裝置搭載 Android 17 以上版本,系統會自動將指定聯絡人資料類型的舊版 ACTION_PICK 意圖升級為新的安全介面。不過,為充分運用多重選取等進階功能,建議開發人員更新實作程式碼,並使用 ContentResolver 查詢傳回的工作階段 URI。


整合聯絡人選擇工具:如要整合聯絡人選擇工具,開發人員可以使用 ACTION_PICK_CONTACTS 意圖。以下程式碼範例說明如何啟動挑選器,並要求特定資料欄位,例如電子郵件地址和電話號碼。

// State to hold the list of selected contacts
var contacts by remember { mutableStateOf<List>(emptyList()) }
// Launcher for the Contact Picker intent
val pickContact = rememberLauncherForActivityResult(StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val resultUri = it.data?.data ?: return@rememberLauncherForActivityResult
    // Process the result URI in a background thread
    coroutine.launch {
        contacts = processContactPickerResultUri(resultUri, context)
    }
}
}
// Define the specific contact data fields you need
val requestedFields = arrayListOf(
Email.CONTENT_ITEM_TYPE,
Phone.CONTENT_ITEM_TYPE,
)
// Set up the intent for the Contact Picker
val pickContactIntent = Intent(ACTION_PICK_CONTACTS).apply {
putExtra(EXTRA_PICK_CONTACTS_SELECTION_LIMIT, 5)
putStringArrayListExtra(
EXTRA_PICK_CONTACTS_REQUESTED_DATA_FIELDS,
requestedFields
)
putExtra(EXTRA_PICK_CONTACTS_MATCH_ALL_DATA_FIELDS, false)
}
// Launch the picker
pickContact.launch(pickContactIntent)

使用者選取要開啟的檔案後,應用程式會查詢傳回的工作階段 URI,擷取所要求的聯絡資訊,藉此處理結果。

// Data class representing a parsed Contact with selected details
data class Contact(val id: String, val name: String, val email: String?, val phone: String?)

// Helper function to query the content resolver with the URI returned by the Contact Picker.
// Parses the cursor to extract contact details such as name, email, and phone number
private suspend fun processContactPickerResultUri(
    sessionUri: Uri,
    context: Context
): List<Contact> = withContext(Dispatchers.IO) {
    // Define the columns we want to retrieve from the ContactPicker ContentProvider
    val projection = arrayOf(
        ContactsContract.Contacts._ID,
        ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
        ContactsContract.Data.MIMETYPE, // Type of data (e.g., email or phone)
        ContactsContract.Data.DATA1, // The actual data (Phone number / Email string)
    )

    val results = mutableListOf<Contact>()

    // Note: The Contact Picker Session Uri doesn't support custom selection & selectionArgs.
    context.contentResolver.query(sessionUri, projection, null, null, null)?.use { cursor ->
        // Get the column indices for our requested projection
        val contactIdIdx = cursor.getColumnIndex(ContactsContract.Contacts._ID)
        val mimeTypeIdx = cursor.getColumnIndex(ContactsContract.Data.MIMETYPE)
        val nameIdx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)
        val data1Idx = cursor.getColumnIndex(ContactsContract.Data.DATA1)

        while (cursor.moveToNext()) {
            val contactId = cursor.getString(contactIdIdx)
            val mimeType = cursor.getString(mimeTypeIdx)
            val name = cursor.getString(nameIdx) ?: ""
            val data1 = cursor.getString(data1Idx) ?: ""

            // Determine if the current row represents an email or a phone number
            val email = if (mimeType == Email.CONTENT_ITEM_TYPE) data1 else null
            val phone = if (mimeType == Phone.CONTENT_ITEM_TYPE) data1 else null

            // Add the parsed contact to our results list
            results.add(Contact(contactId, name, email, phone))
        }
    }

    return@withContext results
}

如要查看完整說明文件,請按這裡

開發人員最佳做法

為提供最佳使用者體驗並維持高安全標準,我們建議採取下列行動:

  • 資料最小化:只要求應用程式所需的特定資料欄位 (例如電子郵件)。
  • 立即保留:由於工作階段 URI 存取權是暫時的,因此請立即保留所選資料。

繼續閱讀