Novedades sobre productos

Selector de contactos: Compartir contactos con prioridad en la privacidad

Lectura de 4 min
Roxanna Aliabadi Walker
Gerente de producto

La privacidad y el control del usuario siguen siendo el eje central 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 el mismo nivel de privacidad, simplicidad y excelente experiencia del usuario a la selección de contactos.

Un nuevo estándar para la privacidad de la información de contacto

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 este enfoque era funcional, 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, ya que proporciona 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 la minimización de la huella de permisos.

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 funciones potentes:

  • Solicitudes de datos detallados: 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 la selección de uno o varios contactos, 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 esta opción, el sistema devuelve 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 esta nueva intención, la interfaz permitirá que los usuarios seleccionen contenido de otros perfiles de usuario, como un perfil de trabajo, un perfil clonado o un espacio privado.
  • Rendimiento optimizado: El selector de contactos devuelve un solo URI que permite consultar resultados de forma colectiva, lo que elimina la necesidad de consultar el URI de cada contacto por separado, como lo requiere ACTION_PICK. Esta eficiencia reduce aún más la sobrecarga del sistema, ya que utiliza una sola transacción de Binder.

Retrocompatibilidad y su implementación

En el caso de 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 nueva interfaz 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 devuelto.


Integra el selector de contactosPara 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 correos electrónicos y 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 devuelto 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., el 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