Contacts Provider

Contacts Provider est un composant Android puissant et flexible qui gère le dépôt central de données sur les personnes de l'appareil. Le fournisseur de contacts est la source des données que vous voyez dans l'application Contacts de l'appareil. Vous pouvez également accéder à ses données dans votre propre application et les transférer entre l'appareil et les services en ligne. Le fournisseur gère un large éventail de sources de données et essaie de gérer autant de données que possible pour chaque personne, ce qui rend son organisation complexe. C'est pourquoi l'API du fournisseur inclut un vaste ensemble de classes et d'interfaces de contrat qui facilitent la récupération et la modification des données.

Ce guide décrit les éléments suivants:

  • Structure de base du fournisseur
  • Comment récupérer des données auprès du fournisseur
  • Modifier les données du fournisseur
  • Écrire un adaptateur de synchronisation pour synchroniser les données de votre serveur vers le fournisseur de contacts

Dans ce guide, nous partons du principe que vous connaissez les principes de base des fournisseurs de contenu Android. Pour en savoir plus sur les fournisseurs de contenu Android, consultez le guide Principes de base des fournisseurs de contenu.

Organisation du fournisseur de contacts

Contacts Provider est un composant de fournisseur de contenu Android. Elle gère trois types de données sur une personne, chacune correspondant à une table proposée par le fournisseur, comme illustré dans la figure 1:

Figure 1 : Structure de la table du fournisseur de contacts.

Les trois tableaux sont communément appelés par les noms de leurs classes de contrat. Les classes définissent des constantes pour les URI de contenu, les noms de colonne et les valeurs de colonne utilisées par les tables:

ContactsContract.Contacts table
Lignes représentant différentes personnes, sur la base des agrégations de lignes de contact brutes.
ContactsContract.RawContacts table
Lignes contenant un résumé des données d'une personne, spécifiques à un compte utilisateur et à un type.
ContactsContract.Data table
Lignes contenant les détails du contact brut, tels que les adresses e-mail ou les numéros de téléphone.

Les autres tables représentées par des classes de contrat dans ContactsContract sont des tables auxiliaires utilisées par le fournisseur de contacts pour gérer ses opérations ou prendre en charge des fonctions spécifiques dans les contacts ou les applications de téléphonie de l'appareil.

Contacts bruts

Un contact brut représente les données d'une personne provenant d'un seul type de compte et d'un seul nom de compte. Étant donné que le fournisseur de contacts autorise plusieurs services en ligne comme source de données pour une personne, il autorise plusieurs contacts bruts pour la même personne. Plusieurs contacts bruts permettent également à un utilisateur de combiner les données d'une personne provenant de plusieurs comptes du même type de compte.

La plupart des données d'un contact brut ne sont pas stockées dans la table ContactsContract.RawContacts. Au lieu de cela, il est stocké dans une ou plusieurs lignes de la table ContactsContract.Data. Chaque ligne de données comporte une colonne Data.RAW_CONTACT_ID contenant la valeur RawContacts._ID de la ligne ContactsContract.RawContacts parente.

Colonnes brutes importantes concernant les contacts

Les colonnes importantes de la table ContactsContract.RawContacts sont répertoriées dans le tableau 1. Veuillez lire les remarques qui suivent après le tableau:

Tableau 1. Colonnes de contacts brutes importantes.

Nom de la colonne Utiliser Notes
ACCOUNT_NAME Nom du type de compte qui est la source de ce contact brut. Par exemple, le nom d'un compte Google est l'une des adresses Gmail du propriétaire de l'appareil. Pour plus d'informations, consultez la section suivante sur ACCOUNT_TYPE. Le format de ce nom est spécifique au type de compte associé. Il ne s'agit pas nécessairement d'une adresse e-mail.
ACCOUNT_TYPE Type de compte qui est la source de ce contact brut. Par exemple, le type de compte d'un compte Google est com.google. Complétez toujours votre type de compte avec un identifiant de domaine pour un domaine que vous possédez ou contrôlez. Votre compte sera ainsi unique. Un type de compte qui fournit des données de contacts est généralement associé à un adaptateur de synchronisation qui se synchronise avec Contacts Provider.
DELETED Indicateur "supprimé" pour un contact brut Cette option permet au fournisseur de contacts de gérer la ligne en interne jusqu'à ce que les adaptateurs de synchronisation puissent la supprimer de leurs serveurs, puis la supprimer du dépôt.

Notes

Voici quelques remarques importantes concernant la table ContactsContract.RawContacts:

  • Le nom d'un contact brut n'est pas stocké dans sa ligne dans ContactsContract.RawContacts. Au lieu de cela, il est stocké dans la table ContactsContract.Data, sur une ligne ContactsContract.CommonDataKinds.StructuredName. Un contact brut ne possède qu'une seule ligne de ce type dans la table ContactsContract.Data.
  • Attention:Pour utiliser vos propres données de compte dans une ligne de contact brute, vous devez d'abord les enregistrer auprès de AccountManager. Pour ce faire, invitez les utilisateurs à ajouter le type et le nom de leur compte à la liste des comptes. Si vous ne le faites pas, le fournisseur de contacts supprimera automatiquement votre ligne de contacts bruts.

    Par exemple, si vous souhaitez que votre application conserve les données des contacts de votre service Web avec le domaine com.example.dataservice et que le compte de l'utilisateur pour votre service est becky.sharp@dataservice.example.com, celui-ci doit d'abord ajouter le "type" (com.example.dataservice) et le nom du compte (becky.smart@dataservice.example.com) avant que votre application puisse ajouter des lignes de contacts brutes. Vous pouvez expliquer cette exigence à l'utilisateur dans la documentation, ou lui demander d'ajouter le type et le nom, ou les deux. Les types et les noms de comptes sont décrits plus en détail dans la section suivante.

Sources des données brutes sur les contacts

Pour comprendre le fonctionnement des contacts bruts, prenons l'exemple de l'utilisatrice "Emily Dickinson", qui a défini les trois comptes utilisateur suivants sur son appareil:

  • emily.dickinson@gmail.com
  • emilyd@gmail.com
  • Compte Twitter "belle_of_amherst"

Cet utilisateur a activé la synchronisation des contacts pour ces trois comptes dans les paramètres Accounts.

Supposons qu'Emily Dickinson ouvre une fenêtre de navigateur, se connecte à Gmail en tant que emily.dickinson@gmail.com, ouvre Contacts et ajoute "Thomas Higginson". Par la suite, elle se connecte à Gmail en tant que emilyd@gmail.com et envoie un e-mail à "Thomas Higginson", qui l'ajoute automatiquement en tant que contact. Elle suit également "colonel_tom" (ID Twitter de Thomas Higginson) sur Twitter.

Suite à ce travail, le fournisseur de contacts crée trois contacts bruts:

  1. Contact brut pour "Thomas Higginson" associé à emily.dickinson@gmail.com. Le type de compte utilisateur est Google.
  2. Un deuxième contact brut pour "Thomas Higginson" associé à emilyd@gmail.com. Le type de compte utilisateur est également Google. Il existe un deuxième contact brut, même si le nom est identique à l'ancien, car la personne a été ajoutée pour un autre compte utilisateur.
  3. Un troisième contact brut pour "Thomas Higginson" associé à "belle_of_amherst". Le type de compte utilisateur est Twitter.

Données

Comme indiqué précédemment, les données d'un contact brut sont stockées dans une ligne ContactsContract.Data associée à la valeur _ID du contact brut. Un même contact brut peut ainsi avoir plusieurs instances du même type de données, telles que des adresses e-mail ou des numéros de téléphone. Par exemple, si "Thomas Higginson" pour emilyd@gmail.com (la ligne de contacts bruts de Thomas Higginson associée au compte Google emilyd@gmail.com) possède une adresse e-mail personnelle thigg@gmail.com et une adresse e-mail professionnelle thomas.higginson@gmail.com, le fournisseur de contacts stocke les deux lignes d'adresse e-mail et les associe au contact brut.

Notez que différents types de données sont stockés dans cette seule table. Les lignes concernant le nom à afficher, le numéro de téléphone, l'adresse e-mail, l'adresse postale, la photo et les détails du site Web se trouvent dans la table ContactsContract.Data. Pour vous aider à gérer ce mode, la table ContactsContract.Data comporte certaines colonnes avec des noms descriptifs et d'autres avec des noms génériques. Le contenu d'une colonne "nom descriptif" a la même signification quel que soit le type de données de la ligne, tandis que le contenu d'une colonne "nom générique" a différentes significations selon le type de données.

Noms de colonnes descriptifs

Voici quelques exemples de noms de colonnes descriptifs:

RAW_CONTACT_ID
Valeur de la colonne _ID du contact brut pour ces données.
MIMETYPE
Type des données stockées dans cette ligne, exprimé sous la forme d'un type MIME personnalisé. Le fournisseur de contacts utilise les types MIME définis dans les sous-classes de ContactsContract.CommonDataKinds. Ces types MIME sont Open Source et peuvent être utilisés par toute application ou tout adaptateur de synchronisation fonctionnant avec le fournisseur de contacts.
IS_PRIMARY
Si ce type de ligne de données peut apparaître plusieurs fois pour un contact brut, la colonne IS_PRIMARY signale la ligne de données qui contient les données principales du type. Par exemple, si l'utilisateur appuie de manière prolongée sur le numéro de téléphone d'un contact et sélectionne Définir par défaut, la colonne IS_PRIMARY de la ligne ContactsContract.Data contenant ce numéro est définie sur une valeur non nulle.

Noms de colonnes génériques

15 colonnes génériques nommées DATA1 à DATA15 sont en disponibilité générale et quatre colonnes génériques supplémentaires (SYNC1 à SYNC4) qui ne doivent être utilisées que par les adaptateurs de synchronisation. Les constantes des noms de colonne génériques fonctionnent toujours, quel que soit le type de données contenues dans la ligne.

La colonne DATA1 est indexée. Le fournisseur de contacts utilise toujours cette colonne pour les données qui, selon le fournisseur, seront la cible la plus fréquente d'une requête. Par exemple, dans une ligne d'e-mail, cette colonne contient l'adresse e-mail réelle.

Par convention, la colonne DATA15 est réservée au stockage de données BLOB (Binary Large Object), telles que les vignettes de photos.

Noms de colonnes spécifiques au type

Pour faciliter l'utilisation des colonnes pour un type de ligne particulier, Contacts Provider fournit également des constantes de nom de colonne spécifiques au type, définies dans les sous-classes de ContactsContract.CommonDataKinds. Les constantes affectent simplement le même nom de colonne sous un autre nom de constante, ce qui vous permet d'accéder aux données d'une ligne d'un type particulier.

Par exemple, la classe ContactsContract.CommonDataKinds.Email définit des constantes de nom de colonne spécifiques au type pour une ligne ContactsContract.Data de type MIME Email.CONTENT_ITEM_TYPE. La classe contient la constante ADDRESS pour la colonne d'adresse e-mail. La valeur réelle de ADDRESS est "data1", qui est identique au nom générique de la colonne.

Attention:N'ajoutez pas vos propres données personnalisées à la table ContactsContract.Data à l'aide d'une ligne associée à l'un des types MIME prédéfinis par le fournisseur. Sinon, vous risquez de perdre les données ou de provoquer un dysfonctionnement du fournisseur. Par exemple, vous ne devez pas ajouter de ligne avec le type MIME Email.CONTENT_ITEM_TYPE contenant un nom d'utilisateur au lieu d'une adresse e-mail dans la colonne DATA1. Si vous utilisez votre propre type MIME personnalisé pour la ligne, vous êtes libre de définir vos propres noms de colonnes spécifiques au type et d'utiliser les colonnes comme vous le souhaitez.

La figure 2 montre comment les colonnes descriptives et les colonnes de données apparaissent dans une ligne ContactsContract.Data, et comment les noms de colonnes spécifiques au type "superposent" les noms de colonnes génériques

Correspondance entre les noms de colonnes spécifiques au type et les noms de colonnes génériques

Figure 2. Noms de colonnes spécifiques au type et noms de colonnes génériques.

Classes de noms de colonnes spécifiques au type

Le tableau 2 liste les classes de noms de colonnes spécifiques au type les plus couramment utilisées:

Tableau 2. Classes de noms de colonnes spécifiques au type

Classe Mapping Type de données Notes
ContactsContract.CommonDataKinds.StructuredName Données de nom du contact brut associé à cette ligne de données. Un contact brut ne comporte qu'une seule de ces lignes.
ContactsContract.CommonDataKinds.Photo Photo principale du contact brut associé à cette ligne de données. Un contact brut ne comporte qu'une seule de ces lignes.
ContactsContract.CommonDataKinds.Email Adresse e-mail du contact brut associé à cette ligne de données. Un contact brut peut avoir plusieurs adresses e-mail.
ContactsContract.CommonDataKinds.StructuredPostal Adresse postale du contact brut associé à cette ligne de données. Un contact brut peut avoir plusieurs adresses postales.
ContactsContract.CommonDataKinds.GroupMembership Identifiant qui associe le contact brut à l'un des groupes du fournisseur de contacts. Les groupes sont une fonctionnalité facultative d'un type de compte et d'un nom de compte. Ils sont décrits plus en détail dans la section Groupes de contacts.

Contacts

Le fournisseur de contacts combine les lignes de contacts brutes de tous les types et noms de comptes pour former un contact. Cela facilite l'affichage et la modification de toutes les données qu'un utilisateur a collectées à son sujet. Le fournisseur de contacts gère la création de nouvelles lignes de contacts et l'agrégation des contacts bruts avec une ligne de contact existante. Ni les applications ni les adaptateurs de synchronisation ne sont autorisés à ajouter des contacts, et certaines colonnes d'une ligne de contacts sont en lecture seule.

Remarque:Si vous essayez d'ajouter un contact au fournisseur de contacts avec un insert(), vous obtiendrez une exception UnsupportedOperationException. Si vous essayez de mettre à jour une colonne répertoriée comme "lecture seule", la mise à jour est ignorée.

Contacts Provider crée un contact en réponse à l'ajout d'un nouveau contact brut qui ne correspond à aucun contact existant. Le fournisseur procède également si les données d'un contact brut existant changent et ne correspondent plus au contact auquel il était précédemment associé. Si une application ou un adaptateur de synchronisation crée un contact brut correspondant à un contact existant, ce nouveau contact brut est agrégé au contact existant.

Le fournisseur de contacts associe une ligne de contact à ses lignes de contact brutes à la colonne _ID de la ligne de contact dans la table Contacts. La colonne CONTACT_ID de la table de contacts bruts ContactsContract.RawContacts contient des valeurs _ID pour la ligne de contacts associée à chaque ligne de contacts bruts.

La table ContactsContract.Contacts comporte également la colonne LOOKUP_KEY, qui est un lien "permanent" vers la ligne de contact. Étant donné que le fournisseur de contacts gère les contacts automatiquement, il peut modifier la valeur _ID d'une ligne de contact en réponse à une agrégation ou à une synchronisation. Même dans ce cas, l'URI de contenu CONTENT_LOOKUP_URI combiné au LOOKUP_KEY du contact pointe toujours vers la ligne du contact. Vous pouvez donc utiliser LOOKUP_KEY pour conserver les liens vers les contacts "favoris", et ainsi de suite. Cette colonne a son propre format qui n'est pas lié à celui de la colonne _ID.

La figure 3 montre comment les trois tableaux principaux sont liés les uns aux autres.

Tableaux principaux du fournisseur de contacts

Figure 3. Relations entre les tables Contacts, Contacts bruts et Détails.

Attention : Si vous publiez votre application sur le Google Play Store, ou si elle est installée sur un appareil équipé d'Android 10 (niveau d'API 29) ou version ultérieure, n'oubliez pas qu'un ensemble limité de champs et de méthodes de données des contacts est obsolète.

Dans les conditions mentionnées, le système efface régulièrement toutes les valeurs écrites dans ces champs de données:

Les API utilisées pour définir les champs de données ci-dessus sont également obsolètes:

En outre, les champs suivants n'affichent plus les contacts fréquents. Notez que certains de ces champs n'influencent le classement des contacts que lorsque ceux-ci font partie d'un type de données spécifique.

Si vos applications accèdent à ces champs ou API, ou les mettent à jour, utilisez d'autres méthodes. Par exemple, vous pouvez répondre à certains cas d'utilisation en faisant appel à des fournisseurs de contenu privé ou à d'autres données stockées dans vos applications ou systèmes de backend.

