Provider di contatti

Il provider di contatti è un componente Android potente e flessibile che gestisce il repository centrale di dati sulle persone. Il fornitore di contatti è la fonte dei dati visualizzati nell'applicazione Contatti del dispositivo. Puoi anche accedere ai suoi dati nella tua applicazione e trasferire dati tra il dispositivo e i servizi online. Il fornitore supporta una vasta gamma di origini dati e cerca di gestire il maggior numero possibile di dati per ogni persona, con il risultato che la sua organizzazione è complessa. Per questo motivo, l'API del provider include una un ampio insieme di classi e interfacce contrattuali che facilitano sia il recupero che modifica.

Questa guida descrive quanto segue:

  • La struttura di base del provider.
  • Come recuperare i dati dal provider.
  • Come modificare i dati nel provider.
  • Come scrivere un'app di aggiornamento per sincronizzare i dati dal tuo server al fornitore di servizi di contatto.

Questa guida presuppone che tu conosca le nozioni di base sui fornitori di contenuti Android. Per scoprire di più sui fornitori di contenuti Android, leggi le guida ai concetti di base per i fornitori di contenuti.

Organizzazione provider di contatti

Il provider di contatti è un componente del fornitore di contenuti Android. Mantiene tre tipi di Dati relativi a una persona, ognuno dei quali corrisponde a una tabella offerta dal fornitore, illustrato nella figura 1:

Figura 1. Struttura della tabella del fornitore di contatti.

Le tre tabelle vengono comunemente indicate con i nomi dei relativi tipi di contratto. Le classi definiscono costanti per gli URI dei contenuti, i nomi delle colonne e i valori delle colonne utilizzati dalle tabelle:

ContactsContract.Contacts tabella
Righe che rappresentano persone diverse, in base a aggregazioni di righe di contatto non elaborate.
ContactsContract.RawContacts tabella
Righe contenenti un riepilogo dei dati di una persona, specifici per un account utente e un tipo.
ContactsContract.Data tabella
Righe contenenti i dettagli del contatto non elaborato, come indirizzi email o numeri di telefono.

Le altre tabelle rappresentate dalle classi contrattuali in ContactsContract sono tabelle ausiliarie utilizzate dal provider di contatti per gestire le proprie operazioni o l'assistenza funzioni specifiche nei contatti o nelle applicazioni di telefonia del dispositivo.

Contatti non elaborati

Un contatto non elaborato rappresenta i dati di una persona provenienti da un singolo tipo di account e dal nome dell'account. Poiché il provider di contatti consente più di un servizio online come origine di dati di una persona, il provider di contatti consente più contatti non elaborati per la stessa persona. Più contatti non elaborati consentono inoltre a un utente di combinare i dati di una persona provenienti da più account dello stesso tipo di account.

La maggior parte dei dati di un contatto non elaborato non è memorizzata nella tabella ContactsContract.RawContacts. ma in una o più righe della tabella ContactsContract.Data. Ogni riga di dati ha una colonna Data.RAW_CONTACT_ID che contiene il valore RawContacts._ID del suo riga ContactsContract.RawContacts principale.

Colonne importanti dei contatti non elaborati

Le colonne importanti della tabella ContactsContract.RawContacts sono elencate nella tabella 1. Leggi le note che seguono la tabella:

Tabella 1. Colonne importanti dei contatti non elaborati.

Nome colonna Usa Note
ACCOUNT_NAME Il nome dell'account per il tipo di account che ha dato origine a questo contatto non elaborato. Ad esempio, il nome di un Account Google è uno degli indirizzi Gmail del proprietario del dispositivo indirizzi IP esterni. Per ulteriori informazioni, consulta la voce successiva per ACCOUNT_TYPE. Il formato di questo nome è specifico per il tipo di account. Non deve necessariamente essere un indirizzo email.
ACCOUNT_TYPE Il tipo di account che è la fonte di questo contatto non elaborato. Ad esempio, l'account il tipo di Account Google è com.google. Specifica sempre il tipo di account con un identificatore di dominio per un dominio di tua proprietà o di tua competenza. In questo modo avrai la certezza che è univoco. Un tipo di account che offre dati sui contatti di solito è associato a un adattatore di sincronizzazione, si sincronizza con il provider di contatti.
DELETED L'elemento "eliminato" per un contatto non elaborato. Questo flag consente al provider di contatti di mantenere la riga internamente fino alla sincronizzazione gli adattatori sono in grado di eliminare la riga dai propri server e dal repository.

Note

Di seguito sono riportate alcune note importanti sull' Tabella ContactsContract.RawContacts:

  • Il nome di un contatto non elaborato non è archiviato nella sua riga in ContactsContract.RawContacts. Viene invece memorizzato nella tabella ContactsContract.Data, in una riga ContactsContract.CommonDataKinds.StructuredName. Un contatto non elaborato ha una sola riga di questo tipo nella tabella ContactsContract.Data.
  • Attenzione:per utilizzare i dati del tuo account in una riga di contatto non elaborata, è necessario la prima registrazione presso AccountManager. A questo scopo, invia un prompt agli utenti di aggiungere il tipo di account e il nome del proprio account all'elenco di account. In caso contrario al termine di questa operazione, il provider di contatti eliminerà automaticamente la riga del contatto non elaborata.

    Ad esempio, se vuoi che l'app conservi i dati dei contatti per il servizio basato sul web con il dominio com.example.dataservice e l'account dell'utente per il tuo servizio è becky.sharp@dataservice.example.com, l'utente deve prima aggiungere l'account "type" (com.example.dataservice) e "nome" dell'account (becky.smart@dataservice.example.com) prima che la tua app possa aggiungere righe di contatto non elaborate. Puoi spiegare questo requisito all'utente nella documentazione oppure puoi richiedere all'utente di aggiungere il tipo e il nome, o entrambi. Tipi e nomi di account sono descritti più dettagliatamente nella prossima sezione.

Origini dei dati non elaborati dei contatti

Per capire come funzionano i contatti non elaborati, prendi in considerazione l'utente "Emily Dickinson" che ha i seguenti tre account utente definiti sul suo dispositivo:

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

Questo utente ha abilitato la sincronizzazione dei contatti per tutti e tre questi account in Impostazioni Account.

Supponiamo che Emily Dickinson apra una finestra del browser e acceda a Gmail emily.dickinson@gmail.com, apre Contatti e aggiunge "Thomas Higginson". In seguito, accede a Gmail come emilyd@gmail.com e invia un'email a "Thomas Higginson", che automaticamente lo aggiunge come contatto. Segue anche "colon_tom" (ID Twitter di Thomas Higginson) su Twitter

Come risultato di questo lavoro, il provider di contatti crea tre contatti non elaborati:

  1. Un contatto grezzo per "Thomas Higginson" associati a emily.dickinson@gmail.com. Il tipo di account utente è Google.
  2. Un secondo contatto non elaborato per "Thomas Higginson" associati a emilyd@gmail.com. Il tipo di account utente è anche Google. C'è un secondo contatto grezzo persino identico a un nome precedente, perché la persona è stata aggiunta per un un altro account utente.
  3. Un terzo contatto non elaborato per "Thomas Higginson" associata a "belle_of_amherst". Il tipo di account utente è Twitter.

Dati

