Provedor de contatos

O provedor de contatos é um componente Android poderoso e flexível que gerencia o repositório central de dados sobre pessoas do dispositivo. O provedor de contatos é a fonte dos dados que aparecem no aplicativo de contatos do dispositivo. Você também pode acessar os dados dele no seu próprio aplicativo e transferir dados entre o dispositivo e serviços on-line. O provedor acomoda uma ampla variedade de fontes de dados e tenta gerenciar o máximo de dados possível para cada pessoa, resultando em uma organização complexa. Por isso, a API do provedor inclui um conjunto extenso de classes e interfaces de contrato que facilitam a recuperação e a modificação de dados.

Este guia descreve o seguinte:

  • A estrutura básica do provedor.
  • Como recuperar dados por um provedor.
  • Como modificar dados no provedor.
  • Como escrever um adaptador de sincronização para sincronizar dados do seu servidor com o provedor de contatos.

Este guia considera que o leitor conhece os fundamentos sobre provedores de conteúdo do Android. Para saber mais sobre provedores de conteúdo do Android, leia o guia Fundamentos do provedor de conteúdo.

Organização do Provedor de contatos

O Provedor de contatos é um componente do provedor de conteúdo do Android. Ele mantém três tipos de dados sobre uma pessoa, cada um correspondendo a uma tabela oferecida pelo provedor, conforme ilustrado na figura 1:

Figura 1. Estrutura da tabela do Provedor de Contatos.

As três tabelas são comumente identificadas pelo nome de suas classes de contrato. As classes definem constantes para URIs de conteúdo, nomes de colunas e valores de colunas usados pelas tabelas:

Tabela ContactsContract.Contacts
Linhas que representam pessoas diferentes, com base em agregações de linhas de contato brutas.
Tabela ContactsContract.RawContacts
Linhas que contêm um resumo dos dados de uma pessoa, específicos para uma conta e um tipo de usuário.
Tabela ContactsContract.Data
Linhas que contêm os detalhes do contato bruto, como endereços de e-mail ou números de telefone.

As outras tabelas representadas por classes de contrato em ContactsContract são auxiliares que o provedor de contatos usa para gerenciar as operações ou oferecer suporte a funções específicas nos aplicativos de contatos ou telefonia do dispositivo.

Contatos brutos

Um contato bruto representa os dados de uma pessoa provenientes de um único tipo e nome de conta. Como o provedor de contatos permite mais de um serviço on-line como fonte de dados de uma pessoa, ele permite vários contatos brutos para a mesma pessoa. Vários contatos brutos também permitem que um usuário combine os dados de uma pessoa de mais de uma conta do mesmo tipo.

A maioria dos dados de um contato bruto não é armazenada na tabela ContactsContract.RawContacts. Em vez disso, ele é armazenado em uma ou mais linhas na tabela ContactsContract.Data. Cada linha de dados tem uma coluna Data.RAW_CONTACT_ID que contém o valor RawContacts._ID da linha ContactsContract.RawContacts principal.

Colunas importantes de contatos brutos

As colunas importantes na tabela ContactsContract.RawContacts estão listadas na tabela 1. Leia as observações que se seguem após a tabela:

Tabela 1. Importantes colunas de contatos brutos.

Nome da coluna Uso Observações
ACCOUNT_NAME O nome da conta do tipo de conta que é a origem desse contato bruto. Por exemplo, o nome de uma Conta do Google é um dos endereços do Gmail do proprietário do dispositivo. Consulte a próxima entrada para ACCOUNT_TYPE e mais informações. O formato desse nome é específico deste tipo de conta. Não é necessariamente um endereço de e-mail.
ACCOUNT_TYPE O tipo de conta que é a origem desse contato bruto. Por exemplo, o tipo de conta de uma Conta do Google é com.google. Sempre qualifique seu tipo de conta com um identificador de domínio que você possui ou controla. Isso garante que seu tipo de conta seja exclusivo. Um tipo de conta que oferece dados de contatos geralmente tem um adaptador de sincronização associado que sincroniza com o provedor de contatos.
DELETED A flag "deleted" para um contato bruto. Essa flag permite que o provedor de contatos mantenha a linha internamente até que os adaptadores de sincronização consigam excluir a linha dos servidores e, por fim, do repositório.

Observações

Confira a seguir algumas observações importantes sobre a tabela ContactsContract.RawContacts:

  • O nome de um contato bruto não é armazenado na linha dele em ContactsContract.RawContacts. Em vez disso, ele é armazenado na tabela ContactsContract.Data, em uma linha ContactsContract.CommonDataKinds.StructuredName. Um contato bruto tem apenas uma linha desse tipo na tabela ContactsContract.Data.
  • Atenção:para usar os dados da sua conta em uma linha de contato bruto, ela precisa ser registrada primeiro com o AccountManager. Para isso, peça aos usuários que adicionem o tipo e o nome da conta à lista. Se você não fizer isso, o provedor de contatos vai excluir automaticamente a linha de contato bruto.

    Por exemplo, se você quiser que seu app mantenha dados de contatos para seu serviço baseado na Web com o domínio com.example.dataservice, e a conta do usuário para seu serviço for becky.sharp@dataservice.example.com, o usuário precisará primeiro adicionar o "tipo" (com.example.dataservice) e o "nome" (becky.smart@dataservice.example.com) da conta antes que seu app possa adicionar linhas de contato bruto. Você pode explicar esse requisito ao usuário na documentação ou pedir que ele adicione o tipo e o nome, ou ambos. Os tipos e nomes de contas são descritos em mais detalhes na próxima seção.

Fontes de dados de contatos brutos

Para entender como os contatos brutos funcionam, considere a usuária "Emily Dickinson", que tem as seguintes três contas de usuário definidas no dispositivo dela:

  • emily.dickinson@gmail.com
  • emilyd@gmail.com
  • Conta do Twitter "belle_of_amherst"

O usuário ativou a Sincronização de contatos para todas as três contas nas configurações de Contas.

Suponha que Emily Dickinson abra uma janela do navegador, faça login no Gmail como emily.dickinson@gmail.com, abra Contatos e adicione "Thomas Higginson". Mais tarde, ela faz login no Gmail como emilyd@gmail.com e envia um e-mail para "Thomas Higginson", que o adiciona automaticamente como um contato. Ela também segue "colonel_tom" (ID do Twitter de Thomas Higginson) no Twitter.

O Provedor de contatos cria três contatos brutos como resultado desse trabalho:

  1. Um contato bruto de "Thomas Higginson" associado a emily.dickinson@gmail.com. O tipo de conta do usuário é Google.
  2. Um segundo contato bruto de "Thomas Higginson" associado a emilyd@gmail.com. O tipo da conta de usuário também é do Google. Há um segundo contato bruto, mesmo que o nome seja idêntico a um nome anterior, porque a pessoa foi adicionada a uma conta de usuário diferente.
  3. Um terceiro contato bruto de "Thomas Higginson" associado a "belle_of_amherst". O tipo de conta de usuário é do Twitter.

Dados

Como observado anteriormente, os dados de um contato bruto são armazenados em uma linha ContactsContract.Data vinculada ao valor _ID do contato bruto. Isso permite que um único contato bruto tenha várias instâncias do mesmo tipo de dados, como endereços de e-mail ou números de telefone. Por exemplo, se "Thomas Higginson" para emilyd@gmail.com (a linha de contato bruto de Thomas Higginson associada à Conta do Google emilyd@gmail.com) tiver um endereço de e-mail pessoal thigg@gmail.com e um endereço de e-mail profissional thomas.higginson@gmail.com, o provedor de contatos vai armazenar as duas linhas de endereço de e-mail e vincular ambas ao contato bruto.

Diferentes tipos de dados são armazenados nessa única tabela. As linhas de nome de exibição, número de telefone, e-mail, endereço postal, foto e detalhes do site estão na tabela ContactsContract.Data. Para ajudar a gerenciar isso, a tabela ContactsContract.Data tem algumas colunas com nomes descritivos e outras com nomes genéricos. O conteúdo de uma coluna de nome descritivo tem o mesmo significado, independente do tipo de dados na linha, enquanto o conteúdo de uma coluna de nome genérico tem significados diferentes, dependendo do tipo de dados.

Nomes descritivos de colunas

Veja alguns exemplos de nomes descritivos de colunas:

RAW_CONTACT_ID
O valor da coluna _ID do contato bruto para esses dados.
MIMETYPE
O tipo de dados armazenados nesta linha, expresso como um tipo MIME personalizado. O Provedor de contatos usa os tipos MIME definidos nas subclasses de ContactsContract.CommonDataKinds. Esses tipos MIME são de código aberto e podem ser usados por qualquer aplicativo ou adaptador de sincronização que funcione com o provedor de contatos.
IS_PRIMARY
Se esse tipo de linha de dados puder ocorrer mais de uma vez para um contato bruto, a coluna IS_PRIMARY vai sinalizar a linha de dados que contém os dados principais do tipo. Por exemplo, se o usuário pressionar e manter pressionado um número de telefone de um contato e selecionar Definir como padrão, a linha ContactsContract.Data que contém esse número terá a coluna IS_PRIMARY definida como um valor diferente de zero.

Nomes de colunas genéricas

Há 15 colunas genéricas chamadas DATA1 a DATA15 que estão disponíveis de forma geral e mais quatro colunas genéricas SYNC1 a SYNC4 que só devem ser usadas por adaptadores de sincronização. As constantes de nome de coluna genéricas sempre funcionam, independente do tipo de dados que a linha contém.

A coluna DATA1 é indexada. O provedor de contatos sempre usa essa coluna para os dados que ele espera serem o destino mais frequente de uma consulta. Por exemplo, em uma linha de e-mail, essa coluna contém o endereço de e-mail real.

Por convenção, a coluna DATA15 é reservada para armazenar dados de objetos binários grandes (BLOB), como miniaturas de fotos.

Nomes de coluna de tipo específico

Para facilitar o trabalho com as colunas de um tipo específico de linha, o Provedor de contatos também oferece constantes de nome de coluna específicas do tipo, definidas em subclasses de ContactsContract.CommonDataKinds. As constantes apenas atribuem um nome diferente ao mesmo nome de coluna, o que ajuda a acessar dados em uma linha de um tipo específico.