Pour vérifier que la fonctionnalité de votre application n'est pas affectée par cette modification, vous pouvez effacer manuellement ces champs de données. Pour ce faire, exécutez la commande ADB suivante sur un appareil équipé d'Android 4.1 (niveau d'API 16) ou version ultérieure:

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

Données provenant d'adaptateurs de synchronisation

Les utilisateurs saisissent les données des contacts directement dans l'appareil, mais elles sont également transmises au fournisseur de contacts à partir des services Web via des adaptateurs de synchronisation, qui automatisent le transfert de données entre l'appareil et les services. Les adaptateurs de synchronisation s'exécutent en arrière-plan sous le contrôle du système et appellent les méthodes ContentResolver pour gérer les données.

Dans Android, le service Web avec lequel un adaptateur de synchronisation fonctionne est identifié par un type de compte. Chaque adaptateur de synchronisation fonctionne avec un type de compte, mais il peut accepter plusieurs noms de compte pour ce type. Les types et les noms de comptes sont décrits brièvement dans la section Sources de données de contacts brutes. Les définitions suivantes offrent plus de détails et décrivent le lien entre le type et le nom de compte, et les adaptateurs et services de synchronisation.

Type de compte
Identifie un service dans lequel l'utilisateur a stocké des données. La plupart du temps, l'utilisateur doit s'authentifier auprès du service. Par exemple, Google Contacts est un type de compte, identifié par le code google.com. Cette valeur correspond au type de compte utilisé par AccountManager.
le nom de votre compte.
Identifie un compte ou une connexion spécifique pour un type de compte. Les comptes Google Contacts sont identiques aux comptes Google, dont le nom correspond à une adresse e-mail. D'autres services peuvent utiliser un nom d'utilisateur ou un identifiant numérique en un mot.

Il n'est pas nécessaire que chaque type de compte soit unique. Un utilisateur peut configurer plusieurs comptes Google Contacts et télécharger ses données vers Contacts Provider. Cela peut se produire si l'utilisateur a un ensemble de contacts personnels pour un nom de compte personnel et un autre pour son compte professionnel. Les noms de compte sont généralement uniques. Ensemble, ils identifient un flux de données spécifique entre le fournisseur de contacts et un service externe.

Si vous souhaitez transférer les données de votre service vers Contacts Provider, vous devez écrire votre propre adaptateur de synchronisation. Pour en savoir plus, consultez la section Adaptateurs de synchronisation du fournisseur de contacts.

La figure 4 montre comment Contacts Provider s'intègre dans le flux de données sur les personnes. Dans la case "adaptateurs de synchronisation", chaque adaptateur est libellé en fonction de son type de compte.

Flux de données sur les personnes

Figure 4. Flux de données du fournisseur de contacts

Autorisations requises

Les applications qui souhaitent accéder au fournisseur de contacts doivent demander les autorisations suivantes:

Accès en lecture à une ou plusieurs tables
READ_CONTACTS, spécifié dans AndroidManifest.xml avec l'élément <uses-permission> en tant que <uses-permission android:name="android.permission.READ_CONTACTS">.
Accès en écriture à une ou plusieurs tables
WRITE_CONTACTS, spécifié dans AndroidManifest.xml avec l'élément <uses-permission> en tant que <uses-permission android:name="android.permission.WRITE_CONTACTS">.

Ces autorisations ne s'appliquent pas aux données du profil utilisateur. Le profil utilisateur et les autorisations requises sont abordés dans la section suivante, Profil utilisateur.

N'oubliez pas que les données de contact de l'utilisateur sont personnelles et sensibles. Les utilisateurs se soucient de la confidentialité de leurs données. Ils ne veulent donc pas que les applications collectent des données les concernant ou concernant leurs contacts. Si la raison pour laquelle vous avez besoin d'une autorisation pour accéder à ses contacts n'est pas évidente, celui-ci peut donner de mauvaises notes à votre application ou simplement refuser de l'installer.

Le profil utilisateur

La table ContactsContract.Contacts comporte une seule ligne contenant les données de profil de l'utilisateur de l'appareil. Ces données décrivent le user de l'appareil plutôt que l'un des contacts de l'utilisateur. La ligne de contacts de profil est associée à une ligne de contacts bruts pour chaque système qui utilise un profil. Chaque ligne de contact brut du profil peut comporter plusieurs lignes de données. Les constantes permettant d'accéder au profil utilisateur sont disponibles dans la classe ContactsContract.Profile.

L'accès au profil utilisateur nécessite des autorisations spéciales. En plus des autorisations READ_CONTACTS et WRITE_CONTACTS nécessaires pour la lecture et l'écriture, l'accès au profil utilisateur nécessite respectivement les autorisations android.Manifest.permission#READ_PROFILE et android.Manifest.permission#WRITE_PROFILE pour l'accès en lecture et en écriture.

N'oubliez pas que vous devez considérer le profil d'un utilisateur comme étant sensible. L'autorisation android.Manifest.permission#READ_PROFILE vous permet d'accéder aux données permettant d'identifier personnellement l'utilisateur de l'appareil. Veillez à indiquer à l'utilisateur pourquoi vous avez besoin des autorisations d'accès au profil utilisateur dans la description de votre application.

Pour récupérer la ligne de contact contenant le profil de l'utilisateur, appelez ContentResolver.query(). Définissez l'URI de contenu sur CONTENT_URI et ne fournissez aucun critère de sélection. Vous pouvez également utiliser cet URI de contenu comme URI de base pour récupérer les données ou contacts bruts du profil. Par exemple, cet extrait récupère les données du profil:

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

Remarque:Si vous récupérez plusieurs lignes de contact et que vous souhaitez déterminer si l'une d'entre elles correspond au profil utilisateur, testez la colonne IS_USER_PROFILE de la ligne. Cette colonne est définie sur "1" pour le profil utilisateur.

Métadonnées du fournisseur de contacts

Le fournisseur de contacts gère les données qui assurent le suivi de l'état des données de contact dans le dépôt. Ces métadonnées concernant le dépôt sont stockées dans différents emplacements, y compris dans les tables "Contacts bruts", "Données" et "Contacts", la table ContactsContract.Settings et la table ContactsContract.SyncState. Le tableau suivant montre l'effet de chacune de ces métadonnées:

Tableau 3 : Métadonnées dans le fournisseur de contacts

Tableau Colonne Valeurs Signification
ContactsContract.RawContacts DIRTY "0" : aucune modification depuis la dernière synchronisation. Marque les contacts bruts qui ont été modifiés sur l'appareil et qui doivent être synchronisés avec le serveur. Cette valeur est définie automatiquement par le fournisseur de contacts lorsque les applications Android mettent à jour une ligne.

Les adaptateurs de synchronisation qui modifient les contacts bruts ou les tables de données doivent toujours ajouter la chaîne CALLER_IS_SYNCADAPTER à l'URI de contenu qu'ils utilisent. Cela empêche le fournisseur de marquer les lignes comme sales. Dans le cas contraire, les modifications de l'adaptateur de synchronisation semblent être des modifications locales et sont envoyées au serveur, même si celui-ci est à l'origine de la modification.

"1" : modifié depuis la dernière synchronisation, doit être synchronisé avec le serveur.
ContactsContract.RawContacts VERSION Numéro de version de cette ligne. Le fournisseur de contacts incrémente automatiquement cette valeur chaque fois que la ligne ou les données associées changent.
ContactsContract.Data DATA_VERSION Numéro de version de cette ligne. Contacts Provider incrémente automatiquement cette valeur chaque fois que la ligne de données est modifiée.
ContactsContract.RawContacts SOURCE_ID Valeur de chaîne qui identifie ce contact brut de manière unique dans le compte dans lequel il a été créé. Lorsqu'un adaptateur de synchronisation crée un contact brut, cette colonne doit être définie sur l'identifiant unique du serveur correspondant. Lorsqu'une application Android crée un contact brut, elle doit laisser cette colonne vide. Cela indique à l'adaptateur de synchronisation qu'il doit créer un contact brut sur le serveur et obtenir une valeur pour SOURCE_ID.

En particulier, l'ID source doit être unique pour chaque type de compte et doit être stable lors des synchronisations:

  • Unique: chaque contact brut d'un compte doit avoir son propre ID de source. Si vous ne appliquez pas cette règle, vous rencontrerez des problèmes dans l'application Contacts. Notez que deux contacts bruts associés au même type de compte peuvent avoir le même ID source. Par exemple, le contact brut "Thomas Higginson" pour le compte emily.dickinson@gmail.com est autorisé à avoir le même ID de source que le contact brut "Thomas Higginson" pour le compte emilyd@gmail.com.
  • Stable: les ID sources font partie intégrante des données du service en ligne pour le contact brut. Par exemple, si l'utilisateur efface l'espace de stockage des contacts à partir des paramètres des applications et se resynchronise, les contacts bruts restaurés doivent avoir les mêmes identifiants sources qu'auparavant. Si vous n'appliquez pas cette règle, les raccourcis cesseront de fonctionner.
ContactsContract.Groups GROUP_VISIBLE "0" : les contacts de ce groupe ne doivent pas être visibles dans l'interface utilisateur des applications Android. Cette colonne permet d'assurer la compatibilité avec les serveurs qui autorisent un utilisateur à masquer les contacts de certains groupes.
"1" : les contacts de ce groupe peuvent être visibles dans l'interface utilisateur des applications.
ContactsContract.Settings UNGROUPED_VISIBLE "0" : pour ce compte et ce type de compte, les contacts qui n'appartiennent à aucun groupe sont invisibles dans l'interface utilisateur des applications Android. Par défaut, les contacts sont invisibles si aucun de leurs contacts bruts ne fait partie d'un groupe (l'appartenance à un groupe d'un contact brut est indiquée par une ou plusieurs lignes ContactsContract.CommonDataKinds.GroupMembership dans la table ContactsContract.Data). En définissant cette option sur la ligne du tableau ContactsContract.Settings pour un type de compte et un compte, vous pouvez forcer la visibilité des contacts sans groupe. Cette option permet notamment d'afficher les contacts de serveurs qui n'utilisent pas de groupes.
"1" : pour ce compte et ce type de compte, les contacts n'appartenant à aucun groupe sont visibles dans l'interface utilisateur des applications.
ContactsContract.SyncState (tous) Stockez les métadonnées de votre adaptateur de synchronisation à l'aide de ce tableau. Cette table vous permet de stocker l'état de la synchronisation et d'autres données liées à la synchronisation de manière persistante sur l'appareil.

Accès au fournisseur de contacts

Cette section décrit les consignes d'accès aux données du fournisseur de contacts et traite des points suivants:

  • Requêtes d'entité.
  • Modification par lot
  • Récupération et modification avec des intents
  • Intégrité des données.

La procédure à partir d'un adaptateur de synchronisation est également abordée plus en détail dans la section Adaptateurs de synchronisation du fournisseur de contacts.

Interroger des entités

Les tables du fournisseur de contacts étant organisées de façon hiérarchique, il est souvent utile de récupérer une ligne et toutes les lignes "enfants" qui y sont associées. Par exemple, pour afficher toutes les informations sur une personne, vous pouvez récupérer toutes les lignes ContactsContract.RawContacts d'une seule ligne ContactsContract.Contacts ou toutes les lignes ContactsContract.CommonDataKinds.Email d'une seule ligne ContactsContract.RawContacts. Pour faciliter ce processus, le fournisseur de contacts propose des constructions d'entités, qui agissent comme des jointures de base de données entre les tables.

Une entité est semblable à un tableau composé de colonnes sélectionnées à partir d'un tableau parent et de son tableau enfant. Lorsque vous interrogez une entité, vous fournissez une projection et des critères de recherche basés sur les colonnes disponibles à partir de l'entité. Le résultat est un objet Cursor contenant une ligne pour chaque ligne de la table enfant récupérée. Par exemple, si vous interrogez ContactsContract.Contacts.Entity pour un nom de contact et toutes les lignes ContactsContract.CommonDataKinds.Email pour tous les contacts bruts portant ce nom, vous obtenez un Cursor contenant une ligne pour chaque ligne ContactsContract.CommonDataKinds.Email.

Les entités simplifient les requêtes. À l'aide d'une entité, vous pouvez récupérer simultanément toutes les données de contact d'un contact ou d'un contact brut, ce qui vous évite d'avoir à interroger la table parent pour obtenir un ID, puis à interroger la table enfant avec cet ID. En outre, le fournisseur de contacts traite une requête sur une entité dans le cadre d'une transaction unique, ce qui garantit la cohérence interne des données récupérées.

Remarque:Une entité ne contient généralement pas toutes les colonnes des tables parentes et enfants. Si vous tentez d'utiliser un nom de colonne qui ne figure pas dans la liste des constantes de nom de colonne pour l'entité, vous obtiendrez une erreur Exception.

L'extrait de code suivant montre comment récupérer toutes les lignes brutes d'un contact. L'extrait de code fait partie d'une application plus vaste comportant deux activités, "main" et "detail". L'activité principale affiche une liste de lignes de contacts. Lorsque l'utilisateur en sélectionne une, l'activité envoie son ID à l'activité détaillée. L'activité "Détail" utilise ContactsContract.Contacts.Entity pour afficher toutes les lignes de données de tous les contacts bruts associés au contact sélectionné.

Cet extrait est issu de l'activité "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.
}