Come detto in precedenza, i dati di un contatto non elaborato vengono archiviati in ContactsContract.Data riga collegata ai dati del contatto non elaborato Valore _ID. Ciò consente a un singolo contatto non elaborato di avere più istanze dello stesso Tipo di dati come indirizzi email o numeri di telefono. Ad esempio, se "Thomas Higginson" per emilyd@gmail.com (la riga di contatto non elaborata di Thomas Higginson associato all'Account Google emilyd@gmail.com) ha un indirizzo email di casa di thigg@gmail.com e un indirizzo email di lavoro di thomas.higginson@gmail.com, il provider di contatti archivia i due indirizzi email di righe e le collega entrambi al contatto non elaborato.

Tieni presente che in questa tabella sono archiviati diversi tipi di dati. Le righe dei dettagli di nome visualizzato, numero di telefono, email, indirizzo postale, foto e sito web si trovano nella tabella ContactsContract.Data. Per gestire più facilmente questa situazione, La tabella ContactsContract.Data ha alcune colonne con nomi descrittivi, e altri con nomi generici. I contenuti di una colonna con nome descrittivo hanno lo stesso significato a prescindere dal tipo di dati nella riga, mentre i contenuti di una colonna con nome generico significati diversi a seconda del tipo di dati.

Nomi di colonna descrittivi

Ecco alcuni esempi di nomi di colonna descrittivi:

RAW_CONTACT_ID
Il valore della colonna _ID del contatto non elaborato per questi dati.
MIMETYPE
Il tipo di dati archiviati in questa riga, espresso come tipo MIME personalizzato. Il provider di contatti utilizza i tipi MIME definiti nei sottoclassi di ContactsContract.CommonDataKinds. Questi tipi MIME sono open source, e può essere utilizzato da qualsiasi applicazione o adattatore di sincronizzazione che funzioni con il provider di contatti.
IS_PRIMARY
Se questo tipo di riga di dati può comparire più di una volta per un contatto non elaborato, IS_PRIMARY flag di colonna la riga di dati che contiene i dati primari per il tipo. Ad esempio, se l'utente preme a lungo un numero di telefono di un contatto e seleziona Imposta predefinito, la riga ContactsContract.Data contenente il numero ha la colonna IS_PRIMARY impostata su un valore diverso da zero.

Nomi di colonne generiche

Esistono 15 colonne generiche denominate da DATA1 a DATA15 disponibili a livello generale e altre quattro colonne generiche da SYNC1 a SYNC4 che devono essere utilizzate solo dagli adattatori di sincronizzazione. Le costanti dei nomi di colonna generici funzionano sempre, indipendentemente dal tipo di dati contenuti nella riga.

La colonna DATA1 è indicizzata. Il fornitore di contatti utilizza sempre questa colonna per i dati che il fornitore ritiene essere il target più frequente di una query. Ad esempio: in una riga email, questa colonna contiene l'indirizzo email effettivo.

Per convenzione, la colonna DATA15 è riservata all'archiviazione di dati BLOB (oggetti binari di grandi dimensioni), come le miniature delle foto.

Nomi di colonne specifici per tipo

Per facilitare l'utilizzo delle colonne di un particolare tipo di riga, il provider di contatti fornisce anche costanti dei nomi delle colonne specifiche per il tipo, definite nelle sottoclassi ContactsContract.CommonDataKinds. Le costanti danno semplicemente costante diversa allo stesso nome di colonna, che consente di accedere ai dati in una riga di tipo specifico.

Ad esempio, la classe ContactsContract.CommonDataKinds.Email definisce costanti del nome della colonna specifiche del tipo per una riga ContactsContract.Data con tipo MIME Email.CONTENT_ITEM_TYPE. La classe contiene la costante ADDRESS per l'indirizzo email colonna. Il valore effettivo di ADDRESS è "data1", che corrisponde al nome generico della colonna.

Attenzione: non aggiungere dati personalizzati alla ContactsContract.Data utilizzando una riga che include uno degli tipi MIME predefiniti del provider. In questo caso, potresti perdere i dati o far sì che il provider un malfunzionamento. Ad esempio, non devi aggiungere una riga con tipo MIME Email.CONTENT_ITEM_TYPE che contiene un nome utente anziché un indirizzo email nella colonna DATA1. Se utilizzi il tuo tipo MIME personalizzato per la riga, puoi per definire nomi di colonna personalizzati per il tipo e utilizza le colonne come preferisci.

La figura 2 mostra come le colonne descrittive e le colonne di dati vengono visualizzate in un ContactsContract.Data riga e il nome "overlay" per la colonna specifica per tipo i nomi generici delle colonne

Come i nomi delle colonne specifici per tipo vengono mappati ai nomi delle colonne generiche

Figura 2. Nomi di colonne specifici per tipo e nomi di colonne generici.

Classi di nomi di colonna specifici per tipo

La tabella 2 elenca le classi di nomi di colonne specifici per tipo più utilizzati:

Tabella 2. Classi di nomi di colonna specifici per tipo

Corso di mappatura Tipo di dati Note
ContactsContract.CommonDataKinds.StructuredName I dati del nome del contatto non elaborato associato a questa riga di dati. Un contatto non elaborato ha una sola di queste righe.
ContactsContract.CommonDataKinds.Photo La foto principale del contatto non elaborato associato a questa riga di dati. Un contatto non elaborato ha solo una di queste righe.
ContactsContract.CommonDataKinds.Email Un indirizzo email per il contatto non elaborato associato a questa riga di dati. Un contatto non elaborato può avere più indirizzi email.
ContactsContract.CommonDataKinds.StructuredPostal Un indirizzo postale per il contatto non elaborato associato a questa riga di dati. Un contatto non elaborato può avere più indirizzi postali.
ContactsContract.CommonDataKinds.GroupMembership Un identificatore che collega il contatto non elaborato a uno dei gruppi nel provider di contatti. I gruppi sono una funzionalità facoltativa di un tipo di account e del nome dell'account. Sono descritti in per saperne di più nella sezione Gruppi di contatti.

Contatti

Il provider di contatti combina le righe dei contatti non elaborate di tutti i tipi di account e nomi di account per formare un contatto. Semplifica la visualizzazione e la modifica di tutti i dati raccolti dall'utente per una persona. Il provider di contatti gestisce la creazione di nuovi contatti e l'aggregazione di contatti non elaborati con una riga di contatto esistente. Né le applicazioni né gli adattatori di sincronizzazione possono aggiungere contatti e alcune colonne di una riga di contatti sono di sola lettura.

Nota: se provi ad aggiungere un contatto al provider di contatti con un valore insert(), verrà generata un'eccezione UnsupportedOperationException. Se provi ad aggiornare una colonna elencata come "sola lettura", l'aggiornamento viene ignorato.

Il provider di contatti crea un nuovo contatto in risposta all'aggiunta di un nuovo contatto non elaborato che non corrisponde a nessun contatto esistente. Il fornitore esegue questa operazione anche se i dati di un contatto grezzo esistente cambiano in modo da non corrispondere più al contatto a cui era precedentemente associato. Se un'applicazione o un'apposita funzionalità di sincronizzazione crea un nuovo contatto non elaborato che corrisponde a un contatto esistente, il nuovo contatto non elaborato viene aggregato al contatto esistente.

Il provider di contatti collega una riga di contatto alle relative righe di contatti non elaborati con il valore Colonna _ID in Contacts tabella. La colonna CONTACT_ID della tabella dei contatti non elaborati ContactsContract.RawContacts contiene _ID valori per alla riga dei contatti associata a ogni riga dei contatti non elaborati.

La tabella ContactsContract.Contacts contiene anche la colonna LOOKUP_KEY che è un "permanente" alla riga del contatto. Poiché il provider di contatti mantiene i contatti potrebbe modificare il valore _ID di una riga di contatto in risposta a un'aggregazione o una sincronizzazione. Anche in questo caso, l'URI dei contenuti CONTENT_LOOKUP_URI combinato con LOOKUP_KEY del contatto continuerà a puntare alla riga del contatto, quindi puoi utilizzare LOOKUP_KEY per mantenere i link ai contatti "preferiti" e così via. Questa colonna ha un proprio formato non correlati al formato della colonna _ID.

La figura 3 mostra la correlazione tra le tre tabelle principali.

Tabelle principali del provider di contatti

Figura 3. Relazioni tabella Contatti, Contatti non elaborati e Dettagli.

Attenzione: se pubblichi la tua app sul Google Play Store o se la tua app è su un dispositivo con Android 10 (livello API 29) o versioni successive, tieni presente che un insieme limitato di metodi e campi dei dati di contatto è obsoleto.

Nelle condizioni indicate, il sistema cancella periodicamente tutti i valori scritte in questi campi di dati:

Anche le API utilizzate per impostare i campi di dati precedenti sono obsolete:

Inoltre, i seguenti campi non restituiscono più i contatti frequenti. Nota che alcuni di questi campi influenzano il ranking dei contatti solo quando i contatti fanno parte di una dati tipo.

Se le tue app accedono o aggiornano questi campi o queste API, utilizza metodi alternativi. Ad esempio, puoi soddisfare determinati casi d'uso utilizzando privato dei fornitori di contenuti o di altri dati archiviati all'interno dell'app o del backend sistemi operativi.

Per verificare che la funzionalità della tua app non sia interessata da questa modifica, puoi cancellare manualmente questi campi di dati. Per farlo, esegui il seguente comando ADB su un dispositivo con Android 4.1 (livello API 16) o versioni successive:

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

Dati da adattatori di sincronizzazione

Gli utenti inseriscono i dati dei contatti direttamente nel dispositivo, ma i dati passano anche in Contatti Provider di servizi web tramite adattatore di sincronizzazione, che automatizzano il trasferimento di dati tra il dispositivo e i servizi. Gli adattatori di sincronizzazione vengono eseguiti in background sotto il controllo del sistema e chiamano i metodi ContentResolver per gestire i dati.

In Android, il servizio web con cui funziona un adattatore di sincronizzazione è identificato da un tipo di account. Ogni adattatore di sincronizzazione funziona con un tipo di account, ma può supportare più nomi di account per quel tipo. I tipi e i nomi degli account sono descritti brevemente nella sezione Origini dei dati dei contatti non elaborati. Le seguenti definizioni offrono e descrivere la correlazione tra tipo e nome di account, adattatori di sincronizzazione e servizi.

Tipo di account
Identifica un servizio in cui l'utente ha archiviato dati. La maggior parte del tempo, l'utente deve a eseguire l'autenticazione con il servizio. Ad esempio, Contatti Google è un tipo di account identificato dal codice google.com. Questo valore corrisponde al tipo di account utilizzato AccountManager.
Nome account
Identifica un determinato account o login per un tipo di account. Account Contatti Google corrispondono agli Account Google, che hanno un indirizzo email come nome account. Altri servizi potrebbero utilizzare un nome utente composto da una sola parola o un ID numerico.

I tipi di account non devono essere univoci. Un utente può configurare più account Contatti Google e scaricare i propri dati nel Fornitore di contatti; questo può accadere se l'utente ha un insieme contatti personali per un nome account personale e un altro impostato per il lavoro. I nomi degli account sono solitamente univoci. Insieme, identificano un flusso di dati specifico tra il provider di contatti e un servizio esterno.

Se vuoi trasferire i dati del tuo servizio al fornitore di contatti, devi scrivere il tuo adattatore di sincronizzazione. Questa procedura viene descritta più dettagliatamente nella sezione Adattatori di sincronizzazione del fornitore di contatti.

La Figura 4 mostra il modo in cui il provider di contatti si inserisce nel flusso di dati sulle persone. Nella casella "adattatori di sincronizzazione", ogni adattatore è etichettato in base al tipo di account.

Flusso di dati sulle persone

Figura 4. Il flusso di dati del provider di contatti.

Autorizzazioni richieste

Le applicazioni che vogliono accedere al fornitore di contatti devono richiedere le seguenti autorizzazioni:

Accesso in lettura a una o più tabelle
READ_CONTACTS, specificato in AndroidManifest.xml con l'elemento <uses-permission> come <uses-permission android:name="android.permission.READ_CONTACTS">.
Accesso in scrittura a una o più tabelle
WRITE_CONTACTS, specificato in AndroidManifest.xml con l'elemento <uses-permission> come <uses-permission android:name="android.permission.WRITE_CONTACTS">.

Queste autorizzazioni non si applicano ai dati del profilo utente. Il profilo utente e le relative autorizzazioni richieste sono descritti nella sezione Il profilo utente che segue.

Ricorda che i dati dei contatti dell'utente sono personali e sensibili. Gli utenti sono preoccupati la loro privacy, quindi non vogliono che le applicazioni raccolgano dati su di loro o sui loro contatti. Se non è chiaro il motivo per cui hai bisogno dell'autorizzazione per accedere ai dati dei suoi contatti, il team potrebbe darti le valutazioni dell'applicazione sono scarse o semplicemente rifiutarsi di installarla.

Il profilo dell'utente

La tabella ContactsContract.Contacts ha una singola riga contenente i dati del profilo dell'utente del dispositivo. Questi dati descrivono invece il valore user del dispositivo rispetto a uno dei contatti dell'utente. La riga dei contatti del profilo è collegata a una riga di contatti non elaborati per ogni sistema che utilizza un profilo. Ogni riga di contatto non elaborata del profilo può avere più righe di dati. Le costanti per accedere al profilo utente sono disponibili nella classe ContactsContract.Profile.

L'accesso al profilo utente richiede autorizzazioni speciali. Oltre alle autorizzazioni READ_CONTACTS e WRITE_CONTACTS necessarie per la lettura e la scrittura, l'accesso al profilo dell'utente richiede le autorizzazioni android.Manifest.permission#READ_PROFILE e android.Manifest.permission#WRITE_PROFILE per l'accesso in lettura e scrittura.

Ricorda che devi considerare sensibile il profilo di un utente. L'autorizzazione android.Manifest.permission#READ_PROFILE ti consente di accedere ai che consentono l'identificazione personale. Assicurati di comunicare all'utente il motivo per cui hai bisogno delle autorizzazioni di accesso al profilo utente nella descrizione della tua applicazione.

Per recuperare la riga del contatto che contiene il profilo dell'utente, chiama ContentResolver.query(). Imposta l'URI dei contenuti su CONTENT_URI e non specificarne criteri di selezione. Puoi anche utilizzare questo URI dei contenuti come URI di base per recuperare i dati o i contatti non elaborati per il profilo. Ad esempio, questo snippet recupera i dati per il profilo:

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

Nota: se recuperi più righe di contatto e vuoi determinare se una di queste è il profilo utente, testa la colonna IS_USER_PROFILE della riga. Questa colonna è impostato su "1" se il contatto è il profilo dell'utente.

Metadati del provider di contatti

Il provider di contatti gestisce i dati che tiene traccia dello stato dei dati dei contatti nel repository Git. Questi metadati relativi al repository sono archiviati in varie posizioni, tra cui nelle righe della tabella Contatti, Dati e Contatti non elaborati, il ContactsContract.Settings e la tabella Tabella ContactsContract.SyncState. La tabella seguente mostra le l'effetto di ciascuno di questi metadati:

Tabella 3. Metadati nel provider di contatti

Tabella Colonna Valori Significato
ContactsContract.RawContacts DIRTY "0": non modificato dall'ultima sincronizzazione. Contrassegni i contatti non elaborati che sono stati modificati sul dispositivo e devono essere sincronizzati nuovamente con il server. Il valore viene impostato automaticamente dal provider di contatti quando Android aggiornano una riga.

Gli adattatori di sincronizzazione che modificano le tabelle di dati o dei contatti non elaborati devono sempre aggiungere la stringa CALLER_IS_SYNCADAPTER all'URI dei contenuti che utilizzano. In questo modo, il provider non contrassegna le righe come sporche. Altrimenti, le modifiche all'adattatore di sincronizzazione sembrano essere modifiche locali e inviati al server, anche se quest'ultimo era l'origine della modifica.

"1": modificato dall'ultima sincronizzazione, deve essere sincronizzato di nuovo con il server.
ContactsContract.RawContacts VERSION Il numero di versione di questa riga. Il provider di contatti incrementa automaticamente questo valore ogni volta che la riga o e le modifiche ai dati correlati.
ContactsContract.Data DATA_VERSION Il numero di versione di questa riga. Il provider di contatti incrementa automaticamente questo valore ogni volta che la riga di dati viene modificata.
ContactsContract.RawContacts SOURCE_ID Un valore di stringa che identifica in modo univoco questo contatto non elaborato per l'account in cui è stato creato. Quando un'entità di aggiornamento sincronizza un nuovo contatto non elaborato, questa colonna deve essere impostata sull'ID univoco del server per il contatto non elaborato. Quando un'applicazione Android crea un nuovo contatto non elaborato, deve lasciare questa colonna vuota. Questo indica all'adattatore di sincronizzazione di creare un nuovo contatto non elaborato sul server e di ottenere un valore per SOURCE_ID.

In particolare, l'ID origine deve essere univoco per ogni tipo di account e deve essere stabile durante le sincronizzazioni:

  • Univoco: ogni contatto non elaborato per un account deve avere un proprio ID sorgente. Se non lo imposti, causerai problemi nell'applicazione Contatti. Tieni presente che due contatti non elaborati per lo stesso tipo di account potrebbero avere lo stesso ID origine. Ad esempio, il contatto non elaborato "Thomas Higginson" per l'account emily.dickinson@gmail.com può avere lo stesso ID di origine del contatto non elaborato "Thomas Higginson" per l'account emilyd@gmail.com.
  • Stabile: gli ID origine fanno parte permanente dei dati del servizio online per il contatto non elaborato. Ad esempio, se l'utente cancella lo spazio di archiviazione dei contatti dalle impostazioni delle app e esegue nuovamente la sincronizzazione, i contatti non elaborati ripristinati dovrebbero avere gli stessi ID di origine di prima. Se non esegui l'applicazione forzata, le scorciatoie verranno interrotte funziona.
ContactsContract.Groups GROUP_VISIBLE "0": i contatti in questo gruppo non devono essere visibili nelle UI delle applicazioni per Android. Questa colonna è per la compatibilità con i server che consentono a un utente di nascondere i contatti in determinati gruppi.
"1": i contatti di questo gruppo possono essere visibili nelle UI delle applicazioni.
ContactsContract.Settings UNGROUPED_VISIBLE "0" - Per questo account e tipo di account, i contatti che non appartengono a un gruppo vengono invisibile all'interfaccia utente delle app Android. Per impostazione predefinita, i contatti sono invisibili se nessuno dei relativi contatti non elaborati appartiene a un gruppo (l'appartenenza a un gruppo per un contatto non elaborato è indicata da una o più righe ContactsContract.CommonDataKinds.GroupMembership nella tabella ContactsContract.Data). Impostando questo flag nella riga della tabella ContactsContract.Settings per un tipo di account e un account, puoi forzare la visibilità dei contatti senza gruppi. Un utilizzo di questo flag è mostrare i contatti dei server che non utilizzano i gruppi.
"1": per questo account e tipo di account, i contatti che non appartengono a un gruppo sono visibili alle UI dell'applicazione.
ContactsContract.SyncState (tutti) Usa questa tabella per archiviare i metadati per l'adattatore di sincronizzazione. Con questa tabella puoi memorizzare in modo permanente sullo stato della sincronizzazione e su altri dati correlati alla sincronizzazione sul dispositivo.

