Новости о продуктах

Инструмент выбора контактов: конфиденциальность на первом месте

Время на чтение: 4 минуты
Посмотреть профиль пользователя Roxanna Aliabadi Walker
Roxanna Aliabadi Walker Менеджер по продукту, Android

Конфиденциальность и управление данными остаются в центре внимания Android. Как и в случае с окном выбора фотографий, мы стремимся сделать выбор контактов безопасным, простым и удобным.

Новый стандарт конфиденциальности контактных данных

Раньше приложения, которым требовался доступ к контактам определенного пользователя, полагались на разрешение READ_CONTACTS. Хотя этот подход и работал, приложения часто получали больше данных, чем им было нужно. Новый инструмент выбора контактов Android, представленный в Android 17, меняет эту динамику, предоставляя стандартизированный и безопасный интерфейс с возможностью поиска для выбора контактов.

Эта функция позволяет пользователям предоставлять приложениям доступ только к выбранным контактам, что соответствует принципам прозрачности данных и минимизации разрешений в Android.

picker.png
selection.png

Как это работает

Разработчики могут интегрировать инструмент выбора контактов с помощью намерения Intent.ACTION_PICK_CONTACTS. Обновленный API предлагает несколько мощных возможностей:

  • Запросы на получение определенных данных. Приложения могут запрашивать только нужные им поля, например номера телефонов или адреса электронной почты, а не всю запись контакта.
  • Поддержка множественного выбора. Инструмент позволяет выбирать как один, так и несколько контактов, что дает разработчикам больше гибкости при создании таких функций, как групповые приглашения.
  • Ограничения на выбор. Разработчики могут задавать ограничения на количество контактов, которые пользователь может выбрать за один раз.
  • Временный доступ. При выборе этого варианта система возвращает URI сеанса, который предоставляет временный доступ на чтение к запрошенным данным, гарантируя, что доступ не будет сохраняться дольше, чем необходимо.
  • Доступ к другим профилям.  При использовании нового намерения интерфейс позволит пользователям выбирать контент из других профилей, например рабочего, клонированного или частного пространства.
  • Оптимизированная производительность.  Инструмент выбора контактов возвращает один URI, который позволяет запрашивать коллективные результаты. Это устраняет необходимость запрашивать отдельные URI контактов, как в случае с ACTION_PICK. Это позволяет снизить нагрузку на систему, поскольку используется только одна транзакция 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 сеанса предоставляется временно.
Автор:
Продолжить чтение