Por exemplo, a classe ContactsContract.CommonDataKinds.Email define constantes de nome de coluna específicas do tipo para uma linha ContactsContract.Data que tem o tipo MIME Email.CONTENT_ITEM_TYPE. A classe contém a constante ADDRESS para a coluna de endereço de e-mail. O valor real de ADDRESS é "data1", que é o mesmo nome genérico da coluna.

Atenção:não adicione seus próprios dados personalizados à tabela ContactsContract.Data usando uma linha que tenha um dos tipos MIME predefinidos do provedor. Se fizer isso, você poderá perder os dados ou causar um mau funcionamento no provedor. Por exemplo, não adicione uma linha com o tipo MIME Email.CONTENT_ITEM_TYPE que contenha um nome de usuário em vez de um endereço de e-mail na coluna DATA1. Se você usar seu próprio tipo MIME personalizado para a linha, poderá definir seus próprios nomes de colunas específicos do tipo e usar as colunas como quiser.

A Figura 2 mostra como as colunas descritivas e de dados aparecem em uma linha ContactsContract.Data e como os nomes de colunas específicos do tipo "sobrepõem" os nomes de colunas genéricos.

Como nomes de coluna de tipo específico mapeiam nomes de coluna genéricos

Figura 2. Nomes de coluna de tipo específico e nomes de coluna genérica.

Classes de nome de coluna de tipo específico

A tabela 2 lista as classes de nome de coluna de tipo específico mais usadas:

Tabela 2. Classes de nome de coluna de tipo específico

Classes de mapeamento Tipo de dados Observações
ContactsContract.CommonDataKinds.StructuredName São os dados de nome do contato bruto associados a essa linha de dados. Os contatos brutos têm somente uma dessas linhas.
ContactsContract.CommonDataKinds.Photo É a foto principal do contato bruto associada a essa linha de dados. Os contatos brutos têm somente uma dessas linhas.
ContactsContract.CommonDataKinds.Email É um endereço de e-mail do contato bruto associado a essa linha de dados. Os contatos brutos podem ter diversos endereços de e-mail.
ContactsContract.CommonDataKinds.StructuredPostal É um endereço postal do contato bruto associado a essa linha de dados. Os contatos brutos podem ter diversos endereços postais.
ContactsContract.CommonDataKinds.GroupMembership É um identificador que vincula o contato bruto a um dos grupos no Provedor de contatos. Grupos são um recurso opcional de um tipo e um nome de conta. Eles são descritos em mais detalhes na seção Grupos de contatos.

Contatos

O Provedor de contatos combina as linhas de contato bruto em todos os tipos e nomes de contas para formar um contato. Isso facilita a exibição e a modificação de todos os dados que um usuário coletou sobre uma pessoa. O Provedor de contatos gerencia a criação de novas linhas de contato e a agregação de contatos brutos com uma linha de contato existente. Nem os aplicativos nem os adaptadores de sincronização podem adicionar contatos, e algumas colunas em uma linha de contato são somente leitura.

Observação:se você tentar adicionar um contato ao provedor de contatos com um insert(), vai receber uma exceção UnsupportedOperationException. Se você tentar atualizar uma coluna listada como "somente leitura", a atualização será ignorada.

O provedor de contatos cria um novo contato em resposta à adição de um novo contato bruto que não corresponde a nenhum contato existente. O provedor também faz isso se os dados de um contato bruto mudarem de forma que não correspondam mais ao contato a que estavam vinculados anteriormente. Se um aplicativo ou adaptador de sincronização criar um novo contato bruto que não corresponda a um contato existente, o novo contato bruto será agregado ao contato existente.

O Provedor de contatos vincula uma linha de contato às linhas de contato bruto com a coluna _ID da linha de contato na tabela Contacts. A coluna CONTACT_ID da tabela de contatos brutos ContactsContract.RawContacts contém valores _ID para a linha de contatos associada a cada linha de contatos brutos.

A tabela ContactsContract.Contacts também tem a coluna LOOKUP_KEY, que é um link "permanente" para a linha de contato. Como o provedor de contatos mantém os contatos automaticamente, ele pode mudar o valor _ID de uma linha de contato em resposta a uma agregação ou sincronização. Mesmo que isso aconteça, o URI de conteúdo CONTENT_LOOKUP_URI combinado com o LOOKUP_KEY do contato ainda vai apontar para a linha do contato. Assim, você pode usar LOOKUP_KEY para manter links para contatos favoritos e assim por diante. Essa coluna tem um formato próprio que não está relacionado ao formato da coluna _ID.

A figura 3 mostra como as três tabelas principais se relacionam entre si.

Principais tabelas do Provedor de contatos

Figura 3. Contatos, contatos brutos e relacionamentos da tabela de detalhes.

Atenção : se você publicar seu app na Google Play Store ou se ele estiver em um dispositivo com o Android 10 (nível 29 da API) ou versões mais recentes, lembre-se de que um conjunto limitado de campos de dados e métodos de contatos está obsoleto.

Nas condições mencionadas, o sistema limpa periodicamente os valores gravados nestes campos de dados:

As APIs usadas para definir os campos de dados citados acima também estão obsoletas:

Além disso, os campos a seguir não retornam mais os contatos frequentes. Alguns desses campos influenciam as classificações de contatos somente quando eles fazem parte de um tipo de dados específico.

Se os apps estiverem acessando ou atualizando esses campos ou APIs, use métodos alternativos. Por exemplo, é possível atender a determinados casos de uso usando provedores de conteúdo particulares ou outros dados armazenados no app ou em sistemas de back-end.

Para verificar se a funcionalidade do app não foi afetada por essa mudança, limpe manualmente esses campos de dados. Para isso, execute o seguinte comando ADB em um dispositivo com Android 4.1 (nível 16 da API) ou mais recente:

adb shell content delete \
--uri content://com.android.contacts/contacts/delete_usage

Dados de adaptadores de sincronização

Os usuários inserem dados de contatos diretamente no dispositivo, mas os dados também fluem para o provedor de contatos de serviços da Web por adaptadores de sincronização, que automatizam a transferência de dados entre o dispositivo e os serviços. Os adaptadores de sincronização são executados em segundo plano sob o controle do sistema e chamam métodos ContentResolver para gerenciar dados.

No Android, o serviço Web com que um adaptador de sincronização trabalha é identificado por um tipo de conta. Cada adaptador de sincronização funciona com um tipo de conta, mas pode oferecer suporte a vários nomes de conta desse tipo. Os tipos e nomes de contas são descritos brevemente na seção Fontes de dados de contatos brutos. As definições a seguir oferecem mais detalhes e descrevem como o tipo e o nome da conta se relacionam com adaptadores e serviços de sincronização.

Tipo de conta
Identifica um serviço em que o usuário armazenou dados. Na maioria das vezes, o usuário precisa se autenticar com o serviço. Por exemplo, os Contatos do Google são um tipo de conta, identificados pelo código google.com. Esse valor corresponde ao tipo de conta usado pelo AccountManager.
Nome da conta
Identifica uma conta ou um login específico para um tipo de conta. As contas do Google Contatos são as mesmas que as Contas do Google, que têm um endereço de e-mail como nome de conta. Outros serviços podem usar um nome de usuário com só uma palavra ou código numérico.

Os tipos de conta não precisam ser exclusivos. Um usuário pode configurar várias contas do Google Contatos e baixar os dados para o provedor de contatos. Isso pode acontecer se o usuário tiver um conjunto de contatos pessoais para um nome de conta pessoal e outro para o trabalho. Os nomes de conta geralmente são exclusivos. Juntos, eles identificam um fluxo de dados específico entre o provedor de contatos e um serviço externo.

Se você quiser transferir os dados do seu serviço para o provedor de contatos, escreva seu próprio adaptador de sincronização. Isso é descrito com mais detalhes na seção Adaptadores de sincronização do provedor de contatos.

A Figura 4 mostra como o provedor de contatos se encaixa no fluxo de dados sobre pessoas. Na caixa marcada como "adaptadores de sincronização", cada adaptador é rotulado pelo tipo de conta.

Fluxo de dados sobre pessoas

Figura 4. Fluxo de dados do Provedor de Contatos.

Permissões necessárias

Os apps que quiserem acessar o provedor de contatos precisam solicitar as seguintes permissões:

Acesso de leitura a uma ou mais tabelas
READ_CONTACTS, especificado em AndroidManifest.xml com o elemento <uses-permission> como <uses-permission android:name="android.permission.READ_CONTACTS">.
Acesso de gravação a uma ou mais tabelas
WRITE_CONTACTS, especificado em AndroidManifest.xml com o elemento <uses-permission> como <uses-permission android:name="android.permission.WRITE_CONTACTS">.

Essas permissões não se estendem aos dados do perfil do usuário. O perfil do usuário e as permissões necessárias são abordados na seção a seguir, O perfil do usuário.

Lembre-se de que os dados de contatos do usuário são pessoais e sensíveis. Os usuários se preocupam com a privacidade e não querem que os aplicativos coletem dados sobre eles ou os contatos. Se não for óbvio por que você precisa de permissão para acessar os dados de contatos, as pessoas podem dar notas baixas ao aplicativo ou simplesmente se recusar a instalá-lo.

O perfil do usuário

A tabela ContactsContract.Contacts tem uma única linha com dados de perfil do usuário do dispositivo. Esses dados descrevem o user do dispositivo, e não um dos contatos do usuário. A linha de contatos do perfil está vinculada a uma linha de contatos brutos para cada sistema que usa um perfil. Cada linha de contato bruto de perfil pode ter diversas linhas de dados. As constantes para acessar o perfil do usuário estão disponíveis na classe ContactsContract.Profile.

O acesso ao perfil do usuário exige permissões especiais. Além das permissões READ_CONTACTS e WRITE_CONTACTS necessárias para leitura e gravação, o acesso ao perfil do usuário exige as permissões android.Manifest.permission#READ_PROFILE e android.Manifest.permission#WRITE_PROFILE para acesso de leitura e gravação, respectivamente.

Lembre-se de que o perfil de um usuário é considerado sensível. A permissão android.Manifest.permission#READ_PROFILE permite acessar os dados de identificação pessoal do usuário do dispositivo. Não se esqueça de informar ao usuário por que você precisa de permissões de acesso ao perfil na descrição do aplicativo.

