Wiadomości o usługach

Selektor kontaktów: udostępnianie kontaktów z zachowaniem prywatności

Czas czytania: 4 minuty
Roxanna Aliabadi Walker
Menedżer produktu

Prywatność i kontrola użytkownika pozostają najważniejszymi elementami Androida. Podobnie jak selektor zdjęć, który sprawił, że udostępnianie multimediów stało się bezpieczne i łatwe do wdrożenia, teraz zapewniamy ten sam poziom prywatności, prostoty i wygody w przypadku wyboru kontaktów.

Nowy standard ochrony prywatności kontaktów

W przeszłości aplikacje, które wymagały dostępu do kontaktów konkretnego użytkownika, korzystały z ogólnego uprawnienia READ_CONTACTS. Chociaż to podejście było funkcjonalne, często przyznawało aplikacjom więcej danych, niż było to konieczne. Nowy selektor kontaktów na Androidzie, wprowadzony w Androidzie 17, zmienia tę dynamikę, ponieważ zapewnia standardowy, bezpieczny i umożliwiający wyszukiwanie interfejs do wybierania kontaktów.

Ta funkcja pozwala użytkownikom przyznawać aplikacjom dostęp tylko do wybranych przez nich kontaktów, co jest zgodne z zaangażowaniem Androida w zapewnianie przejrzystości danych i minimalizowanie zakresu uprawnień.

picker.png
selection.png

Jak to działa

Programiści mogą zintegrować selektor kontaktów za pomocą intencji Intent.ACTION_PICK_CONTACTS. Zaktualizowany interfejs API oferuje kilka zaawansowanych funkcji:

  • Szczegółowe prośby o dane: aplikacje mogą określać, których pól potrzebują, np. numerów telefonów lub adresów e-mail, zamiast otrzymywać cały rekord kontaktu.
  • Obsługa wielokrotnego wyboru: selektor obsługuje wybór pojedynczych i wielu kontaktów, co zapewnia deweloperom większą elastyczność w przypadku funkcji takich jak zaproszenia grupowe.
  • Limity wyboru: deweloperzy mogą ustawić niestandardowe limity liczby kontaktów, które użytkownik może wybrać jednocześnie.
  • Dostęp tymczasowy: po wybraniu tej opcji system zwraca identyfikator URI sesji, który zapewnia tymczasowy dostęp do odczytu żądanych danych, dzięki czemu dostęp nie trwa dłużej niż jest to konieczne.
  • Dostęp do innych profili:  gdy użytkownik używa tego nowego zamiaru, interfejs umożliwia mu wybieranie treści z innych profili, takich jak profil służbowy, sklonowany profil lub przestrzeń prywatna.
  • Zoptymalizowana wydajność:  selektor kontaktów zwraca pojedynczy identyfikator URI, który umożliwia zbiorcze wysyłanie zapytań o wyniki, eliminując potrzebę oddzielnego wysyłania zapytań o poszczególne identyfikatory URI kontaktów, jak wymaga tego ACTION_PICK. Ta wydajność dodatkowo zmniejsza narzut systemowy dzięki wykorzystaniu pojedynczej transakcji Binder.

Zgodność wsteczna i wdrożenie

W przypadku urządzeń z Androidem 17 lub nowszym system automatycznie uaktualnia starsze intencje ACTION_PICK, które określają typy danych kontaktowych, do nowego, bezpieczniejszego interfejsu. Aby jednak w pełni korzystać z funkcji zaawansowanych, takich jak wielokrotny wybór, zachęcamy programistów do zaktualizowania kodu implementacji i używania ContentResolver do wysyłania zapytań o zwrócony identyfikator URI sesji.


Integracja selektora kontaktówAby zintegrować selektor kontaktów, deweloperzy używają intencji ACTION_PICK_CONTACTS. Poniżej znajdziesz przykład kodu pokazujący, jak uruchomić selektor i poprosić o określone pola danych, takie jak adres e-mail i numer telefonu.

// 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)

Gdy użytkownik dokona wyboru, aplikacja przetworzy wynik, wysyłając zapytanie do zwróconego identyfikatora URI sesji, aby wyodrębnić żądane informacje kontaktowe.

// 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
}

Pełną dokumentację znajdziesz tutaj.

Sprawdzone metody dla deweloperów

Aby zapewnić użytkownikom jak największą wygodę i utrzymać wysokie standardy bezpieczeństwa, zalecamy:

  • Minimalizacja danych: proś tylko o konkretne pola danych (np. adres e-mail), których potrzebuje Twoja aplikacja.
  • Natychmiastowe utrwalanie: natychmiastowe utrwalanie wybranych danych, ponieważ dostęp do identyfikatora URI sesji jest tymczasowy.

Czytaj dalej