Accesso provider di contatti

Questa sezione descrive le linee guida per accedere ai dati del fornitore di contatti, concentrandosi su quanto segue:

  • Query sulle entità.
  • Modifica in gruppo.
  • Recupero e modifica con intent.
  • Integrità dei dati.

Anche le modifiche apportate da un adattatore di sincronizzazione sono trattate in modo più dettagliato nella sezione. Adattatori di sincronizzazione del fornitore di contatti.

Eseguire query sulle entità

Poiché le tabelle del provider di contatti sono organizzate in una gerarchia, spesso è utile recupera una riga e tutti gli elementi "secondari" alle righe collegate. Ad esempio, per visualizzare tutte le informazioni su una persona, potresti voler recuperare tutte le righe ContactsContract.RawContacts per una singola riga ContactsContract.Contacts o tutte le righe ContactsContract.CommonDataKinds.Email per una singola riga ContactsContract.RawContacts. Per semplificare questa operazione, il fornitore di contatti offre costrutti di entità, che agiscono come join del database tra le tabelle.

Un'entità è come una tabella composta da colonne selezionate da una tabella principale e dalla relativa tabella secondaria. Quando esegui una query su un'entità, fornisci una proiezione e criteri di ricerca basati sulle colonne disponibili dall'entità. Il risultato è un Cursor che contiene una riga per ogni riga della tabella secondaria recuperata. Ad esempio, se esegui una query su ContactsContract.Contacts.Entity per un nome di contatto e su tutte le righe ContactsContract.CommonDataKinds.Email per tutti i contatti non elaborati corrispondenti a quel nome, viene restituito un Cursor contenente una riga per ogni riga ContactsContract.CommonDataKinds.Email.