Para recuperar a linha de contato que contém o perfil do usuário, chame ContentResolver.query(). Defina o URI de conteúdo como CONTENT_URI e não forneça nenhum critério de seleção. Você também pode usar esse URI de conteúdo como o URI base para recuperar contatos ou dados brutos do perfil. Por exemplo, esse snippet recupera dados do perfil:

Kotlin

// Sets the columns to retrieve for the user profile
projection = arrayOf(
        ContactsContract.Profile._ID,
        ContactsContract.Profile.DISPLAY_NAME_PRIMARY,
        ContactsContract.Profile.LOOKUP_KEY,
        ContactsContract.Profile.PHOTO_THUMBNAIL_URI
)

// Retrieves the profile from the Contacts Provider
profileCursor = contentResolver.query(
        ContactsContract.Profile.CONTENT_URI,
        projection,
        null,
        null,
        null
)

Java

// Sets the columns to retrieve for the user profile
projection = new String[]
    {
        Profile._ID,
        Profile.DISPLAY_NAME_PRIMARY,
        Profile.LOOKUP_KEY,
        Profile.PHOTO_THUMBNAIL_URI
    };

// Retrieves the profile from the Contacts Provider
profileCursor =
        getContentResolver().query(
                Profile.CONTENT_URI,
                projection ,
                null,
                null,
                null);

Observação:se você recuperar várias linhas de contato e quiser determinar se uma delas é o perfil do usuário, teste a coluna IS_USER_PROFILE da linha. Essa coluna é definida como "1" se o contato for o perfil do usuário.

Metadados do Provedor de contatos

O provedor de contatos gerencia dados que acompanham o estado dos dados de contatos no repositório. Esses metadados sobre o repositório são armazenados em vários lugares, incluindo as linhas das tabelas Contatos brutos, Dados e Contatos, as tabelas ContactsContract.Settings e ContactsContract.SyncState. A tabela a seguir mostra o efeito de cada um desses metadados:

Tabela 3. Metadados no Provedor de Contatos

Tabela Coluna Valores Significado
ContactsContract.RawContacts DIRTY "0": não mudou desde a última sincronização. Marca os contatos brutos que foram alterados no dispositivo e precisam ser sincronizados novamente com o servidor. O valor é definido automaticamente pelo provedor de contatos quando os aplicativos Android atualizam uma linha.

Adaptadores de sincronização que modificam o contato ou as tabelas de dados brutos sempre precisam anexar a string CALLER_IS_SYNCADAPTER ao URI de conteúdo usado. Isso evita que o provedor sinalize alguma linha como suja. Caso contrário, as modificações do adaptador de sincronização vão parecer locais e serão enviadas ao servidor, mesmo que ele tenha sido a origem da modificação.

"1": mudou desde a última sincronização e precisa ser sincronizado novamente com o servidor.
ContactsContract.RawContacts VERSION É o número da versão dessa linha. O provedor de contatos incrementa automaticamente esse valor sempre que a linha ou os dados relacionados são alterados.
ContactsContract.Data DATA_VERSION É o número da versão dessa linha. O provedor de contatos incrementa automaticamente esse valor sempre que a linha de dados é alterada.
ContactsContract.RawContacts SOURCE_ID Um valor de string que identifica de forma exclusiva esse contato bruto para a conta em que ele foi criado. Quando um adaptador de sincronização cria um contato bruto, essa coluna precisa ser definida como o ID exclusivo do servidor para o contato bruto. Quando um aplicativo Android cria um novo contato bruto, ele precisa deixar essa coluna vazia. Isso indica ao adaptador de sincronização que ele precisa criar um novo contato bruto no servidor e receber um valor para o SOURCE_ID.

Em particular, o ID da origem precisa ser exclusivo para cada tipo de conta e estável em todas as sincronizações:

  • Exclusivo: cada contato bruto de uma conta precisa ter um ID de origem próprio. Se você não fizer isso, vai causar problemas no aplicativo de contatos. Dois contatos brutos da mesma conta type podem ter o mesmo ID de origem. Por exemplo, o contato bruto "Thomas Higginson" da conta emily.dickinson@gmail.com pode ter o mesmo ID de origem do contato bruto "Thomas Higginson" da conta emilyd@gmail.com.
  • Estável: os IDs de origem são uma parte permanente dos dados do serviço on-line para o contato bruto. Por exemplo, se o usuário limpar o armazenamento de contatos nas configurações de apps e sincronizar novamente, os contatos brutos restaurados terão os mesmos IDs de origem de antes. Se você não fizer isso, os atalhos vão parar de funcionar.
ContactsContract.Groups GROUP_VISIBLE "0": os contatos desse grupo não podem ser visíveis nas interfaces de aplicativos Android. Essa coluna é para compatibilidade com servidores que permitem que um usuário oculte contatos em determinados grupos.
"1": os contatos neste grupo podem ficar visíveis nas interfaces de aplicativos.
ContactsContract.Settings UNGROUPED_VISIBLE "0": para essa conta e tipo de conta, os contatos que não pertencem a um grupo ficam invisíveis para as interfaces de aplicativos Android. Por padrão, os contatos ficam invisíveis se nenhum dos contatos brutos pertencer a um grupo. A associação de um contato bruto a um grupo é indicada por uma ou mais linhas ContactsContract.CommonDataKinds.GroupMembership na tabela ContactsContract.Data. Ao definir essa flag na linha da tabela ContactsContract.Settings para um tipo de conta e uma conta, você pode forçar a visibilidade dos contatos sem grupos. Uma das funções dessa flag é mostrar contatos de servidores que não usam grupos.
"1": para essa conta e tipo de conta, os contatos que não pertencem a um grupo ficam visíveis nas interfaces do aplicativo.
ContactsContract.SyncState (todos) Use essa tabela para armazenar metadados do seu adaptador de sincronização. Com essa tabela, é possível armazenar o estado da sincronização e outros dados relacionados a ela de forma persistente no dispositivo.

Acesso ao Provedor de contatos

Esta seção descreve as diretrizes para acessar dados do provedor de contatos, com foco no seguinte:

  • Consultas de entidade.
  • Modificação em lote.
  • Recuperação e modificação com intents.
  • Integridade dos dados.

Fazer modificações de um adaptador de sincronização também é abordado com mais detalhes na seção Adaptadores de sincronização do provedor de contatos.

Consultas de entidades

Como as tabelas do Provedor de contatos são organizadas em uma hierarquia, geralmente é útil recuperar uma linha e todas as linhas "filhas" vinculadas a ela. Por exemplo, para mostrar todas as informações de uma pessoa, talvez seja necessário extrair todas as linhas ContactsContract.RawContacts de uma única linha ContactsContract.Contacts ou todas as linhas ContactsContract.CommonDataKinds.Email de uma única linha ContactsContract.RawContacts. Para facilitar isso, o Provedor de contatos oferece construções de entidade, que funcionam como junções de banco de dados entre tabelas.

Uma entidade é como uma tabela composta de colunas selecionadas de uma tabela pai e uma tabela filha. Ao consultar uma entidade, você fornece uma projeção e critérios de pesquisa com base nas colunas disponíveis na entidade. O resultado é um Cursor que contém uma linha para cada linha da tabela filha recuperada. Por exemplo, se você consultar ContactsContract.Contacts.Entity para um nome de contato e todas as linhas ContactsContract.CommonDataKinds.Email de todos os contatos brutos desse nome, vai receber um Cursor com uma linha para cada linha ContactsContract.CommonDataKinds.Email.

As entidades simplificam as consultas. Usando uma entidade, é possível recuperar todos os dados de um contato ou contato bruto de uma só vez, em vez de consultar primeiro a tabela principal para receber um ID e depois consultar a tabela secundária com esse ID. Além disso, o provedor de contatos processa uma consulta em relação a uma entidade em uma única transação, o que garante que os dados recuperados sejam internamente consistentes.

Observação:uma entidade geralmente não contém todas as colunas da tabela pai e filha. Se você tentar trabalhar com um nome de coluna que não está na lista de constantes de nome de coluna para a entidade, vai receber um Exception.

O snippet a seguir mostra como recuperar todas as linhas de contato bruto de um contato. O snippet faz parte de um aplicativo maior com duas atividades, "main" e "detail". A atividade principal mostra uma lista de linhas de contato. Quando o usuário seleciona uma, a atividade envia o ID dela para a atividade de detalhes. A atividade de detalhes usa o ContactsContract.Contacts.Entity para mostrar todas as linhas de dados de todos os contatos brutos associados ao contato selecionado.

Este snippet é extraído da atividade "detail":

Kotlin

...
    /*
     * Appends the entity path to the URI. In the case of the Contacts Provider, the
     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
     */
    contactUri = Uri.withAppendedPath(
            contactUri,
            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY
    )

    // Initializes the loader identified by LOADER_ID.
    loaderManager.initLoader(
            LOADER_ID,  // The identifier of the loader to initialize
            null,       // Arguments for the loader (in this case, none)
            this        // The context of the activity
    )

    // Creates a new cursor adapter to attach to the list view
    cursorAdapter = SimpleCursorAdapter(
            this,                       // the context of the activity
            R.layout.detail_list_item,  // the view item containing the detail widgets
            mCursor,                    // the backing cursor
            fromColumns,               // the columns in the cursor that provide the data
            toViews,                   // the views in the view item that display the data
            0)                          // flags

    // Sets the ListView's backing adapter.
    rawContactList.adapter = cursorAdapter
...
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
    /*
     * Sets the columns to retrieve.
     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
     * DATA1 contains the first column in the data row (usually the most important one).
     * MIMETYPE indicates the type of data in the data row.
     */
    val projection: Array<String> = arrayOf(
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
            ContactsContract.Contacts.Entity.DATA1,
            ContactsContract.Contacts.Entity.MIMETYPE
    )

    /*
     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
     * contact collated together.
     */
    val sortOrder = "${ContactsContract.Contacts.Entity.RAW_CONTACT_ID} ASC"

    /*
     * Returns a new CursorLoader. The arguments are similar to
     * ContentResolver.query(), except for the Context argument, which supplies the location of
     * the ContentResolver to use.
     */
    return CursorLoader(
            applicationContext, // The activity's context
            contactUri,        // The entity content URI for a single contact
            projection,         // The columns to retrieve
            null,               // Retrieve all the raw contacts and their data rows.
            null,               //
            sortOrder           // Sort by the raw contact ID.
    )
}

