Novedades de productos

Selector de contactos: Uso compartido de contactos con prioridad para la privacidad

Lectura de 4 min
Roxanna Aliabadi Walker
Gerente de producto

La privacidad y el control del usuario siguen siendo el centro de la experiencia de Android. Así como el selector de fotos hizo que el uso compartido de contenido multimedia fuera seguro y fácil de implementar, ahora brindamos ese mismo nivel de privacidad, simplicidad y excelente experiencia del usuario a la selección de contactos.

Un nuevo estándar para la privacidad de los contactos

Históricamente, las aplicaciones que requerían acceso a los contactos de un usuario específico dependían del permiso amplio READ_CONTACTS. Si bien es funcional, este enfoque a menudo otorgaba a las apps más datos de los necesarios. El nuevo selector de contactos de Android, que se introdujo en Android 17, cambia esta dinámica al proporcionar una interfaz estandarizada, segura y con capacidad de búsqueda para la selección de contactos.

Esta función permite a los usuarios otorgar a las apps acceso solo a los contactos específicos que elijan, lo que se alinea con el compromiso de Android con la transparencia de los datos y las huellas de permisos minimizadas.

picker.png
selection.png

Cómo funciona

Los desarrolladores pueden integrar el selector de contactos con el intent Intent.ACTION_PICK_CONTACTS. Esta API actualizada ofrece varias capacidades potentes:

  • Solicitudes de datos detalladas: Las apps pueden especificar exactamente qué campos necesitan, como números de teléfono o direcciones de correo electrónico, en lugar de recibir el registro de contacto completo.
  • Compatibilidad con la selección múltiple: El selector admite selecciones de contactos únicas y múltiples, lo que brinda a los desarrolladores más flexibilidad para funciones como las invitaciones grupales.
  • Límites de selección: Los desarrolladores pueden establecer límites personalizados en la cantidad de contactos que un usuario puede seleccionar a la vez.
  • Acceso temporal: Cuando se selecciona, el sistema muestra un URI de sesión que proporciona acceso de lectura temporal a los datos solicitados, lo que garantiza que el acceso no persista más de lo necesario.
  • Acceso a otros perfiles: Cuando se usa este nuevo intent, la interfaz permitirá a los usuarios seleccionar contenido de otros perfiles de usuario, como un perfil de trabajo, un perfil clonado o un espacio privado.
  • Rendimiento optimizado: El selector de contactos muestra un solo URI que permite realizar consultas colectivas de resultados, lo que elimina la necesidad de consultar el URI de contacto individual por separado, como lo requiere ACTION_PICK. Esta eficiencia reduce aún más la sobrecarga del sistema mediante el uso de una sola transacción Binder.

Retrocompatibilidad e implementación

En los dispositivos que ejecutan Android 17 o versiones posteriores, el sistema actualiza automáticamente los intents ACTION_PICK heredados que especifican tipos de datos de contacto a la interfaz nueva y más segura. Sin embargo, para aprovechar al máximo las funciones avanzadas, como la selección múltiple, se recomienda a los desarrolladores que actualicen su código de implementación y utilicen ContentResolver para consultar el URI de sesión que se muestra.


Integra el selector de contactos. Para integrar el selector de contactos, los desarrolladores usan el intent ACTION_PICK_CONTACTS. A continuación, se muestra un ejemplo de código que demuestra cómo iniciar el selector y solicitar campos de datos específicos, como el correo electrónico y los números de teléfono.

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

Después de que el usuario realiza una selección, la app procesa el resultado consultando el URI de sesión que se muestra para extraer la información de contacto solicitada.

// 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 documentación completa aquí.

Prácticas recomendadas para desarrolladores

Para proporcionar la mejor experiencia del usuario y mantener altos estándares de seguridad, te recomendamos lo siguiente:

  • Minimización de datos: Solicita solo los campos de datos específicos (p.ej., correo electrónico) que necesita tu app.
  • Persistencia inmediata: Conserva los datos seleccionados de inmediato, ya que el acceso al URI de sesión es temporal.
Escrito por:

Seguir leyendo