Novidades sobre produtos

Seletor de contatos: compartilhamento de contatos com foco na privacidade

Leitura de 4 minutos
Roxanna Aliabadi Walker
Gerente de produtos

A privacidade e o controle do usuário continuam sendo o foco da experiência do Android. Assim como o seletor de fotos tornou o compartilhamento de mídia seguro e fácil de implementar, agora estamos trazendo o mesmo nível de privacidade, simplicidade e ótima experiência do usuário para a seleção de contatos.

Um novo padrão para a privacidade de contatos

Historicamente, os aplicativos que exigiam acesso aos contatos de um usuário específico dependiam da permissão READ_CONTACTS. Embora funcional, essa abordagem geralmente concedia aos apps mais dados do que o necessário. O novo seletor de contatos do Android, introduzido no Android 17, muda essa dinâmica, fornecendo uma interface padronizada, segura e pesquisável para a seleção de contatos.

Esse recurso permite que os usuários concedam aos apps acesso apenas aos contatos específicos que escolherem, alinhando-se ao compromisso do Android com a transparência de dados e a minimização das permissões.

picker.png
selection.png

Como funciona

Os desenvolvedores podem integrar o seletor de contatos usando a intent Intent.ACTION_PICK_CONTACTS. Essa API atualizada oferece vários recursos avançados:

  • Solicitações de dados granulares:os apps podem especificar exatamente quais campos precisam, como números de telefone ou endereços de e-mail, em vez de receber o registro de contato inteiro.
  • Suporte à seleção múltipla:o seletor oferece suporte a seleções de contato únicas e múltiplas, oferecendo aos desenvolvedores mais flexibilidade para recursos como convites de grupo.
  • Limites de seleção:os desenvolvedores podem definir limites personalizados para o número de contatos que um usuário pode selecionar de uma só vez.
  • Acesso temporário:após a seleção, o sistema retorna um URI de sessão que fornece acesso de leitura temporário aos dados solicitados, garantindo que o acesso não persista por mais tempo do que o necessário.
  • Acesso a outros perfis : ao usar essa nova intent, a interface vai permitir que os usuários selecionem conteúdo de outros perfis de usuário, como um perfil de trabalho, um perfil clonado ou um espaço particular.
  • Performance otimizada: O seletor de contatos retorna um único URI que permite a consulta de resultados coletivos, eliminando a necessidade de consultar o URI de contato individual separadamente, conforme exigido por ACTION_PICK. Essa eficiência reduz ainda mais a sobrecarga do sistema usando uma única transação Binder.

Compatibilidade com versões anteriores e implementação

Para dispositivos com o Android 17 ou mais recente, o sistema atualiza automaticamente as intents ACTION_PICK legadas que especificam tipos de dados de contato para a nova interface mais segura. No entanto, para aproveitar ao máximo os recursos avançados, como a seleção múltipla, os desenvolvedores são incentivados a atualizar o código de implementação e usar o ContentResolver para consultar o URI de sessão retornado.


Integrar o seletor de contatosPara integrar o seletor de contatos, os desenvolvedores usam a intent ACTION_PICK_CONTACTS. Confira abaixo um exemplo de código que demonstra como iniciar o seletor e solicitar campos de dados específicos, como e-mail e números de telefone.

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

Depois que o usuário faz uma seleção, o app processa o resultado consultando o URI de sessão retornado para extrair as informações de contato solicitadas.

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

Confira a documentação completa aqui.

Práticas recomendadas para desenvolvedores

Para oferecer a melhor experiência do usuário e manter altos padrões de segurança, recomendamos o seguinte:

  • Minimização de dados:solicite apenas os campos de dados específicos (por exemplo, e-mail) de que seu app precisa.
  • Persistência imediata:persista os dados selecionados imediatamente, já que o acesso ao URI de sessão é temporário.
Escrito por:

Continuar lendo