Le entità semplificano le query. Utilizzando un'entità, puoi recuperare tutti i dati dei contatti per un un contatto non elaborato o un contatto non elaborato contemporaneamente, invece di dover prima eseguire una query sulla tabella padre per ottenere e poi eseguire una query sulla tabella figlio con quell'ID. Inoltre, il fornitore di contatti elabora una query su un'entità in un'unica transazione, il che garantisce che i dati recuperati siano internamente coerenti.

Nota: in genere un'entità non contiene tutte le colonne della tabella principale e di quella secondaria. Se provi a utilizzare un nome di colonna non presente nell'elenco delle costanti dei nomi di colonna per l'entità, verrà visualizzato un Exception.

Lo snippet riportato di seguito mostra come recuperare tutte le righe di contatto non elaborate di un contatto. Lo snippet fa parte di un'applicazione più grande con due attività, "main" e "detail". L'attività principale mostra un elenco di righe di contatto. Quando l'utente ne seleziona una, l'attività invia il relativo ID all'attività dettagliata. L'attività di dettaglio utilizza ContactsContract.Contacts.Entity per visualizzare tutte le righe di dati di tutti i contatti non elaborati associati al contatto selezionato.

Questo snippet viene recuperato dalla sezione attività:

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

Al termine del caricamento, LoaderManager richiama un callback per onLoadFinished(). Uno degli argomenti in entrata di questo metodo è Cursor con i risultati della query. Nella tua app puoi ottenere dati di questo Cursor per visualizzarli o utilizzarli ulteriormente.

Modifica collettiva

Ove possibile, devi inserire, aggiornare ed eliminare i dati nel provider di contatti in "modalità batch", creando un ArrayList di oggetti ContentProviderOperation e chiamando applyBatch(). Poiché il provider di contatti esegue tutte le operazioni in un applyBatch() in una singola transazione, le modifiche non lasceranno mai il repository dei contatti in uno stato inconsistente. Una modifica di batch semplifica anche l'inserimento di un contatto non elaborato e dei suoi dati dettagliati contemporaneamente.

Nota: per modificare un singolo contatto non elaborato, ti consigliamo di inviare un'intent all'applicazione Contatti del dispositivo anziché gestire la modifica nella tua app. Questa operazione è descritta in modo più dettagliato nella sezione Recupero e modifica con gli intent.

Punti di rendimento

Una modifica batch contenente molte operazioni può bloccare altri processi, con un conseguente peggioramento dell'esperienza utente complessiva. Per organizzare tutte le modifiche che vuoi eseguire in un numero minimo di elenchi separati e, al contempo, impedire che blocchino il sistema, devi impostare i punti di rendimento per una o più operazioni. Un punto di rendimento è un oggetto ContentProviderOperation che ha le sue Valore isYieldAllowed() impostato su true. Quando il provider di contatti rileva un punto di rendimento, interrompe il suo lavoro per lascia eseguire altri processi e chiude la transazione corrente. Quando il provider si riavvia, continua con l'operazione successiva in ArrayList e avvia una nuova transazione.

I punti di rendimento generano più di una transazione per chiamata a applyBatch(). A causa di dovresti impostare un punto di rendimento per l'ultima operazione per un insieme di righe correlate. Ad esempio, devi impostare un punto di snervamento per l'ultima operazione in un insieme che aggiunge un righe di contatto non elaborate e le relative righe di dati associate o l'ultima operazione per un insieme di righe relative a un singolo contatto.

I punti di rendimento sono anche un'unità di operazione atomica. Tutti gli accessi tra due punti di rendimento hanno esito positivo o negativo come singola unità. Se non imposti punti di rendimento, l'operazione atomica più piccola è l'intero batch di operazioni. Se utilizzi i punti di rendimento, eviti che le operazioni peggiorino il rendimento del sistema, garantendo al contempo che un sottoinsieme di operazioni sia atomico.

Modifica dei riferimenti precedenti

Quando inserisci una nuova riga di contatto non elaborato e le relative righe di dati associate come insieme di oggetti ContentProviderOperation, devi collegare le righe di dati alla riga di contatto non elaborato inserendo il valore _ID del contatto non elaborato come valore RAW_CONTACT_ID. Tuttavia, questo valore non è disponibile quando crei ContentProviderOperation per la riga di dati perché non hai ancora applicato ContentProviderOperation per la riga del contatto non elaborato. Per ovviare a questo, la classe ContentProviderOperation.Builder ha il metodo withValueBackReference(). Questo metodo consente di inserire o modificare una colonna con il risultato di un'operazione precedente.

Il metodo withValueBackReference() ha due argomenti:

key
La chiave di una coppia chiave-valore. Il valore di questo argomento deve essere il nome di una colonna nella tabella che stai modificando.
previousResult
Indica l'indice a partire da 0 di un valore nell'array di oggetti ContentProviderResult di applyBatch(). Come le operazioni in batch, il risultato di ogni operazione viene archiviato un array intermedio di risultati. Il valore previousResult è l'indice di uno di questi risultati, che viene recuperato e archiviato insieme all'elemento key valore. Ciò ti consente di inserire un nuovo record di contatto non elaborato e recuperare valore _ID, quindi crea un "riferimento a ritroso" alle quando aggiungi una riga ContactsContract.Data.

L'intero array di risultati viene creato alla prima chiamata applyBatch(), con una dimensione uguale a quella di ArrayList di ContentProviderOperation oggetti forniti. Tuttavia, tutte gli elementi nell'array dei risultati sono impostati su null e, se provi eseguire un rinvio a un risultato per un'operazione non ancora applicata, withValueBackReference() lancia Exception.

Gli snippet riportati di seguito mostrano come inserire un nuovo contatto non elaborato e dati in blocco. Loro Includere un codice che stabilisce un punto di rendimento e utilizza un backreference.

Il primo snippet recupera i dati di contatto dall'interfaccia utente. A questo punto, l'utente ha già selezionato l'account a cui aggiungere il nuovo contatto non elaborato.

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

Lo snippet successivo crea un'operazione per inserire la riga del contatto non elaborato nella tabella 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());

Successivamente, il codice crea delle righe di dati per le righe del nome visualizzato, del numero di telefono e dell'email.

Ogni oggetto del generatore di operazioni utilizza withValueBackReference() per ottenere RAW_CONTACT_ID. I punti di riferimento fanno riferimento all'oggetto ContentProviderResult della prima operazione, che aggiunge la riga del contatto non elaborato e restituisce il nuovo valore _ID. Di conseguenza, ogni riga di dati viene collegata automaticamente RAW_CONTACT_ID alla nuova riga ContactsContract.RawContacts a cui appartiene.

L'oggetto ContentProviderOperation.Builder che aggiunge la riga email è segnalato con withYieldAllowed(), che imposta un punto di rendimento:

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

L'ultimo snippet mostra la chiamata a applyBatch() che inserisce le nuove righe di dati e contatti non elaborati.

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

Le operazioni collettive ti consentono anche di implementare il controllo ottimistico della contemporaneità, un metodo per applicare le transazioni di modifica senza dover bloccare il repository sottostante. Per utilizzare questo metodo, si applica la transazione e poi si controlla la presenza di altre modifiche che possono essere state effettuate contemporaneamente. Se rilevi che si è verificata una modifica incoerente, rollback della transazione e riprova.