Une fois le chargement terminé, LoaderManager appelle un rappel à onLoadFinished(). L'un des arguments entrants de cette méthode est un objet Cursor contenant les résultats de la requête. Dans votre propre application, vous pouvez obtenir les données de ce Cursor pour les afficher ou les utiliser davantage.

Modification par lot

Dans la mesure du possible, insérez, mettez à jour et supprimez des données dans le fournisseur de contacts en "mode de traitement par lot" en créant une ArrayList d'objets ContentProviderOperation et en appelant applyBatch(). Étant donné que le fournisseur de contacts effectue toutes les opérations d'un élément applyBatch() en une seule transaction, vos modifications ne laisseront jamais un état incohérent dans le dépôt de contacts. Une modification par lot facilite également l'insertion simultanée d'un contact brut et de ses données détaillées.

Remarque:Pour modifier un seul contact brut, envisagez d'envoyer un intent à l'application de contacts de l'appareil plutôt que de gérer la modification dans votre application. Pour en savoir plus, consultez la section Récupération et modification avec des intents.

Points de rendement

Une modification par lot contenant un grand nombre d'opérations peut bloquer d'autres processus, ce qui nuit à l'expérience utilisateur globale. Pour organiser toutes les modifications que vous souhaitez effectuer dans le moins de listes distinctes possible, tout en les empêchant de bloquer le système, vous devez définir des points de rendement pour une ou plusieurs opérations. Un point de rendement est un objet ContentProviderOperation dont la valeur isYieldAllowed() est définie sur true. Lorsque le fournisseur de contacts rencontre un point de rendement, il suspend son travail pour permettre l'exécution d'autres processus et ferme la transaction en cours. Lorsque le fournisseur redémarre, il passe à l'opération suivante dans ArrayList et lance une nouvelle transaction.

Les points de rendement génèrent plusieurs transactions par appel à applyBatch(). C'est pourquoi vous devez définir un point de rendement pour la dernière opération sur un ensemble de lignes associées. Par exemple, vous devez définir un point de rendement pour la dernière opération d'un ensemble qui ajoute des lignes de contact brutes et les lignes de données associées, ou la dernière opération pour un ensemble de lignes associées à un seul contact.

Les points de rendement sont également une unité d'opération atomique. Tous les accès entre deux points de rendement aboutissent ou échouent comme une seule unité. Si vous ne définissez aucun point de rendement, la plus petite opération atomique est l'ensemble du lot d'opérations. Si vous utilisez des points de rendement, vous empêchez les opérations de dégrader les performances du système, tout en garantissant le caractère atomique d'un sous-ensemble d'opérations.

Modifier les anciennes références

Lorsque vous insérez une nouvelle ligne de contact brut et les lignes de données associées en tant qu'ensemble d'objets ContentProviderOperation, vous devez associer les lignes de données à la ligne de contact brut en insérant la valeur _ID du contact brut en tant que valeur RAW_CONTACT_ID. Toutefois, cette valeur n'est pas disponible lorsque vous créez le ContentProviderOperation pour la ligne de données, car vous n'avez pas encore appliqué le ContentProviderOperation pour la ligne de contact brute. Pour contourner ce problème, la classe ContentProviderOperation.Builder utilise la méthode withValueBackReference(). Cette méthode vous permet d'insérer ou de modifier une colonne avec le résultat d'une opération précédente.

La méthode withValueBackReference() comporte deux arguments:

key
Clé d'une paire clé-valeur. La valeur de cet argument doit correspondre au nom d'une colonne de la table que vous modifiez.
previousResult
Index en base 0 d'une valeur dans le tableau d'objets ContentProviderResult de applyBatch(). À mesure que les opérations par lot sont appliquées, le résultat de chaque opération est stocké dans un tableau intermédiaire de résultats. La valeur previousResult est l'index de l'un de ces résultats, qui est récupéré et stocké avec la valeur key. Cela vous permet d'insérer un nouvel enregistrement de contact brut et de récupérer sa valeur _ID, puis de créer une "référence arrière" à la valeur lorsque vous ajoutez une ligne ContactsContract.Data.

Le tableau de résultats complet est créé lorsque vous appelez applyBatch() pour la première fois, avec une taille égale à la taille du ArrayList des objets ContentProviderOperation que vous fournissez. Cependant, tous les éléments du tableau de résultat sont définis sur null. Si vous essayez de faire une référence arrière à un résultat pour une opération qui n'a pas encore été appliquée, withValueBackReference() génère une erreur Exception.

Les extraits de code suivants montrent comment insérer un nouveau contact brut et des données par lot. Elles incluent du code qui établit un point de rendement et utilise une référence arrière.

Le premier extrait récupère les données sur les contacts à partir de l'interface utilisateur. À ce stade, l'utilisateur a déjà sélectionné le compte pour lequel le nouveau contact brut doit être ajouté.

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());

L'extrait de code suivant crée une opération pour insérer la ligne de contact brute dans la table 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());

Ensuite, le code crée des lignes de données pour les lignes du nom à afficher, du numéro de téléphone et de l'adresse e-mail.

Chaque objet de compilateur d'opérations utilise withValueBackReference() pour obtenir le RAW_CONTACT_ID. La référence renvoie vers l'objet ContentProviderResult de la première opération, qui ajoute la ligne de contact brute et renvoie sa nouvelle valeur _ID. Par conséquent, chaque ligne de données est automatiquement associée par son RAW_CONTACT_ID à la nouvelle ligne ContactsContract.RawContacts à laquelle elle appartient.

L'objet ContentProviderOperation.Builder qui ajoute la ligne d'adresse e-mail est signalé avec l'option withYieldAllowed(), qui définit un point de rendement:

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());

Le dernier extrait montre l'appel à applyBatch() qui insère les nouvelles lignes de données et de contacts brutes.

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);
    }
}

Les opérations par lot vous permettent également de mettre en œuvre un contrôle de simultanéité optimiste, une méthode permettant d'appliquer des transactions de modification sans avoir à verrouiller le dépôt sous-jacent. Pour utiliser cette méthode, appliquez la transaction, puis recherchez d'autres modifications qui ont pu être apportées en même temps. Si vous constatez qu'une modification incohérente s'est produite, effectuez un rollback de la transaction, puis réessayez.