Java

...
    /*
     * Appends the entity path to the URI. In the case of the Contacts Provider, the
     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
     */
    contactUri = Uri.withAppendedPath(
            contactUri,
            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);

    // Initializes the loader identified by LOADER_ID.
    getLoaderManager().initLoader(
            LOADER_ID,  // The identifier of the loader to initialize
            null,       // Arguments for the loader (in this case, none)
            this);      // The context of the activity

    // Creates a new cursor adapter to attach to the list view
    cursorAdapter = new SimpleCursorAdapter(
            this,                        // the context of the activity
            R.layout.detail_list_item,   // the view item containing the detail widgets
            mCursor,                     // the backing cursor
            fromColumns,                // the columns in the cursor that provide the data
            toViews,                    // the views in the view item that display the data
            0);                          // flags

    // Sets the ListView's backing adapter.
    rawContactList.setAdapter(cursorAdapter);
...
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {

    /*
     * Sets the columns to retrieve.
     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
     * DATA1 contains the first column in the data row (usually the most important one).
     * MIMETYPE indicates the type of data in the data row.
     */
    String[] projection =
        {
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
            ContactsContract.Contacts.Entity.DATA1,
            ContactsContract.Contacts.Entity.MIMETYPE
        };

    /*
     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
     * contact collated together.
     */
    String sortOrder =
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
            " ASC";

    /*
     * Returns a new CursorLoader. The arguments are similar to
     * ContentResolver.query(), except for the Context argument, which supplies the location of
     * the ContentResolver to use.
     */
    return new CursorLoader(
            getApplicationContext(),  // The activity's context
            contactUri,              // The entity content URI for a single contact
            projection,               // The columns to retrieve
            null,                     // Retrieve all the raw contacts and their data rows.
            null,                     //
            sortOrder);               // Sort by the raw contact ID.
}

Quando o carregamento termina, LoaderManager invoca um callback para onLoadFinished(). Um dos argumentos recebidos para esse método é um Cursor com os resultados da consulta. No seu próprio app, você pode receber os dados desse Cursor para mostrar ou trabalhar com eles.

Modificação em lote

Sempre que possível, insira, atualize e exclua dados no provedor de contatos em "modo de lote", criando uma ArrayList de objetos ContentProviderOperation e chamando applyBatch(). Como o provedor de contatos realiza todas as operações em um applyBatch() em uma única transação, suas modificações nunca vão deixar o repositório de contatos em um estado inconsistente. Uma modificação em lote também facilita a inserção de um contato bruto e os dados detalhados dele ao mesmo tempo.

Observação:para modificar um único contato bruto, envie um intent para o aplicativo de contatos do dispositivo em vez de processar a modificação no seu app. Isso é descrito com mais detalhes na seção Recuperação e modificação com intents.

Pontos de rendimento

Uma modificação em lote com um grande número de operações pode bloquear outros processos, resultando em uma experiência ruim para o usuário. Para organizar todas as modificações que você quer fazer em um número mínimo de listas separadas e, ao mesmo tempo, evitar que elas bloqueiem o sistema, defina pontos de interrupção para uma ou mais operações. Um ponto de retorno é um objeto ContentProviderOperation que tem o valor isYieldAllowed() definido como true. Quando o provedor de contatos encontra um ponto de interrupção, ele pausa o trabalho para permitir que outros processos sejam executados e fecha a transação atual. Quando o provedor é iniciado novamente, ele continua com a próxima operação no ArrayList e inicia uma nova transação.

Os pontos de retorno resultam em mais de uma transação por chamada para applyBatch(). Por isso, defina um ponto de interrupção para a última operação de um conjunto de linhas relacionadas. Por exemplo, defina um ponto de retorno para a última operação em um conjunto que adiciona linhas de contato brutas e linhas de dados associadas ou a última operação para um conjunto de linhas relacionadas a um único contato.

Os pontos de rendimento também são uma unidade de operação atômica. Todos os acessos entre dois pontos de retorno serão bem-sucedidos ou falharão como uma única unidade. Se você não definir nenhum ponto de retorno, a menor operação atômica será todo o lote de operações. Se você usar pontos de retorno, vai evitar que as operações prejudiquem o desempenho do sistema e garantir que um subconjunto de operações seja atômico.

Referências de retorno da modificação

Ao inserir uma nova linha de contato bruto e as linhas de dados associadas como um conjunto de objetos ContentProviderOperation, você precisa vincular as linhas de dados à linha de contato bruto inserindo o valor _ID do contato bruto como o valor RAW_CONTACT_ID. No entanto, esse valor não está disponível quando você cria o ContentProviderOperation para a linha de dados, porque ainda não aplicou o ContentProviderOperation para a linha de contato bruto. Para contornar isso, a classe ContentProviderOperation.Builder tem o método withValueBackReference(). Esse método permite inserir ou modificar uma coluna com o resultado de uma operação anterior.

O método withValueBackReference() tem dois argumentos:

key
A chave de um par de chave-valor. O valor desse argumento precisa ser o nome de uma coluna na tabela que você está modificando.
previousResult
O índice baseado em zero de um valor na matriz de objetos ContentProviderResult de applyBatch(). À medida que as operações em lote são aplicadas, o resultado de cada uma delas é armazenado em uma matriz intermediária de resultados. O valor previousResult é o índice de um desses resultados, que é recuperado e armazenado com o valor key. Isso permite inserir um novo registro de contato bruto e receber o valor _ID dele. Depois, faça uma "referência anterior" ao valor ao adicionar uma linha ContactsContract.Data.

Toda a matriz de resultados é criada quando você chama applyBatch() pela primeira vez, com um tamanho igual ao tamanho do ArrayList dos objetos ContentProviderOperation fornecidos. No entanto, todos os elementos na matriz de resultados são definidos como null, e se você tentar fazer uma referência anterior a um resultado de uma operação que ainda não foi aplicada, withValueBackReference() vai gerar um Exception.

Os snippets a seguir mostram como inserir um novo contato bruto e dados em lote. Eles incluem código que estabelece um ponto de retorno e usa uma referência anterior.

O primeiro snippet recupera dados de contato da IU. Neste ponto, o usuário já selecionou a conta em que o novo contato bruto será adicionado.

Kotlin

// Creates a contact entry from the current UI values, using the currently-selected account.
private fun createContactEntry() {
    /*
     * Gets values from the UI
     */
    val name = contactNameEditText.text.toString()
    val phone = contactPhoneEditText.text.toString()
    val email = contactEmailEditText.text.toString()

    val phoneType: String = contactPhoneTypes[mContactPhoneTypeSpinner.selectedItemPosition]

    val emailType: String = contactEmailTypes[mContactEmailTypeSpinner.selectedItemPosition]

Java

// Creates a contact entry from the current UI values, using the currently-selected account.
protected void createContactEntry() {
    /*
     * Gets values from the UI
     */
    String name = contactNameEditText.getText().toString();
    String phone = contactPhoneEditText.getText().toString();
    String email = contactEmailEditText.getText().toString();

    int phoneType = contactPhoneTypes.get(
            contactPhoneTypeSpinner.getSelectedItemPosition());

    int emailType = contactEmailTypes.get(
            contactEmailTypeSpinner.getSelectedItemPosition());

O snippet a seguir cria uma operação para inserir a linha de contato bruto na tabela ContactsContract.RawContacts:

Kotlin

    /*
     * Prepares the batch operation for inserting a new raw contact and its data. Even if
     * the Contacts Provider does not have any data for this person, you can't add a Contact,
     * only a raw contact. The Contacts Provider will then add a Contact automatically.
     */

    // Creates a new array of ContentProviderOperation objects.
    val ops = arrayListOf<ContentProviderOperation>()

    /*
     * Creates a new raw contact with its account type (server type) and account name
     * (user's account). Remember that the display name is not stored in this row, but in a
     * StructuredName data row. No other data is required.
     */
    var op: ContentProviderOperation.Builder =
            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.name)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.type)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

Java

    /*
     * Prepares the batch operation for inserting a new raw contact and its data. Even if
     * the Contacts Provider does not have any data for this person, you can't add a Contact,
     * only a raw contact. The Contacts Provider will then add a Contact automatically.
     */

     // Creates a new array of ContentProviderOperation objects.
    ArrayList<ContentProviderOperation> ops =
            new ArrayList<ContentProviderOperation>();

    /*
     * Creates a new raw contact with its account type (server type) and account name
     * (user's account). Remember that the display name is not stored in this row, but in a
     * StructuredName data row. No other data is required.
     */
    ContentProviderOperation.Builder op =
            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType())
            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName());

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

Em seguida, o código cria linhas de dados para as linhas de nome de exibição, telefone e e-mail.

Cada objeto builder de operação usa withValueBackReference() para receber o RAW_CONTACT_ID. A referência aponta de volta para o objeto ContentProviderResult da primeira operação, que adiciona a linha de contato bruto e retorna o novo valor _ID. Como resultado, cada linha de dados é vinculada automaticamente pelo RAW_CONTACT_ID à nova linha ContactsContract.RawContacts a que pertence.

O objeto ContentProviderOperation.Builder que adiciona a linha de e-mail é marcado com withYieldAllowed(), que define um ponto de retorno:

Kotlin

    // Creates the display name for the new raw contact, as a StructuredName data row.
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * withValueBackReference sets the value of the first argument to the value of
             * the ContentProviderResult indexed by the second argument. In this particular
             * call, the raw contact ID column of the StructuredName data row is set to the
             * value of the result returned by the first operation, which is the one that
             * actually adds the raw contact row.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to StructuredName
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)

            // Sets the data row's display name to the name in the UI.
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

    // Inserts the specified phone number and type as a Phone data row
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Phone
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

            // Sets the phone number and type
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

    // Inserts the specified email and type as a Phone data row
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Email
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

            // Sets the email address and type
            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType)

    /*
     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
     * will yield priority to other threads. Use after every set of operations that affect a
     * single contact, to avoid degrading performance.
     */
    op.withYieldAllowed(true)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