Il controllo della contemporaneità ottimistico è utile per i dispositivi mobili in cui c'è un solo utente una volta e gli accessi simultanei a un repository di dati sono rari. Poiché il blocco non viene utilizzato, senza perdere tempo a impostare i blocchi o ad attendere che vengano rilasciati da altre transazioni.

per utilizzare il controllo ottimistico della contemporaneità durante l'aggiornamento di un singolo ContactsContract.RawContacts riga, segui questi passaggi:

  1. Recupera VERSION del contatto non elaborato colonna insieme agli altri dati recuperati.
  2. Crea un oggetto ContentProviderOperation.Builder adatto per applicare una limitazione utilizzando il metodo newAssertQuery(Uri). Per l'URI dei contenuti, usa RawContacts.CONTENT_URI con l'aggiunta del valore _ID del contatto non elaborato.
  3. Per l'oggetto ContentProviderOperation.Builder, chiama withValue() per confrontare la colonna VERSION con il numero di versione appena recuperato.
  4. Per lo stesso ContentProviderOperation.Builder, chiama withExpectedCount() per garantire che venga testata una sola riga da questa asserzione.
  5. Chiama build() per creare l'oggetto ContentProviderOperation, quindi aggiungilo come primo oggetto in ArrayList da passare a applyBatch().
  6. Applica la transazione batch.

Se la riga del contatto non elaborato viene aggiornata da un'altra operazione tra il momento in cui la leggi e il momento in cui provi a modificarla, l'affermazione "assert" ContentProviderOperation non andrà a buon fine e l'intero batch di operazioni verrà annullato. Puoi quindi scegliere di ritentare il batch o di eseguire un'altra azione.

Lo snippet seguente mostra come creare un ContentProviderOperation "assert" dopo aver eseguito una query per un singolo contatto non elaborato utilizzando 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
    }

Recupero e modifica con intent

L'invio di un'intent all'applicazione Contatti del dispositivo ti consente di accedere indirettamente al fornitore di servizi di contatto. L'intent avvia l'interfaccia utente dell'applicazione dei contatti del dispositivo, in cui gli utenti possono lavorare con i contatti. Con questo tipo di accesso, gli utenti possono:

  • Seleziona un contatto da un elenco e recuperalo nella tua app per ulteriori operazioni.
  • Modifica i dati di un contatto esistente.
  • Inserire un nuovo contatto non elaborato per uno dei suoi account.
  • Eliminare i dati di un contatto o di più contatti.

Se l'utente sta inserendo o aggiornando i dati, puoi prima raccoglierli e inviarli come parte dell'intent.

Quando utilizzi gli intent per accedere al fornitore di contatti tramite l'applicazione Contatti del dispositivo, non devi scrivere la tua interfaccia utente o il tuo codice per accedere al fornitore. Inoltre, non devi e richiedere l'autorizzazione di lettura o scrittura al provider. L'applicazione Contatti del dispositivo può delegarti l'autorizzazione di lettura per un contatto e, poiché apporti modifiche al provider tramite un'altra applicazione, non devi disporre delle autorizzazioni di scrittura.

La procedura generale di invio di un'intenzione per accedere a un fornitore è descritta in dettaglio nella guida Elementi di base per i fornitori di contenuti, nella sezione "Accesso ai dati tramite intent". L'azione, il tipo MIME e i valori dei dati che utilizzi per le attività disponibili sono riassunti nella Tabella 4, mentre i valori extra che puoi utilizzare con putExtra() sono elencati nella documentazione di riferimento per ContactsContract.Intents.Insert:

Tabella 4. Intent del provider di contatti.

Attività Azione Dati Tipo MIME Note
Scegliere un contatto da un elenco ACTION_PICK Uno dei seguenti: Non utilizzato Mostra un elenco di contatti non elaborati o un elenco di dati di un contatto non elaborato, a seconda del tipo di URI dei contenuti fornito.

Chiama startActivityForResult(), che restituisce l'URI dei contenuti della riga selezionata. La forma dell'URI è l'URI dei contenuti della tabella con il LOOKUP_ID della riga aggiunto. L'app Contatti del dispositivo delega le autorizzazioni di lettura e scrittura a questo URI dei contenuti per tutta la durata della tua attività. Per ulteriori dettagli, consulta la guida Elementi di base per i fornitori di contenuti.

Inserire un nuovo contatto non elaborato Insert.ACTION N/D RawContacts.CONTENT_TYPE, tipo MIME per un insieme di contatti non elaborati. Viene visualizzata la schermata Aggiungi contatto dell'applicazione Contatti del dispositivo. La vengono mostrati i valori extra che aggiungi all'intent. Se inviato con startActivityForResult(), l'URI dei contenuti del contatto non elaborato appena aggiunto viene restituito ai dati onActivityResult() di callback di Google nell'argomento Intent, nel "dati" . Per ottenere il valore, chiama getData().
Modificare un contatto ACTION_EDIT CONTENT_LOOKUP_URI per il contatto. L'attività di modifica consentirà all'utente di modificare qualsiasi dato associato a questo contatto. Contacts.CONTENT_ITEM_TYPE, un singolo contatto. Mostra la schermata Modifica contatto nell'applicazione Contatti. Vengono visualizzati i valori extra aggiunti all'intent. Quando l'utente fa clic su Fine per salvare le modifiche, la tua attività torna in primo piano.
Visualizza un selettore che può anche aggiungere dati. ACTION_INSERT_OR_EDIT N/D CONTENT_ITEM_TYPE Questo intent mostra sempre la schermata di selezione dell'app dei contatti. L'utente può scegliere un contatto da modificare o aggiungerne uno nuovo. Usa la schermata di modifica o di aggiunta vengono visualizzati, a seconda della scelta dell'utente e dei dati extra che trasmetti nell'intent . Se l'app visualizza dati di contatto, ad esempio un indirizzo email o un numero di telefono, usa lo scopo di consentire all'utente di aggiungere i dati a un contatto esistente. contatto,

Nota: non è necessario inviare un valore name negli extra di questo intent, perché l'utente sceglie sempre un nome esistente o ne aggiunge uno nuovo. Inoltre, se invii un nome e l'utente sceglie di apportare una modifica, l'app dei contatti per visualizzare il nome inviato, sovrascrivendo il valore precedente. Se l'utente non la nota e salva la modifica, il valore precedente viene perso.

L'app Contatti del dispositivo non consente di eliminare un contatto non elaborato o i relativi dati con un l'intento. Per eliminare un contatto non elaborato, utilizza invece ContentResolver.delete() o ContentProviderOperation.newDelete().

Lo snippet seguente mostra come creare e inviare un'intenzione che inserisce un nuovo contatto e dati non elaborati:

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

Integrità dei dati

Perché il repository dei contatti contiene dati importanti e sensibili che gli utenti si aspettano di trovare corretto e aggiornato, il Provider di contatti ha regole ben definite per l'integrità dei dati. È la tua responsabilità di rispettare queste regole quando modifichi i dati dei contatti. L'importante sono elencate di seguito:

Aggiungi sempre una riga ContactsContract.CommonDataKinds.StructuredName per ogni riga ContactsContract.RawContacts aggiunta.
Una riga ContactsContract.RawContacts senza ContactsContract.CommonDataKinds.StructuredName riga in La tabella ContactsContract.Data potrebbe causare problemi durante e aggregazione.
Collega sempre le nuove righe ContactsContract.Data alla riga ContactsContract.RawContacts principale.
Una riga ContactsContract.Data non collegata a una ContactsContract.RawContacts non sarà visibile nel contatti e potrebbe causare problemi con gli adattatori di sincronizzazione.
Modifica i dati solo dei contatti non elaborati di tua proprietà.
Tieni presente che il fornitore di servizi di contatto in genere gestisce i dati di diversi tipi di account/servizi online. Devi assicurarti che l'applicazione modifichi o elimini solo i dati relativi alle righe di tua proprietà e che inserisca solo dati con un tipo di account e un nome controllati da te.
Utilizza sempre le costanti definite in ContactsContract e nelle relative per autorità, URI dei contenuti, percorsi URI, nomi di colonna, tipi MIME e TYPE valori.
L'utilizzo di queste costanti ti aiuta a evitare errori. Riceverai una notifica anche dal compilatore avvisi se una delle costanti è deprecata.

Righe di dati personalizzate

Creando e utilizzando i tuoi tipi MIME personalizzati, puoi inserire, modificare, eliminare e recuperare le tue righe di dati nella tabella ContactsContract.Data. Le tue righe si limitano all'utilizzo della colonna definita in ContactsContract.DataColumns, anche se puoi mappare la tua specifici del tipo con i nomi delle colonne predefiniti. Nell'applicazione Contatti del dispositivo, i dati delle righe vengono visualizzati ma non possono essere modificati o eliminati e gli utenti non possono aggiungere dati aggiuntivi. Per consentire agli utenti di modificare le righe di dati personalizzati, devi fornire un'attività di editor nella tua applicazione.