Le contrôle de simultanéité optimiste est utile pour un appareil mobile, lorsqu'il n'y a qu'un seul utilisateur à la fois et où les accès simultanés à un dépôt de données sont rares. Comme le verrouillage n'est pas utilisé, vous ne perdez pas de temps à définir des verrouillages ni à attendre que d'autres transactions le libèrent.

Pour utiliser le contrôle de simultanéité optimiste lors de la mise à jour d'une seule ligne ContactsContract.RawContacts, procédez comme suit:

  1. Récupérez la colonne VERSION du contact brut avec les autres données que vous récupérez.
  2. Créez un objet ContentProviderOperation.Builder adapté à l'application d'une contrainte à l'aide de la méthode newAssertQuery(Uri). Pour l'URI de contenu, utilisez RawContacts.CONTENT_URI en ajoutant l'_ID du contact brut.
  3. Pour l'objet ContentProviderOperation.Builder, appelez withValue() pour comparer la colonne VERSION au numéro de version que vous venez de récupérer.
  4. Pour le même ContentProviderOperation.Builder, appelez withExpectedCount() pour vous assurer qu'une seule ligne est testée par cette assertion.
  5. Appelez build() pour créer l'objet ContentProviderOperation, puis ajoutez-le en tant que premier objet du ArrayList que vous transmettez à applyBatch().
  6. Appliquez la transaction groupée.

Si la ligne de contact brut est mise à jour par une autre opération entre le moment où vous la lisez et celui où vous essayez de la modifier, l'instruction ContentProviderOperation échoue, et l'ensemble du lot d'opérations est sauvegardé. Vous pouvez ensuite choisir de relancer le lot ou d'effectuer une autre action.

L'extrait de code suivant montre comment créer une ContentProviderOperation "assertion" après avoir interrogé un seul contact brut à l'aide d'un 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
    }

Récupération et modification avec des intents

L'envoi d'un intent à l'application Contacts de l'appareil vous permet d'accéder indirectement au fournisseur de contacts. L'intent lance l'interface utilisateur de l'application Contacts de l'appareil, dans laquelle les utilisateurs peuvent effectuer des tâches liées aux contacts. Avec ce type d'accès, les utilisateurs peuvent:

  • Choisissez un contact dans une liste et renvoyez-le dans votre application pour y poursuivre le travail.
  • Modifiez les données d'un contact existant.
  • Insérez un nouveau contact brut pour l'un de ses comptes.
  • Supprimer un contact ou les données de contacts

Si l'utilisateur insère ou met à jour des données, vous pouvez d'abord les collecter et les envoyer avec l'intent.

Lorsque vous utilisez des intents pour accéder au fournisseur de contacts via l'application Contacts de l'appareil, vous n'avez pas besoin d'écrire votre propre interface utilisateur ni votre propre code pour accéder au fournisseur. Vous n'avez pas non plus besoin de demander une autorisation de lecture ou d'écriture sur le fournisseur. L'application Contacts de l'appareil peut vous déléguer l'autorisation de lecture d'un contact. Étant donné que vous apportez des modifications au fournisseur via une autre application, vous n'avez pas besoin de disposer d'autorisations en écriture.

Le processus général d'envoi d'un intent pour accéder à un fournisseur est décrit en détail dans le guide Principes de base du fournisseur de contenu de la section "Accès aux données via des intents". L'action, le type MIME et les valeurs de données que vous utilisez pour les tâches disponibles sont résumés dans le tableau 4, tandis que les valeurs supplémentaires que vous pouvez utiliser avec putExtra() sont listées dans la documentation de référence pour ContactsContract.Intents.Insert:

Tableau 4. Contacte les intents du fournisseur

Tâche Action Données Type MIME Notes
Sélectionner un contact dans une liste ACTION_PICK Choisissez l'une des options suivantes : Non utilisé Affiche une liste de contacts bruts ou une liste de données provenant d'un contact brut, en fonction du type d'URI de contenu que vous fournissez.

Appelez startActivityForResult(), qui renvoie l'URI de contenu de la ligne sélectionnée. La forme de l'URI correspond à l'URI de contenu de la table, suivi de l'élément LOOKUP_ID de la ligne. L'application Contacts de l'appareil délègue les autorisations de lecture et d'écriture à cet URI de contenu pendant toute la durée de vie de votre activité. Pour en savoir plus, consultez les principes de base des fournisseurs de contenu.

Insérer un nouveau contact brut Insert.ACTION N/A RawContacts.CONTENT_TYPE, type MIME pour un ensemble de contacts bruts. Affiche l'écran Ajouter un contact de l'application Contacts de l'appareil. Les valeurs d'extras que vous ajoutez à l'intent s'affichent. S'il est envoyé avec startActivityForResult(), l'URI de contenu du contact brut nouvellement ajouté est renvoyé à la méthode de rappel onActivityResult() de votre activité dans l'argument Intent, dans le champ "data". Pour obtenir cette valeur, appelez getData().
Modifier un contact ACTION_EDIT CONTENT_LOOKUP_URI pour le contact. L'activité d'édition permettra à l'utilisateur de modifier toutes les données associées à ce contact. Contacts.CONTENT_ITEM_TYPE, un seul contact. Affiche l'écran Modifier le contact dans l'application Contacts. Les valeurs supplémentaires que vous ajoutez à l'intent s'affichent. Lorsque l'utilisateur clique sur OK pour enregistrer les modifications, votre activité revient au premier plan.
Affichez un sélecteur qui peut également ajouter des données. ACTION_INSERT_OR_EDIT N/A CONTENT_ITEM_TYPE Cet intent affiche toujours l'écran de sélection de l'application Contacts. L'utilisateur peut sélectionner un contact à modifier ou en ajouter un. L'écran de modification ou d'ajout apparaît, selon le choix de l'utilisateur, et les données supplémentaires que vous transmettez à l'intent s'affichent. Si votre application affiche des données de contact telles qu'une adresse e-mail ou un numéro de téléphone, utilisez cet intent pour permettre à l'utilisateur d'ajouter ces données à un contact existant. de contact,

Remarque:Il n'est pas nécessaire d'envoyer une valeur de nom dans les extras de cet intent, car l'utilisateur choisit toujours un nom existant ou en ajoute un nouveau. De plus, si vous envoyez un nom et que l'utilisateur choisit d'effectuer une modification, l'application Contacts affiche le nom que vous envoyez en remplaçant la valeur précédente. Si l'utilisateur ne s'en aperçoit pas et enregistre la modification, l'ancienne valeur est perdue.

L'application Contacts de l'appareil ne vous permet pas de supprimer un contact brut ni aucune de ses données avec un intent. Pour supprimer un contact brut, utilisez plutôt ContentResolver.delete() ou ContentProviderOperation.newDelete().

L'extrait de code suivant montre comment construire et envoyer un intent qui insère un nouveau contact et des données brutes:

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

Intégrité des données

Étant donné que le dépôt de contacts contient des données importantes et sensibles que les utilisateurs s'attendent à trouver correctes et à jour, le fournisseur de contacts dispose de règles bien définies pour l'intégrité des données. Il est de votre responsabilité de vous conformer à ces règles lorsque vous modifiez les données des contacts. Les règles importantes sont listées ici:

Ajoutez toujours une ligne ContactsContract.CommonDataKinds.StructuredName pour chaque ligne ContactsContract.RawContacts que vous ajoutez.
Une ligne ContactsContract.RawContacts sans ligne ContactsContract.CommonDataKinds.StructuredName dans la table ContactsContract.Data peut entraîner des problèmes lors de l'agrégation.
Associez toujours les nouvelles lignes ContactsContract.Data à leur ligne ContactsContract.RawContacts parente.
Une ligne ContactsContract.Data qui n'est pas associée à un ContactsContract.RawContacts n'est pas visible dans l'application Contacts de l'appareil, et peut causer des problèmes avec les adaptateurs de synchronisation.
Ne modifiez que les données de contacts bruts qui vous appartiennent.
N'oubliez pas que le fournisseur de contacts gère généralement les données de différents types de comptes et services en ligne. Vous devez vous assurer que votre application ne modifie ou ne supprime que les données des lignes qui vous appartiennent, et qu'elle n'insère que les données dont le type et le nom de compte sont que vous contrôlez.
Utilisez toujours les constantes définies dans ContactsContract et ses sous-classes pour les autorités, les URI de contenu, les chemins d'URI, les noms de colonne, les types MIME et les valeurs TYPE.
L'utilisation de ces constantes vous permet d'éviter les erreurs. Vous recevrez également des avertissements du compilateur si l'une des constantes est obsolète.

Lignes de données personnalisées

En créant et en utilisant vos propres types MIME personnalisés, vous pouvez insérer, modifier, supprimer et récupérer vos propres lignes de données dans la table ContactsContract.Data. Vos lignes sont limitées à l'utilisation de la colonne définie dans ContactsContract.DataColumns, bien que vous puissiez mapper vos propres noms de colonnes spécifiques au type avec les noms de colonne par défaut. Dans l'application Contacts de l'appareil, les données de vos lignes sont affichées, mais ne peuvent pas être modifiées ni supprimées, et les utilisateurs ne peuvent pas ajouter de données supplémentaires. Pour permettre aux utilisateurs de modifier vos lignes de données personnalisées, vous devez fournir une activité d'édition dans votre propre application.