Java

    // Creates the display name for the new raw contact, as a StructuredName data row.
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * withValueBackReference sets the value of the first argument to the value of
             * the ContentProviderResult indexed by the second argument. In this particular
             * call, the raw contact ID column of the StructuredName data row is set to the
             * value of the result returned by the first operation, which is the one that
             * actually adds the raw contact row.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to StructuredName
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)

            // Sets the data row's display name to the name in the UI.
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

    // Inserts the specified phone number and type as a Phone data row
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Phone
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

            // Sets the phone number and type
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

    // Inserts the specified email and type as a Phone data row
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Email
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

            // Sets the email address and type
            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);

    /*
     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
     * will yield priority to other threads. Use after every set of operations that affect a
     * single contact, to avoid degrading performance.
     */
    op.withYieldAllowed(true);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

O último snippet mostra a chamada para applyBatch() que insere o novo contato bruto e as linhas de dados.

Kotlin

    // Ask the Contacts Provider to create a new contact
    Log.d(TAG, "Selected account: ${mSelectedAccount.name} (${mSelectedAccount.type})")
    Log.d(TAG, "Creating contact: $name")

    /*
     * Applies the array of ContentProviderOperation objects in batch. The results are
     * discarded.
     */
    try {
        contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
    } catch (e: Exception) {
        // Display a warning
        val txt: String = getString(R.string.contactCreationFailure)
        Toast.makeText(applicationContext, txt, Toast.LENGTH_SHORT).show()

        // Log exception
        Log.e(TAG, "Exception encountered while inserting contact: $e")
    }
}

Java

    // Ask the Contacts Provider to create a new contact
    Log.d(TAG,"Selected account: " + selectedAccount.getName() + " (" +
            selectedAccount.getType() + ")");
    Log.d(TAG,"Creating contact: " + name);

    /*
     * Applies the array of ContentProviderOperation objects in batch. The results are
     * discarded.
     */
    try {

            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
    } catch (Exception e) {

            // Display a warning
            Context ctx = getApplicationContext();

            CharSequence txt = getString(R.string.contactCreationFailure);
            int duration = Toast.LENGTH_SHORT;
            Toast toast = Toast.makeText(ctx, txt, duration);
            toast.show();

            // Log exception
            Log.e(TAG, "Exception encountered while inserting contact: " + e);
    }
}

As operações em lote também permitem implementar o controle de simultaneidade otimista, um método de aplicação de transações de modificação sem precisar bloquear o repositório subjacente. Para usar esse método, aplique a transação e verifique outras modificações que podem ter sido feitas ao mesmo tempo. Se você encontrar uma modificação inconsistente, faça o rollback da transação e tente de novo.

O controle de simultaneidade otimista é útil para um dispositivo móvel, em que há apenas um usuário por vez e acessos simultâneos a um repositório de dados são raros. Como o bloqueio não é usado, não há perda de tempo na definição de bloqueios ou na espera que outras transações liberem os bloqueios.

Para usar o controle de simultaneidade otimista ao atualizar uma única linha de ContactsContract.RawContacts, siga estas etapas:

  1. Recupere a coluna VERSION do contato bruto junto com os outros dados.
  2. Crie um objeto ContentProviderOperation.Builder adequado para aplicar uma restrição usando o método newAssertQuery(Uri). Para o URI de conteúdo, use RawContacts.CONTENT_URI com o _ID do contato bruto anexado a ele.
  3. Para o objeto ContentProviderOperation.Builder, chame withValue() para comparar a coluna VERSION com o número da versão que você acabou de recuperar.
  4. Para o mesmo ContentProviderOperation.Builder, chame withExpectedCount() para garantir que apenas uma linha seja testada por essa declaração.
  5. Chame build() para criar o objeto ContentProviderOperation e adicione esse objeto como o primeiro objeto no ArrayList que você transmite para applyBatch().
  6. Aplique a transação em lote.

Se a linha de contato bruto for atualizada por outra operação entre o momento em que você lê a linha e o momento em que tenta modificá-la, a asserção ContentProviderOperation vai falhar, e todo o lote de operações será revertido. Em seguida, você pode tentar novamente o lote ou realizar outra ação.

O snippet a seguir demonstra como criar uma ContentProviderOperation "assert" depois de consultar um único contato bruto usando um CursorLoader:

Kotlin

/*
 * The application uses CursorLoader to query the raw contacts table. The system calls this method
 * when the load is finished.
 */
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
    // Gets the raw contact's _ID and VERSION values
    rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID))
    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION))
}

...

// Sets up a Uri for the assert operation
val rawContactUri: Uri = ContentUris.withAppendedId(
        ContactsContract.RawContacts.CONTENT_URI,
        rawContactID
)

// Creates a builder for the assert operation
val assertOp: ContentProviderOperation.Builder =
        ContentProviderOperation.newAssertQuery(rawContactUri).apply {
            // Adds the assertions to the assert operation: checks the version
            withValue(SyncColumns.VERSION, mVersion)

            // and count of rows tested
            withExpectedCount(1)
        }

// Creates an ArrayList to hold the ContentProviderOperation objects
val ops = arrayListOf<ContentProviderOperation>()

ops.add(assertOp.build())

// You would add the rest of your batch operations to "ops" here

...

// Applies the batch. If the assert fails, an Exception is thrown
try {
    val results: Array<ContentProviderResult> = contentResolver.applyBatch(AUTHORITY, ops)
} catch (e: OperationApplicationException) {
    // Actions you want to take if the assert operation fails go here
}

Java

/*
 * The application uses CursorLoader to query the raw contacts table. The system calls this method
 * when the load is finished.
 */
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {

    // Gets the raw contact's _ID and VERSION values
    rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
}

...

// Sets up a Uri for the assert operation
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactID);

// Creates a builder for the assert operation
ContentProviderOperation.Builder assertOp = ContentProviderOperation.newAssertQuery(rawContactUri);

// Adds the assertions to the assert operation: checks the version and count of rows tested
assertOp.withValue(SyncColumns.VERSION, mVersion);
assertOp.withExpectedCount(1);

// Creates an ArrayList to hold the ContentProviderOperation objects
ArrayList ops = new ArrayList<ContentProviderOperation>;

ops.add(assertOp.build());

// You would add the rest of your batch operations to "ops" here

...

// Applies the batch. If the assert fails, an Exception is thrown
try
    {
        ContentProviderResult[] results =
                getContentResolver().applyBatch(AUTHORITY, ops);

    } catch (OperationApplicationException e) {

        // Actions you want to take if the assert operation fails go here
    }

Recuperação e modificação com intents

Ao enviar uma intent para o aplicativo de contatos do dispositivo, você pode acessar o Provedor de contatos indiretamente. A intent inicia a interface do aplicativo de contatos do dispositivo, em que os usuários podem realizar tarefas relacionadas a contatos. Com esse tipo de acesso, os usuários podem:

  • Selecionar um contato de uma lista e retorná-lo ao aplicativo para trabalhos futuros.
  • Edite os dados de um contato.
  • Inserir um novo contato bruto para quaisquer das suas contas.
  • Excluir um contato ou dados dos contatos.

Se o usuário estiver inserindo ou atualizando dados, colete as informações primeiro e envie como parte da intent.

Quando você usa intents para acessar o provedor de contatos pelo aplicativo de contatos do dispositivo, não é necessário escrever sua própria interface ou código para acessar o provedor. Você também não precisa solicitar permissão para ler ou gravar no provedor. O aplicativo de contatos do dispositivo pode delegar permissão de leitura de um contato a você. Como você está fazendo modificações no provedor por outro aplicativo, não é necessário ter permissões de gravação.

O processo geral de envio de uma intent para acessar um provedor é descrito em detalhes no guia Fundamentos do provedor de conteúdo na seção "Acesso a dados por intents". A ação, o tipo MIME e os valores de dados usados para as tarefas disponíveis estão resumidos na Tabela 4. Já os valores extras que podem ser usados com putExtra() estão listados na documentação de referência para ContactsContract.Intents.Insert:

Tabela 4. Intenções do Provedor de Contatos

Tarefa Ação Dados Tipo MIME Observações
Escolher um contato de uma lista ACTION_PICK Uma destas: Não usado Mostra uma lista de contatos brutos ou uma lista de dados de um contato bruto, dependendo do tipo de URI de conteúdo fornecido.

Chame startActivityForResult(), que retorna o URI de conteúdo da linha selecionada. O formato do URI é o URI de conteúdo da tabela com o LOOKUP_ID da linha anexado a ele. O app de contatos do dispositivo delega permissões de leitura e gravação a esse URI de conteúdo durante toda a atividade. Consulte o guia Fundamentos do provedor de conteúdo para mais detalhes.

Inserir um novo contato bruto Insert.ACTION N/A RawContacts.CONTENT_TYPE, tipo MIME para um conjunto de contatos brutos. Mostra a tela Adicionar contato do aplicativo de contatos do dispositivo. Os valores de extras que você adiciona à intent são mostrados. Se enviado com startActivityForResult(), o URI de conteúdo do contato bruto recém-adicionado é transmitido de volta ao método de callback onActivityResult() da sua atividade no argumento Intent, no campo "data". Para receber o valor, chame getData().
Editar um contato ACTION_EDIT CONTENT_LOOKUP_URI para o contato. Com a atividade do editor, o usuário pode editar qualquer um dos dados associados a esse contato. Contacts.CONTENT_ITEM_TYPE, um único contato. Exibe a tela “Edit Contact” no aplicativo de contatos. Os valores extras que você adiciona à intent são mostrados. Quando o usuário clica em Concluído para salvar as edições, a atividade volta ao primeiro plano.
Mostrar um seletor que também pode adicionar dados. ACTION_INSERT_OR_EDIT N/A CONTENT_ITEM_TYPE Essa intent sempre mostra a tela de seleção do app Contatos. O usuário pode escolher um contato para editar ou adicionar um novo. A tela de edição ou de adição aparece, dependendo da escolha do usuário, e os dados extras transmitidos na intent são mostrados. Se o app mostrar dados de contato, como um e-mail ou número de telefone, use essa intent para permitir que o usuário adicione os dados a um contato existente. contato,

Observação:não é necessário enviar um valor de nome nos extras dessa intent, porque o usuário sempre escolhe um nome existente ou adiciona um novo. Além disso, se você enviar um nome e o usuário escolher fazer uma edição, o app de contatos vai mostrar o nome enviado, substituindo o valor anterior. Se o usuário não perceber isso e salvar a edição, o valor antigo será perdido.

O app Contatos do dispositivo não permite excluir um contato bruto ou qualquer um dos dados dele com uma intent. Em vez disso, para excluir um contato bruto, use ContentResolver.delete() ou ContentProviderOperation.newDelete().

