Nouveautés produits

Sélecteur de contacts : partage de contacts axé sur la confidentialité

Temps de lecture : 4 min
Roxanna Aliabadi Walker
Responsable produit

La confidentialité et le contrôle par l'utilisateur restent au cœur de l'expérience Android. Tout comme le sélecteur de photos a rendu le partage de contenus multimédias sécurisé et facile à implémenter, nous apportons désormais le même niveau de confidentialité, de simplicité et d'expérience utilisateur de qualité à la sélection de contacts.

Une nouvelle norme pour la confidentialité des contacts

Auparavant, les applications nécessitant d'accéder aux contacts d'un utilisateur spécifique s'appuyaient sur l'autorisation étendue READ_CONTACTS. Bien que fonctionnelle, cette approche accordait souvent aux applications plus de données que nécessaire. Le nouveau sélecteur de contacts Android, introduit dans Android 17, modifie cette dynamique en fournissant une interface standardisée, sécurisée et permettant d'effectuer des recherches pour la sélection de contacts.

Cette fonctionnalité permet aux utilisateurs d'accorder aux applications l'accès uniquement aux contacts spécifiques qu'ils choisissent, conformément à l'engagement d'Android en faveur de la transparence des données et de la réduction des autorisations.

picker.png
selection.png

Fonctionnement

Les développeurs peuvent intégrer le sélecteur de contacts à l'aide de l'intent Intent.ACTION_PICK_CONTACTS. Cette API mise à jour offre plusieurs fonctionnalités puissantes :

  • Requêtes de données granulaires : les applications peuvent spécifier exactement les champs dont elles ont besoin, tels que les numéros de téléphone ou les adresses e-mail, au lieu de recevoir l'enregistrement de contact complet.
  • Prise en charge de la sélection multiple : le sélecteur prend en charge la sélection d'un ou de plusieurs contacts, ce qui offre aux développeurs plus de flexibilité pour des fonctionnalités telles que les invitations de groupe.
  • Limites de sélection : les développeurs peuvent définir des limites personnalisées sur le nombre de contacts qu'un utilisateur peut sélectionner à la fois.
  • Accès temporaire : lors de la sélection, le système renvoie un URI de session qui fournit un accès en lecture temporaire aux données demandées, ce qui garantit que l'accès ne persiste pas plus longtemps que nécessaire.
  • Accès à d'autres profils  : lorsque vous utilisez ce nouvel intent, l'interface permet aux utilisateurs de sélectionner du contenu à partir d'autres profils utilisateur, tels qu'un profil professionnel, un profil cloné ou un espace privé.
  • Performances optimisées  : le sélecteur de contacts renvoie un seul URI qui permet d'interroger collectivement les résultats, ce qui élimine la nécessité d'interroger séparément l'URI de chaque contact, comme l'exige ACTION_PICK. Cette efficacité réduit davantage la surcharge du système en utilisant une seule transaction Binder.

Rétrocompatibilité et implémentation

Pour les appareils équipés d'Android 17 ou d'une version ultérieure, le système met automatiquement à niveau les anciens intents ACTION_PICK qui spécifient des types de données de contact vers la nouvelle interface plus sécurisée. Toutefois, pour profiter pleinement des fonctionnalités avancées telles que la sélection multiple, les développeurs sont encouragés à mettre à jour leur code d'implémentation et à utiliser ContentResolver pour interroger l'URI de session renvoyé.


Intégrer le sélecteur de contactsPour intégrer le sélecteur de contacts, les développeurs utilisent l'intent ACTION_PICK_CONTACTS. Vous trouverez ci-dessous un exemple de code montrant comment lancer le sélecteur et demander des champs de données spécifiques, tels que l'adresse e-mail et le numéro de téléphone.

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

Une fois que l'utilisateur a effectué une sélection, l'application traite le résultat en interrogeant l'URI de session renvoyé pour extraire les informations de contact demandées.

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

Consultez la documentation complète ici.

Bonnes pratiques pour les développeurs

Pour offrir la meilleure expérience utilisateur possible et maintenir des normes de sécurité élevées, nous vous recommandons de suivre les quelques règles ci-dessous :

  • Minimisation des données : ne demandez que les champs de données spécifiques (par exemple, l'adresse e-mail) dont votre application a besoin.
  • Persistance immédiate : conservez immédiatement les données sélectionnées, car l'accès à l'URI de session est temporaire.

Lire la suite