Per visualizzare i tuoi dati personalizzati, fornisci un file contacts.xml contenente un Elemento <ContactsAccountType> e uno o più dei suoi <ContactsDataKind> elementi secondari. Questo aspetto è descritto più dettagliatamente nel sezione <ContactsDataKind> element.

Per scoprire di più sui tipi MIME personalizzati, consulta la guida Creare un fornitore di contenuti.

Adattatori di sincronizzazione del provider di contatti

Il provider di contatti è progettato specificamente per gestire la sincronizzazione dei dati dei contatti tra un dispositivo e un servizio online. In questo modo, gli utenti possono scaricare i dati esistenti su un nuovo dispositivo e caricarli su un nuovo account. La sincronizzazione garantisce inoltre che gli utenti dispongano degli ultimi dati a portata di mano, dell'origine delle aggiunte e delle modifiche. Un altro vantaggio della sincronizzazione è che dati dei contatti disponibili anche quando il dispositivo non è connesso alla rete.

Sebbene tu possa implementare la sincronizzazione in diversi modi, il sistema Android fornisce un framework di sincronizzazione dei plug-in che automatizza le seguenti attività:

  • Verifica della disponibilità della rete.
  • Pianificazione ed esecuzione della sincronizzazione in base alle preferenze dell'utente.
  • Riavvio delle sincronizzazioni interrotte.

Per utilizzare questo framework, devi fornire un plug-in per l'adattatore di sincronizzazione. Ogni adattatore di sincronizzazione è specifico fornitore di servizi e contenuti, ma può gestire più nomi di account per lo stesso servizio. Il framework consente anche più adattatori di sincronizzazione per lo stesso servizio e fornitore.

File e classi dell'adattatore di sincronizzazione

Implementa un'app di sincronizzazione come sottoclasse di AbstractThreadedSyncAdapter e installala all'interno di un'applicazione Android. Il sistema apprende l'adattatore di sincronizzazione dagli elementi della tua applicazione e da uno speciale file XML a cui punta il manifest. Il file XML definisce il tipo di account per il servizio online e l'autorità per il fornitore di contenuti, che insieme a identificare in modo univoco l'adattatore. L'adattatore di sincronizzazione non diventa attivo finché l'utente non aggiunge un account per il tipo di account dell'adattatore di sincronizzazione e attiva la sincronizzazione per il fornitore di contenuti con cui si sincronizza l'adattatore. A quel punto, il sistema inizia a gestire l'adattatore, chiamandolo come necessario per eseguire la sincronizzazione tra il fornitore di contenuti e il server.

Nota:l'utilizzo di un tipo di account come parte dell'identificazione dell'adattatore di sincronizzazione consente al sistema per rilevare e raggruppare gli adattatori di sincronizzazione che accedono a servizi diversi nella stessa organizzazione. Ad esempio, gli adattatori di sincronizzazione per i servizi online di Google hanno tutti lo stesso type di account com.google. Quando gli utenti aggiungono un Account Google ai propri dispositivi, degli adattatori di sincronizzazione installati per i servizi Google sono elencati insieme; ogni adattatore di sincronizzazione elencato esegue la sincronizzazione con un altro fornitore di contenuti sul dispositivo.

Perché la maggior parte dei servizi richiede agli utenti di verificare la propria identità prima di accedere dati, il sistema Android offre un framework di autenticazione simile, e spesso utilizzata insieme al framework dell'adattatore di sincronizzazione. Il framework di autenticazione utilizza di autenticazione plug-in, sottoclassi del AbstractAccountAuthenticator. Un authenticator verifica l'identità dell'utente nei seguenti passaggi:

  1. Raccoglie il nome, la password o informazioni simili dell'utente (il nome credenziali).
  2. Invia le credenziali al servizio
  3. Esamina la risposta del servizio.

Se il servizio accetta le credenziali, l'autenticatore può e archiviare le credenziali per utilizzarle in un secondo momento. Grazie al framework di autenticazione dei plug-in, AccountManager può fornire l'accesso a qualsiasi token di autenticazione supporta e sceglie di esporre, ad esempio i token di autenticazione OAuth2.

Sebbene l'autenticazione non sia richiesta, la maggior parte dei servizi di contatti la utilizza. Tuttavia, non è necessario utilizzare il framework di autenticazione Android per eseguire l'autenticazione.

Implementazione dell'adattatore di sincronizzazione

Per implementare un adattatore di sincronizzazione per il provider di contatti, occorre innanzitutto creare un Applicazione per Android contenente quanto segue:

Un componente Service che risponde alle richieste del sistema per eseguire il binding all'adattatore di sincronizzazione.
Quando il sistema desidera eseguire una sincronizzazione, chiama metodo onBind() per ottenere IBinder per l'adattatore di sincronizzazione. Ciò consente al sistema di cross-process ai metodi dell'adattatore.
L'effettivo adattatore di sincronizzazione, implementato come una sottoclasse concreta di AbstractThreadedSyncAdapter.
Questa classe si occupa di scaricare i dati dal server, caricare i dati dispositivo e risoluzione dei conflitti. Il lavoro principale dell'adattatore viene eseguito nel metodo onPerformSync(). Questa classe deve essere creata come un'istanza singleton.
Una sottoclasse di Application.
Questa classe funge da factory per l'oggetto singleton dell'adattatore di sincronizzazione. Utilizza la onCreate() per creare un'istanza dell'adattatore di sincronizzazione e un getter statico per restituire il singleton al Metodo onBind() dell'adattatore di sincronizzazione completamente gestito di Google Cloud.
Facoltativo: un componente Service che risponde a richieste di autenticazione utente da parte del sistema.
AccountManager avvia questo servizio per iniziare la procedura di autenticazione. Il metodo onCreate() del servizio esegue l'inizializzazione di un oggetto authenticator. Quando il sistema vuole autenticare un account utente per l'adattatore di sincronizzazione dell'applicazione, chiama il metodo onBind() del servizio per ottenere un IBinder per l'autenticatore. Ciò consente al sistema di chiamate cross-process ai metodi dell'autenticatore.
Facoltativo: una sottoclasse concreta di AbstractAccountAuthenticator che gestisce le richieste di autenticazione.
Questa classe fornisce metodi che AccountManager richiama per autenticare le credenziali dell'utente con il server. I dettagli della procedura di autenticazione variano notevolmente in base alla tecnologia del server in uso. Dovresti per saperne di più sull'autenticazione, consulta la documentazione del software server.
File XML che definiscono l'adattatore di sincronizzazione e l'autenticatore per il sistema.
I componenti del servizio di autenticazione e dell'adattatore di sincronizzazione descritti in precedenza sono definiti in <service> nel file manifest dell'applicazione. Questi elementi contengono <meta-data> elementi secondari che forniscono dati specifici al sistema:
  • La <meta-data> per il servizio dell'adattatore di sincronizzazione punta alla File XML res/xml/syncadapter.xml. A sua volta, questo file specifica un URI per il servizio web che verrà sincronizzato con il fornitore di contatti e un tipo di account per il servizio web.
  • Facoltativo:il campo <meta-data> per l'autenticatore rimanda al file XML res/xml/authenticator.xml. A sua volta, questo file specifica tipo di account supportato da questo autenticatore, nonché le risorse UI durante il processo di autenticazione. Il tipo di account specificato in deve corrispondere al tipo di account specificato per la sincronizzazione dell'adattatore.

Dati degli stream social

Le tabelle android.provider.ContactsContract.StreamItems e android.provider.ContactsContract.StreamItemPhotos gestiscono i dati in entrata dai social network. Puoi scrivere un'apposita app di aggiornamento che aggiunge i dati dello stream dalla tua rete a queste tabelle oppure puoi leggere i dati dello stream da queste tabelle e visualizzarli nella tua applicazione oppure entrambe le cose. Con queste funzionalità, i tuoi servizi e le tue applicazioni di social networking possono essere integrati nell'esperienza di social networking di Android.

Testo social stream

Gli elementi dello stream sono sempre associati a un contatto non elaborato. La android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID rimanda alla Valore _ID per il contatto non elaborato. Il tipo e il nome dell'account non elaborato contatti sono archiviati anche nella riga dell'elemento dello stream.

Memorizza i dati dello stream nelle seguenti colonne:

android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
Obbligatorio. Il tipo di account dell'utente per il contatto non elaborato associato a questo elemento dello stream. Ricorda di impostare questo valore quando inserisci un elemento stream.
android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
Obbligatorio. Il nome dell'account utente per il contatto non elaborato associato a questo dell'elemento in streaming. Ricordati di impostare questo valore quando inserisci un elemento dello stream.
Colonne identificatore
Obbligatorio. Devi inserire le seguenti colonne di identificatori quando inserisci un elemento dello stream:
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: il valore android.provider.BaseColumns#_ID del contatto a cui è associato questo elemento lo stream.
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: il parametro Il valore android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY del contatto a cui è associato questo elemento dello stream.
  • android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: il valore Il valore android.provider.BaseColumn#_ID del contatto non elaborato che lo stream a cui è associato l'elemento.