O snippet a seguir mostra como construir e enviar uma intent que insere um novo contato e dados brutos:

Kotlin

// Gets values from the UI
val name = contactNameEditText.text.toString()
val phone = contactPhoneEditText.text.toString()
val email = contactEmailEditText.text.toString()

val company = companyName.text.toString()
val jobtitle = jobTitle.text.toString()

/*
 * Demonstrates adding data rows as an array list associated with the DATA key
 */

// Defines an array list to contain the ContentValues objects for each row
val contactData = arrayListOf<ContentValues>()

/*
 * Defines the raw contact row
 */

// Sets up the row as a ContentValues object
val rawContactRow = ContentValues().apply {
    // Adds the account type and name to the row
    put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.type)
    put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.name)
}

// Adds the row to the array
contactData.add(rawContactRow)

/*
 * Sets up the phone number data row
 */

// Sets up the row as a ContentValues object
val phoneRow = ContentValues().apply {
    // Specifies the MIME type for this data row (all data rows must be marked by their type)
    put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

    // Adds the phone number and its type to the row
    put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
}

// Adds the row to the array
contactData.add(phoneRow)

/*
 * Sets up the email data row
 */

// Sets up the row as a ContentValues object
val emailRow = ContentValues().apply {
    // Specifies the MIME type for this data row (all data rows must be marked by their type)
    put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

    // Adds the email address and its type to the row
    put(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
}

// Adds the row to the array
contactData.add(emailRow)

// Creates a new intent for sending to the device's contacts application
val insertIntent = Intent(ContactsContract.Intents.Insert.ACTION).apply {
    // Sets the MIME type to the one expected by the insertion activity
    type = ContactsContract.RawContacts.CONTENT_TYPE

    // Sets the new contact name
    putExtra(ContactsContract.Intents.Insert.NAME, name)

    // Sets the new company and job title
    putExtra(ContactsContract.Intents.Insert.COMPANY, company)
    putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle)

    /*
    * Adds the array to the intent's extras. It must be a parcelable object in order to
    * travel between processes. The device's contacts app expects its key to be
    * Intents.Insert.DATA
    */
    putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData)
}

// Send out the intent to start the device's contacts app in its add contact activity.
startActivity(insertIntent)

Java

// Gets values from the UI
String name = contactNameEditText.getText().toString();
String phone = contactPhoneEditText.getText().toString();
String email = contactEmailEditText.getText().toString();

String company = companyName.getText().toString();
String jobtitle = jobTitle.getText().toString();

// Creates a new intent for sending to the device's contacts application
Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);

// Sets the MIME type to the one expected by the insertion activity
insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);

// Sets the new contact name
insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);

// Sets the new company and job title
insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);

/*
 * Demonstrates adding data rows as an array list associated with the DATA key
 */

// Defines an array list to contain the ContentValues objects for each row
ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();


/*
 * Defines the raw contact row
 */

// Sets up the row as a ContentValues object
ContentValues rawContactRow = new ContentValues();

// Adds the account type and name to the row
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType());
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName());

// Adds the row to the array
contactData.add(rawContactRow);

/*
 * Sets up the phone number data row
 */

// Sets up the row as a ContentValues object
ContentValues phoneRow = new ContentValues();

// Specifies the MIME type for this data row (all data rows must be marked by their type)
phoneRow.put(
        ContactsContract.Data.MIMETYPE,
        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
);

// Adds the phone number and its type to the row
phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);

// Adds the row to the array
contactData.add(phoneRow);

/*
 * Sets up the email data row
 */

// Sets up the row as a ContentValues object
ContentValues emailRow = new ContentValues();

// Specifies the MIME type for this data row (all data rows must be marked by their type)
emailRow.put(
        ContactsContract.Data.MIMETYPE,
        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
);

// Adds the email address and its type to the row
emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);

// Adds the row to the array
contactData.add(emailRow);

/*
 * Adds the array to the intent's extras. It must be a parcelable object in order to
 * travel between processes. The device's contacts app expects its key to be
 * Intents.Insert.DATA
 */
insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);

// Send out the intent to start the device's contacts app in its add contact activity.
startActivity(insertIntent);

Integridade dos dados

Como o repositório de contatos contém dados importantes e sensíveis que os usuários esperam que estejam corretos e atualizados, o provedor de contatos tem regras bem definidas para integridade de dados. É sua responsabilidade obedecer a essas regras ao modificar dados de contatos. As regras importantes estão listadas aqui:

Sempre adicione uma linha ContactsContract.CommonDataKinds.StructuredName para cada linha ContactsContract.RawContacts que você adicionar.
Uma linha ContactsContract.RawContacts sem uma linha ContactsContract.CommonDataKinds.StructuredName na tabela ContactsContract.Data pode causar problemas durante a agregação.
Sempre vincule novas linhas de ContactsContract.Data à linha de ContactsContract.RawContacts principal.
Uma linha ContactsContract.Data que não está vinculada a um ContactsContract.RawContacts não vai ficar visível no aplicativo de contatos do dispositivo e pode causar problemas com adaptadores de sincronização.
Altere dados somente para seus contatos brutos.
Lembre-se de que o provedor de contatos geralmente gerencia dados de vários tipos de conta/serviços on-line diferentes. Verifique se o aplicativo só modifica ou exclui dados de linhas que pertencem a você e se ele só insere dados com um tipo e nome de conta que você controla.
Sempre use as constantes definidas em ContactsContract e suas subclasses para autoridades, URIs de conteúdo, caminhos de URI, nomes de colunas, tipos MIME e valores TYPE.
Usar essas constantes ajuda a evitar erros. Você também vai receber avisos do compilador se alguma das constantes estiver descontinuada.

Linhas de dados personalizados

Ao criar e usar seus próprios tipos MIME personalizados, é possível inserir, editar, excluir e recuperar suas próprias linhas de dados na tabela ContactsContract.Data. Suas linhas são limitadas ao uso da coluna definida em ContactsContract.DataColumns, mas você pode mapear seus próprios nomes de coluna específicos do tipo para os nomes de coluna padrão. No aplicativo de contatos do dispositivo, os dados das suas linhas são mostrados, mas não podem ser editados ou excluídos, e os usuários não podem adicionar mais dados. Para permitir que os usuários modifiquem suas linhas de dados personalizados, você precisa fornecer uma atividade de editor no seu próprio aplicativo.

Para mostrar seus dados personalizados, forneça um arquivo contacts.xml que contenha um elemento <ContactsAccountType> e um ou mais elementos filhos <ContactsDataKind>. Isso é descrito em mais detalhes na seção <ContactsDataKind> element.

Para saber mais sobre tipos MIME personalizados, leia o guia Criar um provedor de conteúdo.

Adaptadores de sincronização do Provedor de contatos

O provedor de contatos foi projetado especificamente para processar a sincronização de dados de contatos entre um dispositivo e um serviço on-line. Isso permite que os usuários façam o download de dados atuais para um novo dispositivo e o upload de dados atuais para uma nova conta. A sincronização também garante que os usuários tenham os dados mais recentes em mãos, independente da origem das adições e mudanças. Outra vantagem da sincronização é que ela disponibiliza os dados de contatos mesmo quando o dispositivo não está conectado à rede.

Embora seja possível implementar a sincronização de várias maneiras, o sistema Android oferece um framework de sincronização de plug-in que automatiza as seguintes tarefas:

  • Verificação da disponibilidade da rede.
  • Agendamento e execução de sincronizações, com base nas preferências do usuário.
  • Reinicialização de sincronizações que foram interrompidas.

Para usar essa biblioteca, deve-se fornecer um plug-in do adaptador de sincronização. Cada adaptador de sincronização é exclusivo de um serviço e provedor de conteúdo, mas pode processar vários nomes de contas para o mesmo serviço. O framework também permite vários adaptadores de sincronização para o mesmo serviço e provedor.

Classes e arquivos do adaptador de sincronização

Você implementa um adaptador de sincronização como uma subclasse de AbstractThreadedSyncAdapter e o instala como parte de um aplicativo Android. O sistema aprende sobre o adaptador de sincronização com elementos no manifesto do aplicativo e com um arquivo XML especial apontado pelo manifesto. O arquivo XML define o tipo de conta do serviço on-line e a autoridade do provedor de conteúdo, que juntos identificam o adaptador de forma exclusiva. O adaptador de sincronização não é ativado até que o usuário adicione uma conta para o tipo de conta do adaptador e ative a sincronização para o provedor de conteúdo com que o adaptador se sincroniza. Nesse ponto, o sistema começa a gerenciar o adaptador, chamando-o conforme necessário para sincronizar entre o provedor de conteúdo e o servidor.

Observação:usar um tipo de conta como parte da identificação do adaptador de sincronização permite que o sistema detecte e agrupe adaptadores de sincronização que acessam serviços diferentes da mesma organização. Por exemplo, os adaptadores de sincronização para serviços on-line do Google têm o mesmo tipo de conta com.google. Quando os usuários adicionam uma Conta do Google aos dispositivos, todos os adaptadores de sincronização instalados para os Serviços do Google são listados juntos. Cada adaptador listado é sincronizado com um provedor de conteúdo diferente no dispositivo.

Como a maioria dos serviços exige que os usuários verifiquem a identidade antes de acessar dados, o sistema Android oferece um framework de autenticação semelhante ao framework de adaptador de sincronização, que é usado com frequência em conjunto com ele. O framework de autenticação usa autenticadores de plug-in que são subclasses de AbstractAccountAuthenticator. Um autenticador verifica a identidade do usuário nas seguintes etapas:

  1. Coleta o nome, a senha ou informações semelhantes do usuário (as credenciais do usuário).
  2. Envia as credenciais para o serviço.
  3. Examina a resposta do serviço.

Se o serviço aceitar as credenciais, o autenticador poderá armazená-las para uso posterior. Devido ao framework de autenticação de plug-in, o AccountManager pode fornecer acesso a qualquer authtoken que um autenticador ofereça suporte e escolha expor, como authtokens do OAuth2.

Embora as autenticações não sejam necessárias, a maioria dos serviços de contato as usam. No entanto, não é necessário usar a estrutura de autenticação do Android para fazer isso.

Implementação do adaptador de sincronização

