Инструмент выбора контактов

Выбор контактов Android – это стандартизированный интерфейс, который позволяет пользователям делиться контактами с вашим приложением. Он доступен на устройствах с Android 17 (уровень API 37) или более поздней версией и обеспечивает конфиденциальность, в отличие от разрешения READ_CONTACTS. Вместо того чтобы запрашивать доступ ко всей адресной книге пользователя, приложение указывает нужные ему поля данных, например номера телефонов или адреса электронной почты, а пользователь выбирает контакты, которыми хочет поделиться. Это дает вашему приложению доступ на чтение только к выбранным данным, обеспечивая детальный контроль и предоставляя пользователям единообразный опыт работы со встроенным поиском, переключением профилей и возможностями множественного выбора без необходимости создавать или поддерживать пользовательский интерфейс.

Как интегрировать инструмент выбора контактов

Чтобы интегрировать инструмент выбора контактов, используйте интент ContactsPickerSessionContract.ACTION_PICK_CONTACTS. Оно запускает инструмент и предоставляет приложению доступ к выбранным контактам.

В отличие от устаревшего намерения ACTION_PICK, инструмент выбора контактов позволяет одновременно указать несколько полей данных, необходимых вашему приложению. Для этого используется метод ContactsPickerSessionContract.EXTRA_REQUESTED_DATA_FIELDS, которому передается массив ArrayList<String> MIME-типов, определенных в ContactsContract.CommonDataKinds.

Примеры стандартных MIME-типов:

  • ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
  • ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
  • ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE

Запустите инструмент выбора.

Чтобы запустить средство выбора, используйте registerForActivityResult с контрактом StartActivityForResult. Вы можете настроить намерение так, чтобы разрешить выбор одного или нескольких элементов.

// 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 to fetch all selected contacts
        coroutine.launch {
            contacts = processContactPickerResultUri(resultUri, context)
        }
    }
}

Режим выбора

Интерфейс инструмента выбора контактов зависит от запрашиваемых полей данных. В зависимости от этих требований пользователи могут выбрать всю запись контакта, если требуется несколько полей, или выбрать определенные элементы данных из информации о контакте.

Разные режимы интерфейса инструмента выбора контактов
Рисунок 1. Интерфейс инструмента выбора контактов адаптируется к запрашиваемым полям данных (выбор одного контакта, нескольких контактов или нескольких номеров телефонов).

Выбрать один контакт

В этом примере приложение запрашивает только номера телефонов. В окне выбора будет отфильтрован список, чтобы показать только контакты с номерами телефонов, и пользователь сможет выбрать конкретный номер.

// 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_USE_SYSTEM_CONTACTS_PICKER, true)
    putStringArrayListExtra(
        EXTRA_PICK_CONTACTS_REQUESTED_DATA_FIELDS,
        requestedFields
    )
}

// Launch the picker
pickContact.launch(pickContactIntent)

Выберите несколько контактов.

Чтобы включить множественный выбор, добавьте дополнительный параметр Intent.EXTRA_ALLOW_MULTIPLE. При необходимости вы можете ограничить количество элементов, которые может выбрать пользователь.

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_USE_SYSTEM_CONTACTS_PICKER, true)
    // Enable multi-select
    putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
    // Set limit of selectable contacts
    putExtra(EXTRA_PICK_CONTACTS_SELECTION_LIMIT, 5)
    // Define the specific contact data fields you need
    putStringArrayListExtra(
        EXTRA_PICK_CONTACTS_REQUESTED_DATA_FIELDS,
        requestedFields
    )
    // Enable this option to only filter contacts that have all the requested data fields
    putExtra(EXTRA_PICK_CONTACTS_MATCH_ALL_DATA_FIELDS, false)
}

// Launch the picker
pickContact.launch(pickContactIntent)

Обработка результатов

Когда пользователь завершает выбор, система возвращает RESULT_OK и URI сеанса. Этот URI предоставляет временный доступ к выбранным данным.

Вы можете запросить этот URI, используя стандартный ContentResolver. Результирующий объект Cursor содержит запрошенные поля данных и соответствует схеме ContactsContract.Data.