android.provider.ContattiContract.StreamItemsColonne#COMMENTI
(Facoltativo) Archivia informazioni di riepilogo che puoi visualizzare all'inizio di un elemento dello stream.
android.provider.ContactsContract.StreamItemsColumns#TEXT
Il testo dell'elemento dello stream, ovvero i contenuti pubblicati dalla fonte dell'elemento o una descrizione di un'azione che ha generato l'elemento dello stream. Questa colonna può contenere la formattazione e le immagini di risorse incorporate che possono essere visualizzate fromHtml(). Il fornitore potrebbe troncare o eliminare contenuti lunghi, ma cercherà di evitare di interrompere i tag.
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
Una stringa di testo contenente il momento in cui l'elemento stream è stato inserito o aggiornato, sotto forma di millisecondi dall'epoca. Le applicazioni che inseriscono o aggiornano elementi di flusso responsabile della manutenzione di questa colonna; non viene gestito automaticamente Provider di contatti.

Per visualizzare le informazioni di identificazione per gli elementi dello stream, utilizza android.provider.ContactsContract.StreamItemsColumns#RES_ICON, android.provider.ContactsContract.StreamItemsColumns#RES_LABEL e android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE per collegarti alle risorse nella tua applicazione.

Anche la tabella android.provider.ContactsContract.StreamItems contiene le colonne Da android.provider.ContactsContract.StreamItemsColumns#SYNC1 a android.provider.ContactsContract.StreamItemsColumns#SYNC4 per l'utilizzo esclusivo di adattatori di sincronizzazione.

Foto nei social stream

La tabella android.provider.ContactsContract.StreamItemPhotos memorizza le foto associate a un elemento dello stream. La colonna android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID della tabella rimanda ai valori della colonna _ID della tabella android.provider.ContactsContract.StreamItems. I riferimenti alle foto vengono archiviati nella tabella in queste colonne:

android.provider.ContactsContract.StreamItemPhotos#PHOTO (un BLOB).
Una rappresentazione binaria della foto, ridimensionata dal fornitore per l'archiviazione e la visualizzazione. Questa colonna è disponibile per la compatibilità con le versioni precedenti di Contatti Il fornitore che lo ha utilizzato per archiviare le foto. Tuttavia, nella versione corrente non devi utilizzare questa colonna per archiviare le foto. Utilizza invece android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID oppure android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI (entrambi descritti nei punti seguenti) per archiviare le foto in un file. Ora questa colonna contiene una miniatura della foto, che è disponibile per la lettura.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
Un identificatore numerico di una foto per un contatto non elaborato. Aggiungi questo valore alla costante DisplayPhoto.CONTENT_URI per ottenere un URI dei contenuti che rimandi a un singolo file di foto, quindi richiama openAssetFileDescriptor() per ottenere un handle per il file di foto.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
Un URI dei contenuti che rimanda direttamente al file della foto rappresentata da questa riga. Chiama openAssetFileDescriptor() con questo URI per ottenere un handle per il file della foto.

Utilizzare le tabelle dello stream di social

Queste tabelle funzionano come le altre tabelle principali nel provider di contatti, ad eccezione delle tabelle seguenti:

  • Queste tabelle richiedono autorizzazioni di accesso aggiuntive. Per leggerli, la tua applicazione deve disporre dell'autorizzazione android.Manifest.permission#READ_SOCIAL_STREAM. A le modifiche, l'applicazione deve avere l'autorizzazione android.Manifest.permission#WRITE_SOCIAL_STREAM.
  • Per la tabella android.provider.ContactsContract.StreamItems, il numero di righe archiviati per ogni contatto non elaborato è limitato. Una volta raggiunto questo limite, il provider di contatti fa spazio per le nuove righe di elementi di flusso eliminando automaticamente le righe con il meno recente android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP. Per ottenere limite, invia una query all'URI dei contenuti android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI. Puoi abbandonare tutti gli argomenti diversi dall'URI dei contenuti impostato su null. La query restituisce un cursore contenente una singola riga, con la singola colonna android.provider.ContactsContract.StreamItems#MAX_ITEMS.

La classe android.provider.ContactsContract.StreamItems.StreamItemPhotos definisce una tabella secondaria di android.provider.ContactsContract.StreamItemPhotos contenente le righe delle foto per un singolo elemento dello stream.

Interazioni social stream

I dati dello stream di social gestiti dal fornitore di servizi di contatto, in combinazione con l'applicazione Contatti del dispositivo, offrono un modo efficace per collegare il tuo sistema di social networking ai contatti esistenti. Sono disponibili le seguenti funzionalità:

  • Sincronizzando il tuo servizio di social networking con il provider di contatti tramite una sincronizzazione. puoi recuperare l'attività recente dei contatti di un utente e memorizzarla i file android.provider.ContactsContract.StreamItems e Tabelle android.provider.ContactsContract.StreamItemPhotos per l'utilizzo in un secondo momento.
  • Oltre alla normale sincronizzazione, puoi attivare l'adattatore di sincronizzazione per recuperare dati aggiuntivi quando l'utente seleziona un contatto da visualizzare. In questo modo, l'adattatore di sincronizzazione può recuperare le foto ad alta risoluzione e gli elementi dello stream più recenti per il contatto.
  • Registrando una notifica con l'applicazione dei contatti del dispositivo e i Contatti Provider, puoi ricevere un intent quando un contatto viene visualizzato, e a quel punto aggiorna lo stato del contatto dal tuo servizio. Questo approccio potrebbe essere più veloce e utilizzare meno larghezza di banda rispetto a una sincronizzazione completa con un'opzione di sincronizzazione.
  • Gli utenti possono aggiungere un contatto al tuo servizio di social network mentre guardano il contatto nell'applicazione contatti del dispositivo. Attiva questa opzione con il "contatto di invito" funzionalità, che attivi con una combinazione di un'attività che aggiunge un contatto esistente al tuo rete e un file XML che fornisce l'applicazione dei contatti del dispositivo e i Provider di contatti con i dettagli della tua applicazione.

La sincronizzazione regolare degli elementi dello stream con il provider di contatti è la stessa delle altre sincronizzazioni. Per saperne di più sulla sincronizzazione, consulta la sezione Adattatori di sincronizzazione del fornitore di contatti. Registrazione delle notifiche e parleremo delle prossime due sezioni.

Registrazione per gestire le visualizzazioni sui social network

Per registrare l'adattatore di sincronizzazione in modo da ricevere notifiche quando l'utente visualizza un contatto gestite dall'adattatore di sincronizzazione:

  1. Crea un file denominato contacts.xml nel file res/xml/ del tuo progetto . Se hai già questo file, puoi saltare questo passaggio.
  2. In questo file, aggiungi l'elemento <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Se questo elemento esiste già, puoi saltare questo passaggio.
  3. Per registrare un servizio che viene inviato una notifica quando l'utente apre la pagina dei dettagli di un contatto nell'applicazione Contatti del dispositivo, aggiungi l'attributo viewContactNotifyService="serviceclass" all'elemento, dove serviceclass è il nome completo della classe del servizio che deve ricevere l'intent dall'applicazione Contatti del dispositivo. Per il servizio di avviso, utilizza una classe che espanda IntentService per consentire al servizio di ricevere gli intent. I dati nell'intent in arrivo contengono l'URI dei contenuti del contatto grezzo su cui l'utente ha fatto clic. Dal servizio di notifica, puoi eseguire il binding e chiamare l'adattatore di sincronizzazione per aggiornare i dati del contatto non elaborato.

Per registrare un'attività da chiamare quando l'utente fa clic su un elemento dello stream, su una foto o su entrambi:

  1. Crea un file denominato contacts.xml nel file res/xml/ del tuo progetto . Se hai già questo file, puoi saltare questo passaggio.
  2. In questo file, aggiungi l'elemento <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Se questo elemento esiste già, puoi saltare questo passaggio.
  3. Per registrare una delle tue attività per gestire l'utente che fa clic su un elemento dello stream nella applicazione contatti del dispositivo, aggiungi l'attributo viewStreamItemActivity="activityclass" all'elemento, dove activityclass è il nome di classe completo dell'attività che dovrebbe ricevere l'intent dall'applicazione dei contatti del dispositivo.
  4. Per registrare una delle tue attività per gestire il clic dell'utente su una foto dello stream nell'applicazione Contatti del dispositivo, aggiungi l'attributo viewStreamItemPhotoActivity="activityclass" all'elemento, dove activityclass è il nome completo della classe dell'attività che deve ricevere l'intent dall'applicazione Contatti del dispositivo.

L'elemento <ContactsAccountType> viene descritto in modo più dettagliato nella sezione <ContactsAccountType> .

L'intent in entrata contiene l'URI dei contenuti dell'elemento o della foto su cui l'utente ha fatto clic. Per avere attività separate per gli elementi di testo e per le foto, utilizza entrambi gli attributi nello stesso file.