Pour afficher vos données personnalisées, fournissez un fichier contacts.xml contenant un élément <ContactsAccountType> et un ou plusieurs de ses éléments enfants <ContactsDataKind>. Pour en savoir plus, consultez la section <ContactsDataKind> element.

Pour en savoir plus sur les types MIME personnalisés, consultez le guide Créer un fournisseur de contenu.

Adaptateurs de synchronisation du fournisseur de contacts

Le fournisseur de contacts est spécialement conçu pour gérer la synchronisation des données de contacts entre un appareil et un service en ligne. Les utilisateurs peuvent ainsi télécharger des données existantes sur un nouvel appareil et les importer dans un nouveau compte. La synchronisation garantit également que les utilisateurs disposent des données les plus récentes, quelle que soit la source des ajouts et des modifications. La synchronisation présente un autre avantage : les données des contacts sont disponibles même lorsque l'appareil n'est pas connecté au réseau.

Bien que vous puissiez implémenter la synchronisation de différentes manières, le système Android fournit un framework de synchronisation de plug-ins qui automatise les tâches suivantes:

  • Vérification de la disponibilité du réseau...
  • Planification et exécution de la synchronisation en fonction des préférences de l'utilisateur
  • Redémarrage des synchronisations arrêtées.

Pour utiliser ce framework, vous devez fournir un plug-in d'adaptateur de synchronisation. Chaque adaptateur de synchronisation est propre à un fournisseur de services et de contenu, mais peut gérer plusieurs noms de compte pour le même service. Le framework permet également d'utiliser plusieurs adaptateurs de synchronisation pour le même service et le même fournisseur.

Classes et fichiers d'adaptateur de synchronisation

Vous allez implémenter un adaptateur de synchronisation en tant que sous-classe de AbstractThreadedSyncAdapter et l'installer dans le cadre d'une application Android. Le système apprend à reconnaître l'adaptateur de synchronisation à partir des éléments du fichier manifeste de votre application et d'un fichier XML spécial vers lequel pointe le fichier manifeste. Le fichier XML définit le type de compte du service en ligne et l'autorité du fournisseur de contenu, qui identifient ensemble l'adaptateur de manière unique. L'adaptateur de synchronisation ne devient actif que lorsque l'utilisateur ajoute un compte pour son type et active la synchronisation pour le fournisseur de contenu avec lequel l'adaptateur de synchronisation se synchronise. À ce stade, le système commence à gérer l'adaptateur, en l'appelant si nécessaire pour la synchronisation entre le fournisseur de contenu et le serveur.

Remarque:L'utilisation d'un type de compte dans l'identification de l'adaptateur de synchronisation permet au système de détecter et de regrouper les adaptateurs de synchronisation qui accèdent à différents services de la même organisation. Par exemple, les adaptateurs de synchronisation pour les services en ligne Google sont tous du même type de compte com.google. Lorsque les utilisateurs ajoutent un compte Google à leurs appareils, tous les adaptateurs de synchronisation pour les services Google installés sont listés ensemble, et chacun d'eux se synchronise avec un fournisseur de contenu différent sur l'appareil.

Étant donné que la plupart des services nécessitent que les utilisateurs valident leur identité avant d'accéder aux données, le système Android propose un framework d'authentification semblable au framework de l'adaptateur de synchronisation, qui est souvent utilisé conjointement. Le framework d'authentification utilise des authentificateurs de plug-ins qui sont des sous-classes de AbstractAccountAuthenticator. Un authentificateur vérifie l'identité de l'utilisateur en procédant comme suit:

  1. Collecte le nom, le mot de passe ou des informations similaires de l'utilisateur (ses identifiants).
  2. Il envoie les identifiants au service.
  3. Examinez la réponse du service.

Si le service les accepte, l'authentificateur peut les stocker pour une utilisation ultérieure. En raison du framework d'authentification de plug-in, AccountManager peut fournir l'accès à tous les jetons d'authentification compatibles avec un authentificateur et qu'il choisit d'exposer, tels que les jetons d'authentification OAuth2.

Bien que l'authentification ne soit pas requise, la plupart des services de gestion des contacts l'utilisent. Toutefois, vous n'êtes pas obligé d'utiliser le framework d'authentification Android pour effectuer l'authentification.

Implémentation de l'adaptateur de synchronisation

Pour implémenter un adaptateur de synchronisation pour le fournisseur de contacts, commencez par créer une application Android contenant les éléments suivants:

Un composant Service qui répond aux requêtes du système de liaison à l'adaptateur de synchronisation.
Lorsque le système souhaite exécuter une synchronisation, il appelle la méthode onBind() du service afin d'obtenir un IBinder pour l'adaptateur de synchronisation. Cela permet au système d'effectuer des appels inter-processus aux méthodes de l'adaptateur.
L'adaptateur de synchronisation réel, implémenté en tant que sous-classe concrète de AbstractThreadedSyncAdapter.
Cette classe permet de télécharger des données à partir du serveur, d'importer des données depuis l'appareil et de résoudre les conflits. Le travail principal de l'adaptateur est effectué dans la méthode onPerformSync(). Cette classe doit être instanciée en tant que singleton.
Une sous-classe de Application.
Cette classe sert de fabrique pour le singleton de l'adaptateur de synchronisation. Utilisez la méthode onCreate() pour instancier l'adaptateur de synchronisation et fournissez une méthode "getter" statique pour renvoyer le singleton à la méthode onBind() du service de l'adaptateur de synchronisation.
Facultatif:composant Service qui répond aux requêtes du système d'authentification des utilisateurs.
AccountManager démarre ce service pour commencer le processus d'authentification. La méthode onCreate() du service instancie un objet authentificateur. Lorsque le système souhaite authentifier un compte utilisateur pour l'adaptateur de synchronisation de l'application, il appelle la méthode onBind() du service afin d'obtenir un IBinder pour l'authentificateur. Cela permet au système d'effectuer des appels inter-processus aux méthodes de l'authentificateur.
Facultatif:sous-classe concrète de AbstractAccountAuthenticator qui gère les requêtes d'authentification.
Cette classe fournit des méthodes que AccountManager appelle pour authentifier les identifiants de l'utilisateur auprès du serveur. Les détails du processus d'authentification varient considérablement selon la technologie de serveur utilisée. Reportez-vous à la documentation de votre logiciel serveur pour en savoir plus sur l'authentification.
Fichiers XML qui définissent l'adaptateur de synchronisation et l'authentificateur au système.
Les composants de l'adaptateur de synchronisation et du service d'authentification décrits précédemment sont définis dans les éléments <service> du fichier manifeste de l'application. Ces éléments contiennent des éléments enfants <meta-data> qui fournissent des données spécifiques au système :
  • L'élément <meta-data> pour le service de l'adaptateur de synchronisation pointe vers le fichier XML res/xml/syncadapter.xml. Ce fichier spécifie un URI pour le service Web qui sera synchronisé avec le fournisseur de contacts et un type de compte pour le service Web.
  • Facultatif:L'élément <meta-data> de l'authentificateur pointe vers le fichier XML res/xml/authenticator.xml. Ce fichier spécifie ensuite le type de compte pris en charge par cet authentificateur, ainsi que les ressources d'interface utilisateur qui apparaissent lors du processus d'authentification. Le type de compte spécifié dans cet élément doit être identique à celui spécifié pour l'adaptateur de synchronisation.

Données des flux de réseaux sociaux

Les tables android.provider.ContactsContract.StreamItems et android.provider.ContactsContract.StreamItemPhotos gèrent les données entrantes des réseaux sociaux. Vous pouvez écrire un adaptateur de synchronisation qui ajoute des données de flux de votre propre réseau à ces tables. Vous pouvez également lire les données de flux de ces tables et les afficher dans votre propre application, ou les deux. Grâce à ces fonctionnalités, vos services et applications de réseau social peuvent être intégrés à l'expérience de réseau social d'Android.

Texte du flux sur les réseaux sociaux

Les éléments de flux sont toujours associés à un contact brut. android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID renvoie à la valeur _ID du contact brut. Le type et le nom du compte du contact brut sont également stockés dans la ligne de l'élément de flux.

Stockez les données de votre flux dans les colonnes suivantes:

android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
Obligatoire. Type de compte de l'utilisateur pour le contact brut associé à cet élément de flux. N'oubliez pas de définir cette valeur lorsque vous insérez un élément de flux.
android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
Obligatoire. Nom du compte utilisateur pour le contact brut associé à cet élément de flux. N'oubliez pas de définir cette valeur lorsque vous insérez un élément de flux.
Colonnes d'identifiant
Obligatoire. Lorsque vous insérez un élément de flux, vous devez insérer les colonnes d'identifiants suivantes :
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: valeur android.provider.BaseColumns#_ID du contact auquel cet élément de flux est associé.
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: valeur android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY du contact auquel cet élément de flux est associé.
  • android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: valeur android.provider.BaseColumns#_ID du contact brut auquel cet élément de flux est associé.
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
Facultatif. Stocke les informations récapitulatives que vous pouvez afficher au début d'un élément de flux.
android.provider.ContactsContract.StreamItemsColumns#TEXT
Texte de l'élément de flux (contenu publié par sa source ou description d'une action ayant généré l'élément de flux). Cette colonne peut contenir n'importe quelle mise en forme et images de ressources intégrées pouvant être affichées par fromHtml(). Le fournisseur peut tronquer le contenu long ou définir des points de suspension, mais il s'efforcera d'éviter de casser les balises.
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
Chaîne de texte contenant l'heure à laquelle l'élément de flux a été inséré ou mis à jour, exprimée en millisecondes depuis l'epoch. Les applications qui insèrent ou mettent à jour des éléments de flux sont responsables de la maintenance de cette colonne. Cette colonne n'est pas automatiquement gérée par le fournisseur de contacts.

Pour afficher les informations d'identification de vos éléments de flux, utilisez android.provider.ContactsContract.StreamItemsColumns#RES_ICON, android.provider.ContactsContract.StreamItemsColumns#RES_LABEL et android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE pour un lien vers les ressources de votre application.

La table android.provider.ContactsContract.StreamItems contient également les colonnes android.provider.ContactsContract.StreamItemsColumns#SYNC1 via android.provider.ContactsContract.StreamItemsColumns#SYNC4, pour l'utilisation exclusive des adaptateurs de synchronisation.

Photos des flux de réseaux sociaux

La table android.provider.ContactsContract.StreamItemPhotos stocke les photos associées à un élément de flux. La colonne android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID de la table renvoie aux valeurs de la colonne _ID de la table android.provider.ContactsContract.StreamItems. Les références photo sont stockées dans le tableau dans les colonnes suivantes:

Colonne android.provider.ContactsContract.StreamItemPhotos#PHOTO (BLOB).
Représentation binaire de la photo, redimensionnée par le fournisseur à des fins de stockage et d'affichage. Cette colonne est disponible pour assurer la rétrocompatibilité avec les versions précédentes du fournisseur de contacts qui l'utilisaient pour stocker des photos. Toutefois, dans la version actuelle, vous ne devez pas utiliser cette colonne pour stocker des photos. Utilisez plutôt android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID ou android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI (ces deux méthodes sont décrites ci-dessous) pour stocker des photos dans un fichier. Cette colonne contient maintenant une vignette de la photo, qui peut être consultée.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
Identifiant numérique d'une photo pour un contact brut. Ajoutez cette valeur à la constante DisplayPhoto.CONTENT_URI pour obtenir un URI de contenu pointant vers un seul fichier photo, puis appelez openAssetFileDescriptor() pour obtenir un handle vers le fichier photo.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
URI de contenu pointant directement vers le fichier photo de la photo représentée par cette ligne. Appelez openAssetFileDescriptor() avec cet URI pour obtenir un handle vers le fichier photo.

Utiliser les tableaux de flux de réseaux sociaux

Ces tableaux fonctionnent de la même manière que les autres tableaux principaux du fournisseur de contacts, à une différence près:

  • Ces tables nécessitent des autorisations d'accès supplémentaires. Pour pouvoir les lire, votre application doit disposer de l'autorisation android.Manifest.permission#READ_SOCIAL_STREAM. Pour les modifier, votre application doit disposer de l'autorisation android.Manifest.permission#WRITE_SOCIAL_STREAM.
  • Pour la table android.provider.ContactsContract.StreamItems, le nombre de lignes stockées pour chaque contact brut est limité. Une fois cette limite atteinte, le fournisseur de contacts fait de la place pour les nouvelles lignes d'éléments de flux en supprimant automatiquement les lignes contenant le plus ancien android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP. Pour connaître la limite, envoyez une requête à l'URI de contenu android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI. Vous pouvez laisser tous les arguments autres que l'URI de contenu définis sur null. La requête renvoie un curseur contenant une seule ligne, avec la seule colonne android.provider.ContactsContract.StreamItems#MAX_ITEMS.

La classe android.provider.ContactsContract.StreamItems.StreamItemPhotos définit un sous-tableau d'android.provider.ContactsContract.StreamItemPhotos contenant les lignes des photos d'un seul élément de flux.

Interactions avec les flux sur les réseaux sociaux

Les données de flux sur les réseaux sociaux gérées par le fournisseur de contacts conjointement avec l'application de contacts de l'appareil constituent un moyen efficace de connecter votre système de réseau social à vos contacts existants. Les fonctionnalités suivantes sont disponibles:

  • En synchronisant votre service de réseau social avec le fournisseur de contacts à l'aide d'un adaptateur de synchronisation, vous pouvez récupérer l'activité récente des contacts d'un utilisateur et la stocker dans les tables android.provider.ContactsContract.StreamItems et android.provider.ContactsContract.StreamItemPhotos pour une utilisation ultérieure.
  • En plus d'effectuer une synchronisation régulière, vous pouvez déclencher votre adaptateur de synchronisation pour récupérer des données supplémentaires lorsque l'utilisateur sélectionne un contact à afficher. Cela permet à votre adaptateur de synchronisation de récupérer des photos haute résolution et les derniers éléments de flux du contact.
  • En enregistrant une notification dans l'application Contacts de l'appareil et dans le fournisseur de contacts, vous pouvez recevoir un intent lorsqu'un contact est consulté, et mettre à jour l'état du contact depuis votre service. Cette approche peut s'avérer plus rapide et utiliser moins de bande passante qu'une synchronisation complète avec un adaptateur de synchronisation.
  • Les utilisateurs peuvent ajouter un contact à votre service de réseau social tout en le consultant dans l'application Contacts de l'appareil. Pour ce faire, vous pouvez utiliser la fonctionnalité "Inviter un contact", à laquelle vous associez une activité qui ajoute un contact existant à votre réseau, et un fichier XML qui fournit les détails de votre application à l'application de gestion des contacts de l'appareil et au fournisseur de contacts.

La synchronisation régulière des éléments de flux avec Contacts Provider est la même que pour les autres synchronisations. Pour en savoir plus sur la synchronisation, consultez la section Adaptateurs de synchronisation du fournisseur de contacts. L'enregistrement de notifications et l'invitation de contacts sont abordés dans les deux sections suivantes.

S'inscrire pour gérer les vues liées aux réseaux sociaux

Pour enregistrer votre adaptateur de synchronisation afin de recevoir des notifications lorsque l'utilisateur affiche un contact géré par votre adaptateur de synchronisation, procédez comme suit:

  1. Créez un fichier nommé contacts.xml dans le répertoire res/xml/ de votre projet. Si vous disposez déjà de ce fichier, vous pouvez ignorer cette étape.
  2. Dans ce fichier, ajoutez l'élément <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Si cet élément existe déjà, vous pouvez ignorer cette étape.
  3. Pour enregistrer un service qui est averti lorsque l'utilisateur ouvre la page d'informations d'un contact dans l'application Contacts de l'appareil, ajoutez l'attribut viewContactNotifyService="serviceclass" à l'élément, où serviceclass est le nom de classe complet du service qui doit recevoir l'intent de l'application Contacts de l'appareil. Pour le service d'alerte, utilisez une classe qui étend IntentService afin de permettre au service de recevoir des intents. Les données de l'intent entrant contiennent l'URI de contenu du contact brut sur lequel l'utilisateur a cliqué. À partir du service d'alerte, vous pouvez associer votre adaptateur de synchronisation, puis l'appeler pour mettre à jour les données du contact brut.

Pour enregistrer une activité à appeler lorsque l'utilisateur clique sur un élément du flux, une photo ou les deux:

  1. Créez un fichier nommé contacts.xml dans le répertoire res/xml/ de votre projet. Si vous disposez déjà de ce fichier, vous pouvez ignorer cette étape.
  2. Dans ce fichier, ajoutez l'élément <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Si cet élément existe déjà, vous pouvez ignorer cette étape.
  3. Pour enregistrer l'une de vos activités afin de gérer les clics des utilisateurs sur un élément de flux dans l'application Contacts de l'appareil, ajoutez l'attribut viewStreamItemActivity="activityclass" à l'élément, où activityclass est le nom de classe complet de l'activité qui doit recevoir l'intent de l'application Contacts de l'appareil.
  4. Pour enregistrer l'une de vos activités afin de gérer le clic d'un utilisateur sur une photo de flux dans l'application Contacts de l'appareil, ajoutez l'attribut viewStreamItemPhotoActivity="activityclass" à l'élément, où activityclass est le nom de classe complet de l'activité qui doit recevoir l'intent de l'application Contacts de l'appareil.

L'élément <ContactsAccountType> est décrit plus en détail dans la section Élément <ContactsAccountType>.