Para implementar um adaptador de sincronização para o Provedor de contatos, comece criando um aplicativo Android que contenha o seguinte:

Um componente Service que responde a solicitações do sistema para vincular ao adaptador de sincronização.
Quando o sistema quer executar uma sincronização, ele chama o método onBind() do serviço para receber um IBinder para o adaptador de sincronização. Isso permite que o sistema faça chamadas entre processos para os métodos do adaptador.
O adaptador de sincronização real, implementado como uma subclasse concreta de AbstractThreadedSyncAdapter.
Essa classe faz o trabalho de baixar dados do servidor, fazer upload de dados do dispositivo e resolver conflitos. O principal trabalho do adaptador é feito no método onPerformSync(). Essa classe deve ser instanciada como um singleton.
Uma subclasse de Application.
Essa classe atua como uma fábrica para o singleton do adaptador de sincronização. Use o método onCreate() para instanciar o adaptador de sincronização e forneça um método "getter" estático para retornar o singleton ao método onBind() do serviço do adaptador de sincronização.
Opcional:um componente Service que responde a solicitações do sistema para autenticação do usuário.
AccountManager inicia esse serviço para começar o processo de autenticação. O método onCreate() do serviço instancia um objeto autenticador. Quando o sistema quer autenticar uma conta de usuário para o adaptador de sincronização do aplicativo, ele chama o método onBind() do serviço para receber um IBinder para o autenticador. Isso permite que o sistema faça chamadas entre processos para os métodos do autenticador.
Opcional:uma subclasse concreta de AbstractAccountAuthenticator que processa solicitações de autenticação.
Essa classe fornece métodos que o AccountManager invoca para autenticar as credenciais do usuário com o servidor. Os detalhes do processo de autenticação variam muito de acordo com a tecnologia de servidor em uso. Consulte a documentação do software do servidor para saber mais sobre autenticação.
Os arquivos XML que definem o adaptador de sincronização e o autenticador para o sistema.
Os componentes do adaptador de sincronização e do serviço de autenticação descritos anteriormente são definidos em <service> elementos no manifesto do aplicativo. Esses elementos contêm elementos filhos <meta-data> que fornecem dados específicos ao sistema:
  • O elemento <meta-data> para o serviço do adaptador de sincronização aponta para o arquivo XML res/xml/syncadapter.xml. Por sua vez, esse arquivo especifica um URI para o serviço da Web que será sincronizado com o provedor de contatos e um tipo de conta para o serviço da Web.
  • Opcional:o elemento <meta-data> do autenticador aponta para o arquivo XML res/xml/authenticator.xml. Por sua vez, esse arquivo especifica o tipo de conta compatível com esse autenticador, bem como os recursos de interface que aparecem durante o processo de autenticação. O tipo de conta especificado neste elemento precisa ser o mesmo especificado para o adaptador de sincronização.

Dados de streams sociais

As tabelas android.provider.ContactsContract.StreamItems e android.provider.ContactsContract.StreamItemPhotos gerenciam dados recebidos de redes sociais. Você pode escrever um adaptador de sincronização que adicione dados de stream da sua própria rede a essas tabelas ou ler dados de stream dessas tabelas e mostrar no seu próprio aplicativo, ou ambos. Com esses recursos, seus serviços e aplicativos de redes sociais podem ser integrados à experiência de redes sociais do Android.

Textos de streams sociais

Itens de stream sempre são associados a um contato bruto. O android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID vincula ao valor _ID do contato bruto. O tipo e o nome da conta do contato bruto também são armazenados na linha do item de stream.

Armazene os dados do seu stream nas colunas a seguir:

android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
Obrigatório. O tipo de conta do usuário para o contato bruto associado a este item de stream. Lembre-se de definir esse valor ao inserir um item de stream.
android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
Obrigatório. O nome da conta do usuário para o contato bruto associado a este item de stream. Lembre-se de definir esse valor ao inserir um item de stream.
Colunas identificadoras
Obrigatório. Insira as seguintes colunas de identificador ao inserir um item de stream:
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: o valor android.provider.BaseColumns#_ID do contato associado a este item de stream.
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: o valor android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY do contato a que este item de fluxo está associado.
  • android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: o valor android.provider.BaseColumns#_ID do contato bruto associado a este item de stream.
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
Opcional. Armazena informações resumidas que podem ser exibidas no início do item de stream.
android.provider.ContactsContract.StreamItemsColumns#TEXT
O texto do item de stream, seja o conteúdo postado pela fonte do item, ou uma descrição de alguma ação que gerou o item de stream. Essa coluna pode conter qualquer formatação e imagens de recursos incorporadas que podem ser renderizadas pelo fromHtml(). O provedor pode truncar ou usar reticências em conteúdo longo, mas vai tentar evitar a quebra de tags.
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
Uma string de texto que contém o momento em que o item do stream foi inserido ou atualizado, na forma de milissegundos desde a época. Os aplicativos que inserem ou atualizam itens de stream são responsáveis por manter essa coluna. Ela não é mantida automaticamente pelo provedor de contatos.

Para mostrar informações de identificação dos itens do stream, use android.provider.ContactsContract.StreamItemsColumns#RES_ICON, android.provider.ContactsContract.StreamItemsColumns#RES_LABEL e android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE para vincular a recursos no seu aplicativo.

A tabela android.provider.ContactsContract.StreamItems também contém as colunas android.provider.ContactsContract.StreamItemsColumns#SYNC1 a android.provider.ContactsContract.StreamItemsColumns#SYNC4 para uso exclusivo de adaptadores de sincronização.

Fotos de streams sociais

A tabela android.provider.ContactsContract.StreamItemPhotos armazena fotos associadas a um item de stream. A coluna android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID da tabela vincula valores na coluna _ID da tabela android.provider.ContactsContract.StreamItems. As referências de fotos são armazenadas na tabela nestas colunas:

Coluna android.provider.ContactsContract.StreamItemPhotos#PHOTO (um BLOB).
Uma representação binária da foto, redimensionada pelo provedor para armazenamento e exibição. Essa coluna está disponível para compatibilidade com versões anteriores do provedor de contatos que a usavam para armazenar fotos. No entanto, na versão atual, não use essa coluna para armazenar fotos. Em vez disso, use android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID ou android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI (ambos descritos nos pontos a seguir) para armazenar fotos em um arquivo. Agora, essa coluna contém uma miniatura da foto, que está disponível para leitura.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
Um identificador numérico de uma foto para um contato bruto. Anexe esse valor à constante DisplayPhoto.CONTENT_URI para receber um URI de conteúdo que aponta para um único arquivo de foto e chame openAssetFileDescriptor() para receber um identificador do arquivo de foto.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
Um URI de conteúdo que aponta diretamente para o arquivo da foto representada por esta linha. Chame openAssetFileDescriptor() com esse URI para receber um identificador do arquivo de foto.

Uso de tabelas de streams sociais

Essas tabelas funcionam como as outras tabelas principais do Provedor de Contatos, exceto que:

  • exigem permissões de acesso adicionais. Para ler esses dados, o aplicativo precisa ter a permissão android.Manifest.permission#READ_SOCIAL_STREAM. Para modificá-los, seu aplicativo precisa ter a permissão android.Manifest.permission#WRITE_SOCIAL_STREAM.
  • Para a tabela android.provider.ContactsContract.StreamItems, o número de linhas armazenadas para cada contato bruto é limitado. Quando esse limite é atingido, o provedor de contatos abre espaço para novas linhas de itens de stream excluindo automaticamente as linhas com o android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP mais antigo. Para saber o limite, faça uma consulta ao URI de conteúdo android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI. Você pode deixar todos os argumentos, exceto o URI de conteúdo, definidos como null. A consulta retorna um cursor que contém uma única linha, com a única coluna android.provider.ContactsContract.StreamItems#MAX_ITEMS.

A classe android.provider.ContactsContract.StreamItems.StreamItemPhotos define uma subtabela de android.provider.ContactsContract.StreamItemPhotos que contém as linhas de fotos de um único item de stream.

Interações de streams sociais

Os dados de stream social gerenciados pelo provedor de contatos, em conjunto com o aplicativo de contatos do dispositivo, oferecem uma maneira eficiente de conectar seu sistema de redes sociais com os contatos atuais. Os seguintes recursos estão disponíveis:

  • Ao sincronizar seu serviço de rede social com o provedor de contatos usando um adaptador de sincronização, é possível recuperar a atividade recente dos contatos de um usuário e armazená-la nas tabelas android.provider.ContactsContract.StreamItems e android.provider.ContactsContract.StreamItemPhotos para uso posterior.
  • Além da sincronização regular, você pode acionar o adaptador para recuperar dados adicionais quando o usuário selecionar um contato para visualizar. Isso permite que seu adaptador de sincronização recupere fotos de alta resolução e os itens de stream mais recentes do contato.
  • Ao registrar uma notificação com o aplicativo de contatos do dispositivo e o provedor de contatos, você pode receber uma intent quando um contato é visualizado e, nesse momento, atualizar o status do contato no seu serviço. Essa abordagem pode ser mais rápida e usar menos largura de banda do que uma sincronização completa com um adaptador de sincronização.
  • Os usuários podem adicionar um contato ao seu serviço de rede social enquanto olham para ele no aplicativo de contatos do dispositivo. Isso é feito com o recurso "convidar contato", que é ativado com uma combinação de uma atividade que adiciona um contato existente à sua rede e um arquivo XML que fornece o aplicativo de contatos do dispositivo e o provedor de contatos com os detalhes do seu aplicativo.

A sincronização regular de itens de stream com o provedor de contatos é igual a outras sincronizações. Para saber mais sobre a sincronização, consulte a seção Adaptadores de sincronização do provedor de contatos. O registro de notificações e o convite de contatos são abordados nas duas seções a seguir.

Registro para processar exibições de redes sociais