// Data class representing a parsed Contact with selected details.
data class Contact(
    val lookupKey: String,
    val name: String,
    val emails: List<String>,
    val phones: List<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.LOOKUP_KEY,
        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)
    )

    // We use `LOOKUP_KEY` as a unique ID to aggregate all contact info related to a same person
    val contactsMap = mutableMapOf<String, Contact>()

    // Note: The Contact Picker Session Uri doesn't support custom selection & selectionArgs.
    // We query the URI directly to get the results chosen by the user.
    context.contentResolver.query(sessionUri, projection, null, null, null)?.use { cursor ->
        // Get the column indices for our requested projection
        val lookupKeyIdx = cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)
        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 lookupKey = cursor.getString(lookupKeyIdx)
            val mimeType = cursor.getString(mimeTypeIdx)
            val name = cursor.getString(nameIdx) ?: ""
            val data1 = cursor.getString(data1Idx) ?: ""

            val email = if (mimeType == Email.CONTENT_ITEM_TYPE) data1 else null
            val phone = if (mimeType == Phone.CONTENT_ITEM_TYPE) data1 else null

            val existingContact = contactsMap[lookupKey]
            if (existingContact != null) {
                contactsMap[lookupKey] = existingContact.copy(
                    emails = if (email != null) existingContact.emails + email else existingContact.emails,
                    phones = if (phone != null) existingContact.phones + phone else existingContact.phones
                )
            } else {
                contactsMap[lookupKey] = Contact(
                    lookupKey = lookupKey,
                    name = name,
                    emails = if (email != null) listOf(email) else emptyList(),
                    phones = if (phone != null) listOf(phone) else emptyList()
                )
            }
        }
    }

    return@withContext contactsMap.values.toList()
}

Обратная совместимость

В приложениях, предназначенных для Android 17 (уровень API 37) и более поздних версий, существующий интент Intent.ACTION_PICK обновится автоматически для использования нового интерфейса инструмента выбора контактов.

Если в вашем приложении уже используется ACTION_PICK, вам не нужно менять код, чтобы получить новый интерфейс. Однако, чтобы воспользоваться новыми функциями, такими как получение одного Uri для запроса контактных данных, переключение между личным и рабочим профилями или запросы нескольких полей данных, вам необходимо обновить реализацию, чтобы использовать ContactsPickerSessionContract.ACTION_PICK_CONTACTS или новые дополнительные параметры интента.

Тестирование на более старых целевых SDK

Вы можете протестировать новое поведение инструмента выбора на устройствах с Android 17 и более поздними версиями, даже если ваше приложение предназначено для более ранней версии SDK. Для этого добавьте логическое дополнение EXTRA_USE_SYSTEM_CONTACTS_PICKER в намерение ACTION_PICK.

Рекомендации

  • Запрашивайте только необходимые разрешения. Если приложению нужно только отправлять SMS, запросите разрешение Phone.CONTENT_ITEM_TYPE. Средство выбора автоматически отфильтрует контакты, у которых нет номеров телефонов, что приведет к более чистому пользовательскому интерфейсу для пользователя.
  • Управление несколькими записями данных для одного контакта. У отдельных контактов часто есть несколько адресов электронной почты или номеров телефонов. Чтобы пользователю было проще разобраться в них, рекомендуем сгруппировать их с помощью ContactsContract.Contacts.LOOKUP_KEY. Кроме того, вы можете получать определенные ярлыки для каждой записи (например, "рабочий" или "личный"), чтобы предлагать более детализированные варианты выбора в интерфейсе приложения.
  • Немедленно сохраняйте данные. URI сеанса предоставляет временное разрешение на чтение. Если вам нужно получить доступ к этой контактной информации позже (после завершения процесса приложения), ваше приложение должно сохранить контактные данные.
  • Не полагайтесь на данные в аккаунте. Чтобы защитить конфиденциальность пользователя и предотвратить создание цифровых отпечатков, метаданные аккаунта удаляются из результатов.