Interazione con il servizio di social networking

Gli utenti non devono uscire dall'applicazione dei contatti del dispositivo per invitare un contatto sui tuoi social di networking. Puoi invece fare in modo che l'app dei contatti del dispositivo invii un intent per invitare a una delle tue attività. Per configurare questa impostazione:

  1. Crea un file denominato contacts.xml nel file res/xml/ del tuo progetto . Se hai già il file, puoi saltare questo passaggio.
  2. In questo file, aggiungi l'elemento <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Se questo elemento esiste già, puoi saltare questo passaggio.
  3. Aggiungi i seguenti attributi:
    • inviteContactActivity="activityclass"
    • inviteContactActionLabel="@string/invite_action_label"
    Il valore activityclass è il nome della classe completo del l'attività che dovrebbe ricevere l'intento. Il valore invite_action_label è una stringa di testo visualizzata nel menu Aggiungi collegamento nell'applicazione Contatti del dispositivo.

Nota: ContactsSource è un nome di tag deprecato per ContactsAccountType.

Riferimento contacts.xml

Il file contacts.xml contiene elementi XML che controllano l'interazione dei tuoi l'adattatore di sincronizzazione e l'applicazione con l'applicazione dei contatti e il provider di contatti. Questi sono descritti nelle sezioni seguenti.

<ContattiAccountType> elemento

L'elemento <ContactsAccountType> controlla l'interazione dei tuoi con l'applicazione dei contatti. La sintassi è la seguente:

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

contenuto in:

res/xml/contacts.xml

può contenere:

<ContactsDataKind>

Descrizione:

Dichiara i componenti Android e le etichette dell'interfaccia utente che consentono agli utenti di invitare uno dei loro contatti su un social network, di inviare notifiche quando uno dei loro stream di social network viene aggiornato e così via.

Tieni presente che il prefisso dell'attributo android: non è necessario per gli attributi di <ContactsAccountType>.

Attributi:

inviteContactActivity
Il nome completo della classe dell'attività della domanda che vuoi si attiva quando l'utente seleziona Aggiungi connessione dal applicazione per i contatti.
inviteContactActionLabel
Una stringa di testo che viene visualizzata per l'attività specificata in inviteContactActivity, nel menu Aggiungi connessione. Ad esempio, puoi utilizzare la stringa "Segui nella mia rete". Puoi utilizzare una risorsa stringa identificatore di questa etichetta.
viewContactNotifyService
Il nome di classe completo di un servizio nella tua applicazione che deve ricevere notifiche quando l'utente visualizza un contatto. Questa notifica viene inviata dall'applicazione Contatti del dispositivo e consente alla tua applicazione di posticipare le operazioni che richiedono un uso intensivo di dati fino a quando non sono necessarie. Ad esempio, la tua applicazione può rispondere a questa notifica leggendo e mostrando la foto ad alta risoluzione del contatto e gli elementi più recenti dello stream di social. Questa funzionalità viene descritta più dettagliatamente nella sezione Interazioni sui social stream.
viewGroupActivity
Il nome completo della classe di un'attività nella tua applicazione che può visualizzare informazioni di gruppo. Quando l'utente fa clic sull'etichetta del gruppo nei contatti del dispositivo. viene visualizzata l'interfaccia utente per questa attività.
viewGroupActionLabel
L'etichetta visualizzata dall'applicazione Contatti per un controllo dell'interfaccia utente che consente all'utente di visualizzare i gruppi nella tua applicazione.

Per questo attributo è consentito un identificatore di risorsa di stringa.

viewStreamItemActivity
Il nome di classe completo di un'attività nella tua applicazione che viene avviata dall'applicazione Contatti del dispositivo quando l'utente fa clic su un elemento dello stream per un contatto non elaborato.
viewStreamItemPhotoActivity
Il nome di classe completo di un'attività nella tua applicazione che viene avviata dall'applicazione Contatti del dispositivo quando l'utente fa clic su una foto nell'elemento dello stream per un contatto non elaborato.

Elemento <ContactsDataKind>

L'elemento <ContactsDataKind> controlla la visualizzazione delle righe di dati personalizzati della tua applicazione nell'interfaccia utente dell'applicazione Contatti. Ha la seguente sintassi:

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

contenuto in:

<ContactsAccountType>

Descrizione:

Utilizza questo elemento per fare in modo che l'applicazione contatti visualizzi il contenuto di una riga di dati personalizzata come parte dei dettagli di un contatto non elaborato. Ogni elemento secondario <ContactsDataKind> di <ContactsAccountType> rappresenta un tipo di riga di dati personalizzati che alla tabella ContactsContract.Data. Aggiungine uno Elemento <ContactsDataKind> per ogni tipo MIME personalizzato utilizzato. Non disponi di per aggiungere l'elemento se hai una riga di dati personalizzata per la quale non vuoi visualizzare dati.

Attributi:

android:mimeType
Il tipo MIME personalizzato che hai definito per uno dei tipi di riga di dati personalizzati nella sezione Tabella ContactsContract.Data. Ad esempio, il valore vnd.android.cursor.item/vnd.example.locationstatus potrebbe essere un tipo MIME personalizzato per una riga di dati che registra l'ultima posizione nota di un contatto.
android:icon
Una risorsa drawable Android visualizzata dall'applicazione Contatti accanto ai tuoi dati. Utilizzalo per indicare che i dati provengono dal tuo servizio.
android:summaryColumn
Il nome della colonna per il primo dei due valori recuperati dalla riga di dati. La viene visualizzato nella prima riga della voce per questa riga di dati. La prima riga è destinada a essere utilizzata come riepilogo dei dati, ma è facoltativa. Vedi anche android:detailColumn.
android:detailColumn
Il nome della colonna per il secondo di due valori recuperati dalla riga di dati. Il valore è visualizzata come seconda riga della voce per questa riga di dati. Vedi anche android:summaryColumn.

Ulteriori funzionalità del provider di contatti

Oltre alle funzionalità principali descritte nelle sezioni precedenti, il provider di contatti offre queste utili funzionalità per lavorare con i dati dei contatti:

  • Gruppi di contatti
  • Funzionalità per le foto

Gruppi di contatti

Il provider di contatti può facoltativamente etichettare raccolte di contatti correlati con raggruppa i dati. Se il server associato a un account utente vuole mantenere i gruppi, deve essere trasferito l'adattatore di sincronizzazione per il tipo di account dell'account Raggruppa i dati tra il provider di contatti e il server. Quando gli utenti aggiungono un nuovo contatto server e poi inserisci il contatto in un nuovo gruppo, l'adattatore di sincronizzazione deve aggiungere il nuovo gruppo alla tabella ContactsContract.Groups. Il gruppo o raggruppa un elemento non elaborato contatto a cui appartiene sono archiviati nella tabella ContactsContract.Data, utilizzando il tipo MIME ContactsContract.CommonDataKinds.GroupMembership.

Se stai progettando un'opzione di sincronizzazione che aggiungerà i dati non elaborati dei contatti dal server al fornitore di servizi di contatto e non utilizzi i gruppi, devi indicare al fornitore di servizi di rendere visibili i tuoi dati. Nel codice eseguito quando un utente aggiunge un account. al dispositivo, aggiorna ContactsContract.Settings riga aggiunta dal provider di contatti per l'account. In questa riga, imposta il valore Settings.UNGROUPED_VISIBLE su 1. In questo modo, il fornitore di servizi di contatto renderà sempre visibili i dati dei tuoi contatti, anche se non utilizzi i gruppi.

Foto dei contatti

La tabella ContactsContract.Data memorizza le foto come righe con tipo MIME Photo.CONTENT_ITEM_TYPE. La colonna CONTACT_ID della riga è collegata alla colonna _ID del contatto non elaborato a cui appartiene. La classe ContactsContract.Contacts.Photo definisce una sottotabella ContactsContract.Contacts contenente informazioni sulla foto per il contatto foto principale: la foto principale del contatto principale non elaborato del contatto. Analogamente, la classe ContactsContract.RawContacts.DisplayPhoto definisce una sottotabella di ContactsContract.RawContacts contenente le informazioni sulla foto per la foto principale di un contatto non elaborato.

La documentazione di riferimento per ContactsContract.Contacts.Photo e ContactsContract.RawContacts.DisplayPhoto contengono esempi di recupero delle informazioni sulle foto. Non esiste una classe di convenienza per il recupero dell'istanza principale per un contatto non elaborato, ma puoi inviare una query Tabella ContactsContract.Data, selezione del contatto non elaborato _ID, Photo.CONTENT_ITEM_TYPE e IS_PRIMARY per trovare la riga della foto principale del contatto non elaborato.

I dati dello stream di social di una persona possono includere anche foto. Questi dati vengono archiviati nella tabella android.provider.ContactsContract.StreamItemPhotos, descritta in maggiore dettaglio nella sezione Foto dello stream di social.