Novità sul prodotto

Selettore di contatti: condivisione dei contatti nel rispetto della privacy

Lettura di 4 minuti
Roxanna Aliabadi Walker
Product Manager

La privacy e il controllo degli utenti rimangono al centro dell'esperienza Android. Proprio come il selettore di foto ha reso la condivisione di contenuti multimediali sicura e facile da implementare, ora stiamo portando lo stesso livello di privacy, semplicità e ottima esperienza utente alla selezione dei contatti.

Un nuovo standard per la privacy dei contatti

In passato, le applicazioni che richiedevano l'accesso ai contatti di un utente specifico si basavano sull'autorizzazione generica READ_CONTACTS. Sebbene funzionale, questo approccio spesso concedeva alle app più dati del necessario. Il nuovo selettore di contatti Android, introdotto in Android 17, cambia questa dinamica fornendo un'interfaccia standardizzata, sicura e ricercabile per la selezione dei contatti.

Questa funzionalità consente agli utenti di concedere alle app l'accesso solo ai contatti specifici che scelgono, in linea con l'impegno di Android per la trasparenza dei dati e le impronte delle autorizzazioni ridotte al minimo.

picker.png
selection.png

Come funziona

Gli sviluppatori possono integrare il Selettore di contatti utilizzando l'intent Intent.ACTION_PICK_CONTACTS. Questa API aggiornata offre diverse funzionalità avanzate:

  • Richieste di dati granulari:le app possono specificare esattamente i campi di cui hanno bisogno, ad esempio numeri di telefono o indirizzi email, anziché ricevere l'intero record del contatto.
  • Supporto della selezione multipla:il selettore supporta la selezione di uno o più contatti, offrendo agli sviluppatori maggiore flessibilità per funzionalità come gli inviti di gruppo.
  • Limiti di selezione:gli sviluppatori possono impostare limiti personalizzati al numero di contatti che un utente può selezionare contemporaneamente.
  • Accesso temporaneo:una volta selezionato, il sistema restituisce un URI di sessione che fornisce l'accesso in lettura temporaneo ai dati richiesti, garantendo che l'accesso non persista più del necessario.
  • Accesso ad altri profili: quando utilizzi questo nuovo intent, l'interfaccia consente agli utenti di selezionare contenuti da altri profili utente, ad esempio un profilo di lavoro, un profilo clonato o uno spazio privato.
  • Rendimento ottimizzato : il selettore di contatti restituisce un singolo URI che consente di eseguire query collettive sui risultati, eliminando la necessità di eseguire query individuali sugli URI dei contatti separatamente, come richiesto da ACTION_PICK. Questa efficienza riduce ulteriormente l'overhead del sistema utilizzando una singola transazione Binder.

Compatibilità con le versioni precedenti e implementazione

Per i dispositivi con Android 17 o versioni successive, il sistema esegue automaticamente l'upgrade degli intent ACTION_PICK legacy che specificano i tipi di dati di contatto alla nuova interfaccia più sicura. Tuttavia, per sfruttare appieno le funzionalità avanzate come la selezione multipla, gli sviluppatori sono invitati ad aggiornare il codice di implementazione e utilizzare ContentResolver per eseguire query sull'URI della sessione restituito.


Integra il selettore di contattiPer integrare il selettore di contatti, gli sviluppatori utilizzano l'intent ACTION_PICK_CONTACTS. Di seguito è riportato un esempio di codice che mostra come avviare il selettore e richiedere campi di dati specifici, come email e numeri di telefono.

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

Dopo che l'utente ha effettuato una selezione, l'app elabora il risultato eseguendo una query sull'URI della sessione restituito per estrarre i dati di contatto richiesti.

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

Consulta la documentazione completa qui.

Best practice per gli sviluppatori

Per offrire la migliore esperienza utente e mantenere elevati standard di sicurezza, ti consigliamo di:

  • Minimizzazione dei dati: richiedi solo i campi di dati specifici (ad es. email) di cui ha bisogno la tua app.
  • Persistenza immediata:mantieni i dati selezionati immediatamente, poiché l'accesso all'URI della sessione è temporaneo.

Continua a leggere