L'intent entrant contient l'URI de contenu de l'élément ou de la photo sur lequel l'utilisateur a cliqué. Pour avoir des activités distinctes pour les éléments textuels et les photos, utilisez les deux attributs dans le même fichier.

Interagir avec votre service de réseau social

Les utilisateurs n'ont pas besoin de quitter l'application de contacts de l'appareil pour inviter un contact sur votre site de réseau social. À la place, vous pouvez demander à l'application de contacts de l'appareil d'envoyer un intent pour inviter le contact à l'une de vos activités. Pour configurer une expérience supervisée :

  1. Créez un fichier nommé contacts.xml dans le répertoire res/xml/ de votre projet. Si vous disposez déjà de ce fichier, vous pouvez ignorer cette étape.
  2. Dans ce fichier, ajoutez l'élément <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Si cet élément existe déjà, vous pouvez ignorer cette étape.
  3. Ajoutez les attributs suivants :
    • inviteContactActivity="activityclass"
    • inviteContactActionLabel="@string/invite_action_label"
    La valeur activityclass est le nom de classe complet de l'activité qui doit recevoir l'intent. La valeur invite_action_label est une chaîne de texte qui s'affiche dans le menu Ajouter une connexion de l'application Contacts de l'appareil.

Remarque:ContactsSource est un nom de balise obsolète pour ContactsAccountType.

Documentation de référence sur contacts.xml

Le fichier contacts.xml contient des éléments XML qui contrôlent l'interaction de votre adaptateur de synchronisation et de votre application avec l'application Contacts et le fournisseur de contacts. Ces éléments sont décrits dans les sections suivantes.

Élément <ContactsAccountType>

L'élément <ContactsAccountType> contrôle l'interaction de votre application avec l'application Contacts. Sa syntaxe est la suivante:

<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">

Contenu:

res/xml/contacts.xml

peut contenir:

<ContactsDataKind>

Description :

Déclare les composants Android et les libellés d'interface utilisateur permettant aux utilisateurs d'inviter l'un de leurs contacts à rejoindre un réseau social, d'informer les utilisateurs lorsque l'un de leurs flux de réseau social est mis à jour, etc.

Notez que le préfixe d'attribut android: n'est pas nécessaire pour les attributs de <ContactsAccountType>.

Attributs:

inviteContactActivity
Nom de classe complet de l'activité de votre application que vous souhaitez activer lorsque l'utilisateur sélectionne Ajouter une connexion dans l'application Contacts de l'appareil.
inviteContactActionLabel
Chaîne de texte affichée pour l'activité spécifiée dans inviteContactActivity, dans le menu Ajouter une connexion. Par exemple, vous pouvez utiliser la chaîne "Suivre dans mon réseau". Vous pouvez utiliser un identifiant de ressource de chaîne pour ce libellé.
viewContactNotifyService
Nom de classe complet d'un service de votre application devant recevoir des notifications lorsque l'utilisateur affiche un contact. Cette notification est envoyée par l'application Contacts de l'appareil. Elle permet à votre application de reporter les opérations qui consomment beaucoup de données jusqu'à ce qu'elles soient nécessaires. Par exemple, votre application peut répondre à cette notification en lisant et en affichant la photo haute résolution du contact et les éléments de flux de réseaux sociaux les plus récents. Pour en savoir plus sur cette fonctionnalité, consultez la section Interactions avec des flux sur les réseaux sociaux.
viewGroupActivity
Nom de classe complet d'une activité de votre application pouvant afficher des informations de groupe. Lorsque l'utilisateur clique sur le libellé de groupe dans l'application Contacts de l'appareil, l'interface utilisateur de cette activité s'affiche.
viewGroupActionLabel
Libellé affiché par l'application Contacts pour une commande d'interface utilisateur qui permet à l'utilisateur d'afficher des groupes dans votre application.

Un identifiant de ressource de chaîne est autorisé pour cet attribut.

viewStreamItemActivity
Nom de classe complet d'une activité de votre application que l'application de contacts de l'appareil lance lorsque l'utilisateur clique sur un élément de flux pour rechercher un contact brut.
viewStreamItemPhotoActivity
Nom de classe complet d'une activité de votre application que l'application de contacts de l'appareil lance lorsque l'utilisateur clique sur une photo de l'élément du flux pour rechercher un contact brut.

Élément <ContactsDataKind>

L'élément <ContactsDataKind> contrôle l'affichage des lignes de données personnalisées de votre application dans l'interface utilisateur de l'application Contacts. Sa syntaxe est la suivante:

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

Contenu:

<ContactsAccountType>

Description :

Utilisez cet élément pour que l'application Contacts affiche le contenu d'une ligne de données personnalisée dans les détails d'un contact brut. Chaque élément enfant <ContactsDataKind> de <ContactsAccountType> représente un type de ligne de données personnalisée que votre adaptateur de synchronisation ajoute à la table ContactsContract.Data. Ajoutez un élément <ContactsDataKind> pour chaque type MIME personnalisé que vous utilisez. Vous n'avez pas besoin d'ajouter l'élément si vous disposez d'une ligne de données personnalisée pour laquelle vous ne souhaitez pas afficher de données.

Attributs:

android:mimeType
Type MIME personnalisé que vous avez défini pour l'un de vos types de lignes de données personnalisées dans la table ContactsContract.Data. Par exemple, la valeur vnd.android.cursor.item/vnd.example.locationstatus peut être un type MIME personnalisé pour une ligne de données qui enregistre la dernière position connue d'un contact.
android:icon
Ressource drawable Android que l'application Contacts affiche à côté de vos données. Utilisez-le pour indiquer à l'utilisateur que les données proviennent de votre service.
android:summaryColumn
Nom de colonne pour la première des deux valeurs récupérées à partir de la ligne de données. La valeur s'affiche sur la première ligne de l'entrée pour cette ligne de données. La première ligne est destinée à être utilisée comme un résumé des données, mais cela est facultatif. Voir aussi android:detailColumn.
android:detailColumn
Nom de colonne pour la seconde des deux valeurs récupérées à partir de la ligne de données. La valeur s'affiche dans la deuxième ligne de l'entrée pour cette ligne de données. Voir aussi android:summaryColumn.

Fonctionnalités supplémentaires du fournisseur de contacts

Outre les principales fonctionnalités décrites dans les sections précédentes, le fournisseur de contacts propose les fonctionnalités utiles suivantes pour utiliser les données des contacts:

  • Groupes de contacts
  • Fonctionnalités photo

Groupes de contacts

Le fournisseur de contacts peut éventuellement ajouter des libellés à des collections de contacts associés à l'aide de données de groupe. Si le serveur associé à un compte utilisateur souhaite gérer des groupes, l'adaptateur de synchronisation pour le type de compte du compte doit transférer les données des groupes entre le fournisseur de contacts et le serveur. Lorsque les utilisateurs ajoutent un nouveau contact au serveur, puis le placent dans un nouveau groupe, l'adaptateur de synchronisation doit ajouter le nouveau groupe à la table ContactsContract.Groups. Le ou les groupes auxquels appartient un contact brut sont stockés dans la table ContactsContract.Data à l'aide du type MIME ContactsContract.CommonDataKinds.GroupMembership.

Si vous concevez un adaptateur de synchronisation qui ajoute des données de contact brutes du serveur au fournisseur de contacts et que vous n'utilisez pas de groupes, vous devez demander au fournisseur de rendre vos données visibles. Dans le code exécuté lorsqu'un utilisateur ajoute un compte à l'appareil, mettez à jour la ligne ContactsContract.Settings ajoutée par le fournisseur de contacts pour le compte. Sur cette ligne, définissez la valeur de la colonne Settings.UNGROUPED_VISIBLE sur 1. Dans ce cas, Contacts Provider rend toujours les données de vos contacts visibles, même si vous n'utilisez pas de groupes.

Photos du contact

La table ContactsContract.Data stocke les photos sous forme de lignes de type MIME Photo.CONTENT_ITEM_TYPE. La colonne CONTACT_ID de la ligne est associée à la colonne _ID du contact brut auquel elle appartient. La classe ContactsContract.Contacts.Photo définit un sous-tableau de ContactsContract.Contacts contenant des informations sur la photo principale d'un contact, qui est la photo principale du contact brut principal. De même, la classe ContactsContract.RawContacts.DisplayPhoto définit un sous-tableau de ContactsContract.RawContacts contenant des informations sur la photo principale d'un contact brut.

La documentation de référence pour ContactsContract.Contacts.Photo et ContactsContract.RawContacts.DisplayPhoto contient des exemples de récupération d'informations sur les photos. Il n'existe pas de classe pratique permettant de récupérer la vignette principale d'un contact brut, mais vous pouvez envoyer une requête à la table ContactsContract.Data, en sélectionnant l'_ID du contact brut, la Photo.CONTENT_ITEM_TYPE et la colonne IS_PRIMARY pour trouver la ligne de la photo principale du contact brut.

Les données de flux sur les réseaux sociaux d'une personne peuvent également inclure des photos. Celles-ci sont stockées dans la table android.provider.ContactsContract.StreamItemPhotos, plus en détail dans la section Photos de flux de réseaux sociaux.