Para registrar seu adaptador de sincronização e receber notificações quando o usuário visualizar um contato gerenciado por ele:

  1. Crie um arquivo chamado contacts.xml no diretório res/xml/ do projeto. Se você já tiver esse arquivo, pule esta etapa.
  2. Nesse arquivo, adicione o elemento <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Se esse elemento já existir, pule esta etapa.
  3. Para registrar um serviço que é notificado quando o usuário abre a página de detalhes de um contato no aplicativo de contatos do dispositivo, adicione o atributo viewContactNotifyService="serviceclass" ao elemento, em que serviceclass é o nome de classe totalmente qualificado do serviço que deve receber a intent do aplicativo de contatos do dispositivo. Para o serviço de notificação, use uma classe que estenda IntentService para permitir que o serviço receba intents. Os dados na intent recebida contêm o URI de conteúdo do contato bruto em que o usuário clicou. No serviço de notificação, é possível vincular e chamar o adaptador de sincronização para atualizar os dados do contato bruto.

Para registrar uma atividade a ser chamada quando o usuário clica em um item de stream, em uma foto ou em ambos:

  1. Crie um arquivo chamado contacts.xml no diretório res/xml/ do projeto. Se você já tiver esse arquivo, pule esta etapa.
  2. Nesse arquivo, adicione o elemento <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Se esse elemento já existir, pule esta etapa.
  3. Para registrar uma das suas atividades para processar o clique do usuário em um item de stream no app de contatos do dispositivo, adicione o atributo viewStreamItemActivity="activityclass" ao elemento, em que activityclass é o nome de classe totalmente qualificado da atividade que deve receber a intent do app de contatos do dispositivo.
  4. Para registrar uma das suas atividades para processar o clique do usuário em uma foto de stream no aplicativo de contatos do dispositivo, adicione o atributo viewStreamItemPhotoActivity="activityclass" ao elemento, em que activityclass é o nome de classe totalmente qualificado da atividade que deve receber a intent do aplicativo de contatos do dispositivo.

O elemento <ContactsAccountType> é descrito em mais detalhes na seção Elemento<ContactsAccountType>.

O intent recebido contém o URI de conteúdo do item ou foto em que o usuário clicou. Para ter atividades separadas para itens de texto e para fotos, use os dois atributos no mesmo arquivo.

Interação com o serviço de redes sociais

Os usuários não precisam sair do app de contatos do dispositivo para convidar um contato para seu site de rede social. Em vez disso, você pode fazer com que o app de contatos do dispositivo envie uma intent para convidar o contato para uma das suas atividades. Para definir essa configuração:

  1. Crie um arquivo chamado contacts.xml no diretório res/xml/ do projeto. Se você já tiver esse arquivo, pule esta etapa.
  2. Nesse arquivo, adicione o elemento <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Se esse elemento já existir, pule esta etapa.
  3. Adicione os seguintes atributos:
    • inviteContactActivity="activityclass"
    • inviteContactActionLabel="@string/invite_action_label"
    O valor activityclass é o nome de classe totalmente qualificado da atividade que vai receber a intent. O valor invite_action_label é uma string de texto que aparece no menu Adicionar conexão do app de contatos do dispositivo.

Observação:ContactsSource é um nome de tag descontinuado para ContactsAccountType.

Referência do contacts.xml

O arquivo contacts.xml contém elementos XML que controlam a interação do adaptador de sincronização e do aplicativo com o app Contatos e o provedor de contatos. Esses elementos são descritos nas seções a seguir.

Elemento <ContactsAccountType>

O elemento <ContactsAccountType> controla a interação do seu aplicativo com o app de contatos. Ele tem a seguinte sintaxe:

<ContactsAccountType
        xmlns:android="http://schemas.android.com/apk/res/android"
        inviteContactActivity="activity_name"
        inviteContactActionLabel="invite_command_text"
        viewContactNotifyService="view_notify_service"
        viewGroupActivity="group_view_activity"
        viewGroupActionLabel="group_action_text"
        viewStreamItemActivity="viewstream_activity_name"
        viewStreamItemPhotoActivity="viewphotostream_activity_name">

contido em:

res/xml/contacts.xml

pode conter:

<ContactsDataKind>

Descrição:

Declara componentes do Android e rótulos da interface que permitem aos usuários convidar um dos contatos para uma rede social, notificar os usuários quando um dos feeds de redes sociais é atualizado e assim por diante.

O prefixo de atributo android: não é necessário para os atributos de <ContactsAccountType>.

Atributos:

inviteContactActivity
O nome completo da classe da atividade no seu aplicativo que você quer ativar quando o usuário selecionar Adicionar conexão no app de contatos do dispositivo.
inviteContactActionLabel
Uma string de texto exibida para a atividade especificada em inviteContactActivity, no menu Adicionar conexão. Por exemplo, você pode usar a string "Seguir na minha rede". Você pode usar um identificador de recurso de string para esse rótulo.
viewContactNotifyService
O nome de classe totalmente qualificado de um serviço no seu aplicativo que deve receber notificações quando o usuário visualiza um contato. Essa notificação é enviada pelo app de contatos do dispositivo e permite que seu aplicativo adie operações com uso intenso de dados até que sejam necessárias. Por exemplo, seu aplicativo pode responder a essa notificação lendo e mostrando a foto em alta resolução do contato e os itens mais recentes do stream social. Esse recurso é descrito em mais detalhes na seção Interações com o fluxo social.
viewGroupActivity
O nome de classe totalmente qualificado de uma atividade no seu aplicativo que pode mostrar informações do grupo. Quando o usuário clica no rótulo do grupo no aplicativo de contatos do dispositivo, a UI dessa atividade é exibida.
viewGroupActionLabel
O rótulo que o aplicativo de contatos mostra para um controle da interface que permite ao usuário consultar grupos no aplicativo.

Um identificador de recurso de string é permitido para esse atributo.

viewStreamItemActivity
O nome completo da classe de uma atividade no seu aplicativo que o app de contatos do dispositivo inicia quando o usuário clica em um item de stream de um contato bruto.
viewStreamItemPhotoActivity
O nome completo da classe de uma atividade no seu aplicativo que o app de contatos do dispositivo inicia quando o usuário clica em uma foto no item do stream de um contato bruto.

Elemento <ContactsDataKind>

O elemento <ContactsDataKind> controla a exibição das linhas de dados personalizados do seu aplicativo na interface do usuário do app Contatos. Ele tem a seguinte sintaxe:

<ContactsDataKind
        android:mimeType="MIMEtype"
        android:icon="icon_resources"
        android:summaryColumn="column_name"
        android:detailColumn="column_name">

contido em:

<ContactsAccountType>

Descrição:

Use esse elemento para que o aplicativo de contatos mostre o conteúdo de uma linha de dados personalizada como parte dos detalhes de um contato bruto. Cada elemento filho <ContactsDataKind> de <ContactsAccountType> representa um tipo de linha de dados personalizada que o adaptador de sincronização adiciona à tabela ContactsContract.Data. Adicione um elemento <ContactsDataKind> para cada tipo MIME personalizado que você usa. Não é necessário adicionar o elemento se você tiver uma linha de dados personalizada para a qual não quer mostrar dados.

Atributos:

android:mimeType
O tipo MIME personalizado que você definiu para um dos tipos de linha de dados personalizados na tabela ContactsContract.Data. Por exemplo, o valor vnd.android.cursor.item/vnd.example.locationstatus pode ser um tipo MIME personalizado para uma linha de dados que registra o último local conhecido de um contato.
android:icon
Um recurso drawable do Android que o aplicativo de contatos mostra ao lado dos seus dados. Use isso para indicar ao usuário que os dados vêm do seu serviço.
android:summaryColumn
O nome da coluna para o primeiro de dois valores recuperados da linha de dados. O valor é mostrado como a primeira linha da entrada dessa linha de dados. A primeira linha pode ser usada como um resumo dos dados, mas isso é opcional. Consulte também android:detailColumn.
android:detailColumn
O nome da coluna do segundo de dois valores extraídos da linha de dados. O valor é mostrado como a segunda linha da entrada dessa linha de dados. Consulte também android:summaryColumn.

Recursos adicionais do Provedor de contatos

Além dos principais recursos descritos nas seções anteriores, o Provedor de contatos oferece estes recursos úteis para trabalhar com dados de contatos:

  • Grupos de contatos
  • Recursos de foto

Grupos de contatos

O provedor de contatos pode rotular coleções de contatos relacionados com dados de grupo. Se o servidor associado a uma conta de usuário quiser manter grupos, o adaptador de sincronização do tipo de conta precisará transferir dados de grupos entre o provedor de contatos e o servidor. Quando os usuários adicionam um novo contato ao servidor e o colocam em um novo grupo, o adaptador de sincronização precisa adicionar o novo grupo à tabela ContactsContract.Groups. O grupo ou os grupos a que um contato bruto pertence são armazenados na tabela ContactsContract.Data, usando o tipo MIME ContactsContract.CommonDataKinds.GroupMembership.

Se você estiver criando um adaptador de sincronização que vai adicionar dados de contato brutos do servidor ao provedor de contatos e não estiver usando grupos, informe ao provedor para tornar seus dados visíveis. No código executado quando um usuário adiciona uma conta ao dispositivo, atualize a linha ContactsContract.Settings que o provedor de contatos adiciona à conta. Nessa linha, defina o valor da coluna Settings.UNGROUPED_VISIBLE como 1. Quando você faz isso, o provedor de contatos sempre torna os dados de contatos visíveis, mesmo que você não use grupos.

Fotos de contatos

A tabela ContactsContract.Data armazena fotos como linhas com o tipo MIME Photo.CONTENT_ITEM_TYPE. A coluna CONTACT_ID da linha está vinculada à coluna _ID do contato bruto a que ela pertence. A classe ContactsContract.Contacts.Photo define uma sub-tabela de ContactsContract.Contacts que contém informações da foto principal de um contato, que é a foto principal do contato bruto principal. Da mesma forma, a classe ContactsContract.RawContacts.DisplayPhoto define uma sub-tabela de ContactsContract.RawContacts que contém informações da foto principal de um contato bruto.

A documentação de referência para ContactsContract.Contacts.Photo e ContactsContract.RawContacts.DisplayPhoto contém exemplos de recuperação de informações de fotos. Não há uma classe de conveniência para recuperar a miniatura principal de um contato bruto, mas você pode enviar uma consulta à tabela ContactsContract.Data, selecionando o _ID, o Photo.CONTENT_ITEM_TYPE e a coluna IS_PRIMARY do contato bruto para encontrar a linha da foto principal dele.

Dados de streams sociais de uma pessoa também podem conter fotos. Elas são armazenadas na tabela android.provider.ContactsContract.StreamItemPhotos, que é descrita em mais detalhes na seção Fotos de stream social.