Nowości dotyczące produktów

Selektor kontaktów: udostępnianie kontaktów z myślą o 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 wprowadzamy ten sam poziom prywatności, prostoty i wygody użytkownika w przypadku wybierania kontaktów.

Nowy standard prywatności kontaktów

W przeszłości aplikacje wymagające dostępu do kontaktów konkretnego użytkownika korzystały z szerokiego 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ę, udostępniając 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 zobowiązaniem Androida do zapewnienia przejrzystości danych i zminimalizowania zakresu uprawnień.

picker.png
selection.png

Jak to działa

Deweloperzy mogą zintegrować selektor kontaktów za pomocą intencji Intent.ACTION_PICK_CONTACTS. Ten zaktualizowany interfejs API oferuje kilka zaawansowanych funkcji:

  • Szczegółowe żądania danych: aplikacje mogą określać dokładnie, które pola są im potrzebne, np. numery telefonów lub adresy e-mail, zamiast otrzymywać cały rekord kontaktu.
  • Obsługa wielokrotnego wyboru: selektor obsługuje wybieranie pojedynczych i wielu kontaktów, co daje deweloperom większą elastyczność w przypadku funkcji takich jak zaproszenia do grupy.
  • Limity wyboru: deweloperzy mogą ustawiać niestandardowe limity liczby kontaktów, które użytkownik może wybrać jednocześnie.
  • Dostęp tymczasowy: Po wybraniu 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ż to konieczne.
  • Dostęp do innych profili:  gdy użytkownik używa tej nowej intencji, interfejs umożliwia mu wybieranie treści z innych profili, np. profilu służbowego, sklonowanego profilu lub przestrzeni prywatnej.
  • Zoptymalizowana wydajność:  selektor kontaktów zwraca pojedynczy identyfikator URI, który umożliwia zbiorcze wysyłanie zapytań o wyniki, co eliminuje konieczność wysyłania zapytań o poszczególne identyfikatory URI kontaktów oddzielnie, jak to jest wymagane w przypadku ACTION_PICK. Ta wydajność dodatkowo zmniejsza obciążenie systemu dzięki wykorzystaniu pojedynczej transakcji Binder.

Zgodność wsteczna i implementacja

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 wykorzystać funkcje zaawansowane, takie jak wielokrotny wybór, deweloperzy powinni zaktualizować kod implementacji i użyć 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, który pokazuje, jak uruchomić selektor i poprosić o konkretne pola danych, takie jak adres e-mail i numery telefonów.

// 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 przetwarza wynik, wysyłając zapytanie o zwrócony identyfikator 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 najlepsze wrażenia i utrzymać wysokie standardy bezpieczeństwa, zalecamy:

  • Minimalizację danych: proś tylko o te pola danych (np. adres e-mail), których potrzebuje Twoja aplikacja.
  • Natychmiastowe utrwalanie: utrwalaj wybrane dane natychmiast, ponieważ dostęp do identyfikatora URI sesji jest tymczasowy.

Czytaj dalej