Contacts Provider

Der Contacts Provider ist eine leistungsstarke und flexible Android-Komponente, die das zentrale Repository des Geräts für Daten zu Personen verwaltet. Der Contacts Provider ist die Datenquelle, die du in der Kontakte-App des Geräts siehst. Du kannst auch in deiner eigenen App auf die Daten zugreifen und Daten zwischen dem Gerät und Onlinediensten übertragen. Der Anbieter unterstützt eine Vielzahl von Datenquellen und versucht, so viele Daten wie möglich für jede Person zu verwalten. Das führt dazu, dass die Organisation komplex ist. Aus diesem Grund enthält die API des Anbieters eine umfangreiche Reihe von Vertragsklassen und Schnittstellen, die sowohl den Datenabruf als auch die Datenänderung erleichtern.

In diesem Leitfaden wird Folgendes beschrieben:

  • Die grundlegende Anbieterstruktur.
  • So rufen Sie Daten vom Anbieter ab.
  • Daten im Anbieter ändern
  • Anleitung zum Schreiben eines Sync Adapters, um Daten von Ihrem Server mit dem Contacts Provider zu synchronisieren.

In diesem Leitfaden wird davon ausgegangen, dass Sie die Grundlagen von Android-Contentanbietern kennen. Weitere Informationen zu Android-Contentanbietern finden Sie im Leitfaden Grundlagen zu Contentanbietern.

Contacts Provider-Organisation

Der Contacts Provider ist eine Android-Contentanbieter-Komponente. Es werden drei Arten von Daten zu einer Person verwaltet, die jeweils einer vom Anbieter angebotenen Tabelle entsprechen, wie in Abbildung 1 dargestellt:

Abbildung 1. Tabellenstruktur des Contacts Provider.

Die drei Tabellen werden üblicherweise mit den Namen ihrer Contract-Klassen bezeichnet. Die Klassen definieren Konstanten für Inhalts-URIs, Spaltennamen und Spaltenwerte, die von den Tabellen verwendet werden:

ContactsContract.Contacts Tabelle
Zeilen, die verschiedene Personen darstellen, basierend auf Aggregationen von Rohkontaktzeilen.
ContactsContract.RawContacts Tabelle
Zeilen mit einer Zusammenfassung der Daten einer Person, die für ein Nutzerkonto und einen Nutzertyp spezifisch sind.
ContactsContract.Data Tabelle
Zeilen mit den Details für den Rohkontakt, z. B. E‑Mail-Adressen oder Telefonnummern.

Die anderen Tabellen, die durch Vertragsklassen in ContactsContract dargestellt werden, sind Hilfstabellen, die der Contacts Provider verwendet, um seine Vorgänge zu verwalten oder bestimmte Funktionen in den Kontakt- oder Telefonie-Apps des Geräts zu unterstützen.

Rohkontakte

Ein Rohkontakt stellt die Daten einer Person dar, die von einem einzelnen Kontotyp und Kontonamen stammen. Da der Contacts Provider mehr als einen Onlinedienst als Datenquelle für eine Person zulässt, sind mehrere Rohkontakte für dieselbe Person möglich. Mehrere Rohkontakte ermöglichen es einem Nutzer auch, die Daten einer Person aus mehreren Konten desselben Kontotyps zu kombinieren.

Die meisten Daten für einen Rohkontakt werden nicht in der Tabelle ContactsContract.RawContacts gespeichert. Stattdessen werden sie in einer oder mehreren Zeilen in der Tabelle ContactsContract.Data gespeichert. Jede Datenzeile hat eine Spalte Data.RAW_CONTACT_ID, die den RawContacts._ID-Wert der übergeordneten ContactsContract.RawContacts-Zeile enthält.

Wichtige Spalten für Rohkontakte

Die wichtigen Spalten in der Tabelle ContactsContract.RawContacts sind in Tabelle 1 aufgeführt. Bitte lesen Sie die Hinweise nach der Tabelle:

Tabelle 1. Wichtige Spalten für Rohkontakte.

Spaltenname Verwenden Hinweise
ACCOUNT_NAME Der Kontoname für den Kontotyp, der die Quelle dieses Rohkontakts ist. Der Kontoname eines Google-Kontos ist beispielsweise eine der Gmail-Adressen des Geräteeigentümers. Weitere Informationen finden Sie im nächsten Eintrag für ACCOUNT_TYPE. Das Format dieses Namens ist spezifisch für den Kontotyp. Es ist nicht unbedingt eine E-Mail-Adresse.
ACCOUNT_TYPE Der Kontotyp, der die Quelle dieses Rohkontakts ist. Der Kontotyp eines Google-Kontos ist beispielsweise com.google. Geben Sie für Ihren Kontotyp immer eine Domain-ID für eine Domain an, die Sie besitzen oder kontrollieren. Dadurch wird sichergestellt, dass Ihr Kontotyp eindeutig ist. Ein Kontotyp, der Kontaktdaten bietet, hat normalerweise einen zugehörigen Sync Adapter, der mit dem Contacts Provider synchronisiert wird.
DELETED Das Flag „deleted“ für einen Rohkontakt. Mit diesem Flag kann der Contacts Provider die Zeile intern beibehalten, bis Sync Adapter die Zeile von ihren Servern löschen und sie dann endgültig aus dem Repository entfernen können.

Hinweise

Im Folgenden finden Sie wichtige Hinweise zur Tabelle ContactsContract.RawContacts:

  • Der Name eines Rohkontakts wird nicht in der entsprechenden Zeile in ContactsContract.RawContacts gespeichert. Stattdessen wird sie in der Tabelle ContactsContract.Data in einer ContactsContract.CommonDataKinds.StructuredName-Zeile gespeichert. Ein Rohkontakt hat nur eine Zeile dieses Typs in der Tabelle ContactsContract.Data.
  • Achtung:Wenn du deine eigenen Kontodaten in einer Rohkontaktzeile verwenden möchtest, müssen sie zuerst beim AccountManager registriert werden. Fordern Sie Nutzer dazu auf, den Kontotyp und den Kontonamen der Liste der Konten hinzuzufügen. Andernfalls löscht der Contacts Provider die Rohkontaktzeile automatisch.

    Wenn deine App beispielsweise Kontaktdaten für deinen webbasierten Dienst mit der Domain com.example.dataservice verwalten soll und das Konto des Nutzers für deinen Dienst becky.sharp@dataservice.example.com ist, muss der Nutzer zuerst den Kontotyp (com.example.dataservice) und den Kontonamen (becky.smart@dataservice.example.com) hinzufügen, bevor deine App Zeilen für Rohkontakte hinzufügen kann. Sie können diese Anforderung in der Dokumentation erläutern oder den Nutzer auffordern, den Typ und den Namen oder beides hinzuzufügen. Kontotypen und Kontonamen werden im nächsten Abschnitt ausführlicher beschrieben.

Quellen für Rohkontaktdaten

Um zu verstehen, wie Rohkontakte funktionieren, betrachten wir die Nutzerin „Emily Dickinson“, die die folgenden drei Nutzerkonten auf ihrem Gerät definiert hat:

  • emily.dickinson@gmail.com
  • emilyd@gmail.com
  • Twitter-Konto „belle_of_amherst“

Dieser Nutzer hat in den Kontoeinstellungen für alle drei Konten die Option Kontakte synchronisieren aktiviert.

Angenommen, Emily Dickinson öffnet ein Browserfenster, meldet sich als emily.dickinson@gmail.com in Gmail an, öffnet „Kontakte“ und fügt „Thomas Higginson“ hinzu. Später meldet sie sich als emilyd@gmail.com in Gmail an und sendet eine E-Mail an „Thomas Higginson“, der dadurch automatisch als Kontakt hinzugefügt wird. Sie folgt auch „colonel_tom“ (Thomas Higginsons Twitter-ID) auf Twitter.

Der Contacts Provider erstellt daraufhin drei Rohkontakte:

  1. Ein Rohkontakt für „Thomas Higginson“, der mit emily.dickinson@gmail.com verknüpft ist. Der Nutzerkontotyp ist Google.
  2. Ein zweiter Rohkontakt für „Thomas Higginson“, der mit emilyd@gmail.com verknüpft ist. Der Nutzerkontotyp ist ebenfalls Google. Es gibt einen zweiten Rohkontakt, obwohl der Name mit einem vorherigen Namen identisch ist, da die Person für ein anderes Nutzerkonto hinzugefügt wurde.
  3. Ein dritter Rohkontakt für „Thomas Higginson“, der mit „belle_of_amherst“ verknüpft ist. Der Nutzerkontotyp ist Twitter.

Daten

Wie bereits erwähnt, werden die Daten für einen Rohkontakt in einer ContactsContract.Data-Zeile gespeichert, die mit dem _ID-Wert des Rohkontakts verknüpft ist. So kann ein einzelner Rohkontakt mehrere Instanzen desselben Datentyps haben, z. B. E-Mail-Adressen oder Telefonnummern. Wenn beispielsweise „Thomas Higginson“ für emilyd@gmail.com (die Rohkontaktzeile für Thomas Higginson, die mit dem Google-Konto emilyd@gmail.com verknüpft ist) die private E‑Mail-Adresse thigg@gmail.com und die geschäftliche E‑Mail-Adresse thomas.higginson@gmail.com hat, speichert der Contacts Provider die beiden E‑Mail-Adresszeilen und verknüpft sie beide mit dem Rohkontakt.

Beachten Sie, dass in dieser Tabelle verschiedene Datentypen gespeichert sind. Die Zeilen für Anzeigename, Telefonnummer, E‑Mail-Adresse, Postanschrift, Foto und Website-Details befinden sich alle in der Tabelle ContactsContract.Data. Zur besseren Verwaltung enthält die Tabelle ContactsContract.Data einige Spalten mit beschreibenden Namen und andere mit generischen Namen. Der Inhalt einer Spalte mit einem beschreibenden Namen hat unabhängig vom Datentyp in der Zeile dieselbe Bedeutung, während der Inhalt einer Spalte mit einem generischen Namen je nach Datentyp unterschiedliche Bedeutungen hat.

Beschreibende Spaltennamen

Hier einige Beispiele für beschreibende Spaltennamen:

RAW_CONTACT_ID
Der Wert der Spalte _ID des Rohkontakts für diese Daten.
MIMETYPE
Der Typ der in dieser Zeile gespeicherten Daten, ausgedrückt als benutzerdefinierter MIME-Typ. Der Contacts Provider verwendet die MIME-Typen, die in den Unterklassen von ContactsContract.CommonDataKinds definiert sind. Diese MIME-Typen sind Open Source und können von jeder Anwendung oder jedem Sync Adapter verwendet werden, die bzw. der mit dem Contacts Provider funktioniert.
IS_PRIMARY
Wenn dieser Typ von Datenzeile für einen Rohkontakt mehr als einmal vorkommen kann, wird in der Spalte IS_PRIMARY die Datenzeile gekennzeichnet, die die primären Daten für den Typ enthält. Wenn der Nutzer beispielsweise lange auf die Telefonnummer eines Kontakts drückt und Als Standard festlegen auswählt, wird für die Spalte IS_PRIMARY der Zeile ContactsContract.Data, die diese Nummer enthält, ein Wert ungleich null festgelegt.

Generische Spaltennamen

Es gibt 15 allgemeine Spalten mit den Namen DATA1 bis DATA15, die allgemein verfügbar sind, und vier zusätzliche allgemeine Spalten mit den Namen SYNC1 bis SYNC4, die nur von Sync Adaptern verwendet werden sollten. Die generischen Spaltennamenkonstanten funktionieren immer, unabhängig vom Datentyp, den die Zeile enthält.

Die Spalte DATA1 ist indexiert. Der Contacts Provider verwendet diese Spalte immer für die Daten, die voraussichtlich am häufigsten abgefragt werden. In einer E‑Mail-Zeile enthält diese Spalte beispielsweise die tatsächliche E‑Mail-Adresse.

Die Spalte DATA15 ist standardmäßig für die Speicherung von BLOB-Daten (großes binäres Datenobjekt) wie Miniaturansichten von Fotos reserviert.

Typspezifische Spaltennamen

Um die Arbeit mit den Spalten für einen bestimmten Zeilentyp zu erleichtern, stellt der Contacts Provider auch typspezifische Spaltennamenkonstanten bereit, die in Unterklassen von ContactsContract.CommonDataKinds definiert sind. Die Konstanten geben demselben Spaltennamen einfach einen anderen Konstantennamen, was Ihnen den Zugriff auf Daten in einer Zeile eines bestimmten Typs erleichtert.

Beispielsweise definiert die Klasse ContactsContract.CommonDataKinds.Email typspezifische Spaltennamenkonstanten für eine ContactsContract.Data-Zeile mit dem MIME-Typ Email.CONTENT_ITEM_TYPE. Die Klasse enthält die Konstante ADDRESS für die Spalte mit der E-Mail-Adresse. Der tatsächliche Wert von ADDRESS ist „data1“, was dem generischen Namen der Spalte entspricht.

Achtung: Fügen Sie der Tabelle ContactsContract.Data keine eigenen benutzerdefinierten Daten über eine Zeile hinzu, die einen der vordefinierten MIME-Typen des Anbieters hat. Andernfalls können Daten verloren gehen oder es kann zu Fehlfunktionen des Anbieters kommen. Sie sollten beispielsweise keine Zeile mit dem MIME-Typ Email.CONTENT_ITEM_TYPE hinzufügen, die in der Spalte DATA1 einen Nutzernamen anstelle einer E-Mail-Adresse enthält. Wenn du deinen eigenen benutzerdefinierten MIME-Typ für die Zeile verwendest, kannst du deine eigenen typspezifischen Spaltennamen definieren und die Spalten nach Belieben verwenden.

Abbildung 2 zeigt, wie beschreibende Spalten und Datenspalten in einer ContactsContract.Data-Zeile angezeigt werden und wie typspezifische Spaltennamen die generischen Spaltennamen „überlagern“.

Zuordnung typspezifischer Spaltennamen zu generischen Spaltennamen

Abbildung 2: Typspezifische Spaltennamen und generische Spaltennamen.

Typspezifische Spaltennamenklassen

In Tabelle 2 sind die am häufigsten verwendeten typspezifischen Spaltennamenklassen aufgeführt:

Tabelle 2. Typspezifische Spaltennamenklassen

Zuordnungsklasse Datentyp Hinweise
ContactsContract.CommonDataKinds.StructuredName Die Namensdaten für den Rohkontakt, der dieser Datenzeile zugeordnet ist. Ein Rohkontakt hat nur eine dieser Zeilen.
ContactsContract.CommonDataKinds.Photo Das Hauptfoto für den Rohkontakt, der mit dieser Datenzeile verknüpft ist. Ein Rohkontakt hat nur eine dieser Zeilen.
ContactsContract.CommonDataKinds.Email Eine E‑Mail-Adresse für den Rohkontakt, der mit dieser Datenzeile verknüpft ist. Ein Rohkontakt kann mehrere E‑Mail-Adressen haben.
ContactsContract.CommonDataKinds.StructuredPostal Eine Postanschrift für den Rohkontakt, der mit dieser Datenzeile verknüpft ist. Ein Rohkontakt kann mehrere Postadressen haben.
ContactsContract.CommonDataKinds.GroupMembership Eine Kennung, die den Rohkontakt mit einer der Gruppen im Contacts Provider verknüpft. Gruppen sind eine optionale Funktion eines Kontotyps und Kontonamens. Sie werden im Abschnitt Kontaktgruppen ausführlicher beschrieben.

Kontakte

Der Contacts Provider kombiniert die Rohkontaktzeilen aller Kontotypen und Kontonamen zu einem Kontakt. Dies erleichtert das Anzeigen und Ändern aller Daten, die ein Nutzer für eine Person erfasst hat. Der Contacts Provider verwaltet die Erstellung neuer Kontaktzeilen und die Zusammenfassung von Rohkontakten mit einer vorhandenen Kontaktzeile. Weder Anwendungen noch Sync Adapter dürfen Kontakte hinzufügen und einige Spalten in einer Kontaktzeile sind schreibgeschützt.

Hinweis:Wenn Sie versuchen, dem Contacts Provider einen Kontakt mit einer insert() hinzuzufügen, erhalten Sie eine UnsupportedOperationException-Ausnahme. Wenn Sie versuchen, eine Spalte zu aktualisieren, die als „schreibgeschützt“ aufgeführt ist, wird die Aktualisierung ignoriert.

Der Contacts Provider erstellt einen neuen Kontakt, wenn ein neuer Rohkontakt hinzugefügt wird, der mit keinem vorhandenen Kontakt übereinstimmt. Der Anbieter tut dies auch, wenn sich die Daten eines vorhandenen Rohkontakts so ändern, dass sie nicht mehr mit dem Kontakt übereinstimmen, dem sie zuvor zugeordnet waren. Wenn eine Anwendung oder ein Sync Adapter einen neuen Rohkontakt erstellt, der mit einem vorhandenen Kontakt übereinstimmt, wird der neue Rohkontakt dem vorhandenen Kontakt hinzugefügt.

Der Contacts Provider verknüpft eine Kontaktzeile mit den zugehörigen Rohkontaktzeilen über die Spalte _ID der Kontaktzeile in der Tabelle Contacts. Die Spalte CONTACT_ID der Tabelle mit Rohkontakten ContactsContract.RawContacts enthält _ID-Werte für die Kontaktzeile, die mit jeder Rohkontaktzeile verknüpft ist.

Die Tabelle ContactsContract.Contacts enthält auch die Spalte LOOKUP_KEY, die einen „permanenten“ Link zur Kontaktzeile darstellt. Da der Contacts Provider Kontakte automatisch verwaltet, kann sich der _ID-Wert einer Kontaktzeile aufgrund einer Zusammenfassung oder Synchronisierung ändern. Auch wenn dies der Fall ist, verweist der Inhalts-URI CONTENT_LOOKUP_URI in Kombination mit dem LOOKUP_KEY des Kontakts weiterhin auf die Kontaktzeile. Du kannst also LOOKUP_KEY verwenden, um Links zu „Favoriten“ und so weiter beizubehalten. Diese Spalte hat ein eigenes Format, das nicht mit dem Format der Spalte _ID zusammenhängt.

Abbildung 3 zeigt, wie die drei Haupttabellen miteinander in Beziehung stehen.

Haupttabellen des Contacts Providers

Abbildung 3: Beziehungen zwischen den Tabellen „Contacts“, „Raw Contacts“ und „Details“.

Achtung : Wenn du deine App im Google Play Store veröffentlichst oder deine App auf einem Gerät mit Android 10 (API-Level 29) oder höher ausgeführt wird, beachte, dass eine begrenzte Anzahl von Kontaktdatenfeldern und -methoden veraltet ist.

Unter den genannten Bedingungen löscht das System regelmäßig alle Werte, die in diese Datenfelder geschrieben wurden:

Die APIs, die zum Festlegen der oben genannten Datenfelder verwendet werden, sind ebenfalls veraltet:

Außerdem werden in den folgenden Feldern keine häufigen Kontakte mehr zurückgegeben. Beachten Sie, dass einige dieser Felder das Ranking von Kontakten nur dann beeinflussen, wenn die Kontakte zu einer bestimmten Datenart gehören.

Wenn deine Apps auf diese Felder oder APIs zugreifen oder sie aktualisieren, verwende alternative Methoden. Beispielsweise können Sie bestimmte Anwendungsfälle mit privaten Contentanbietern oder anderen Daten erfüllen, die in Ihrer App oder Ihren Backend-Systemen gespeichert sind.

Wenn Sie prüfen möchten, ob die Funktionalität Ihrer App von dieser Änderung betroffen ist, können Sie diese Datenfelder manuell löschen. Führe dazu den folgenden ADB-Befehl auf einem Gerät mit Android 4.1 (API-Level 16) oder höher aus:

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

Daten von Sync Adaptern

Nutzer geben Kontaktdaten direkt auf dem Gerät ein. Daten fließen aber auch über Sync Adapter von Webdiensten in den Contacts Provider, die die Übertragung von Daten zwischen dem Gerät und den Diensten automatisieren. Sync Adapter werden im Hintergrund unter der Kontrolle des Systems ausgeführt und rufen ContentResolver-Methoden auf, um Daten zu verwalten.

In Android wird der Webdienst, mit dem ein Sync Adapter arbeitet, durch einen Kontotyp identifiziert. Jeder Sync Adapter funktioniert mit einem Kontotyp, kann aber mehrere Kontonamen für diesen Typ unterstützen. Kontotypen und Kontonamen werden im Abschnitt Quellen für Rohkontaktdaten kurz beschrieben. Die folgenden Definitionen enthalten weitere Details und beschreiben, wie Kontotyp und -name mit Sync Adaptern und Diensten zusammenhängen.

Kontotyp
Identifiziert einen Dienst, in dem der Nutzer Daten gespeichert hat. In den meisten Fällen muss sich der Nutzer beim Dienst authentifizieren. Google Kontakte ist beispielsweise ein Kontotyp, der durch den Code google.com identifiziert wird. Dieser Wert entspricht dem von AccountManager verwendeten Kontotyp.
Kontoname
Identifiziert ein bestimmtes Konto oder eine bestimmte Anmeldung für einen Kontotyp. Google Kontakte-Konten sind mit Google-Konten identisch, die eine E‑Mail-Adresse als Kontonamen haben. Andere Dienste verwenden möglicherweise einen Nutzernamen, der aus einem einzelnen Wort besteht, oder eine numerische ID.

Kontotypen müssen nicht eindeutig sein. Nutzer können mehrere Google Kontakte-Konten konfigurieren und ihre Daten in den Contacts Provider herunterladen. Das kann der Fall sein, wenn Nutzer eine Reihe persönlicher Kontakte für ein privates Konto und eine andere Reihe für die Arbeit haben. Kontonamen sind in der Regel eindeutig. Zusammen identifizieren sie einen bestimmten Datenfluss zwischen dem Contacts Provider und einem externen Dienst.

Wenn du die Daten deines Dienstes an den Contacts Provider übertragen möchtest, musst du einen eigenen Sync Adapter schreiben. Dies wird im Abschnitt Sync-Adapter für Contacts Provider ausführlicher beschrieben.

Abbildung 4 zeigt, wie der Contacts Provider in den Datenfluss personenbezogener Daten passt. Im Feld „Sync Adapters“ ist jeder Adapter mit seinem Kontotyp gekennzeichnet.

Fluss von Daten über Personen

Abbildung 4: Der Datenfluss des Contacts Providers.

Erforderliche Berechtigungen

Anwendungen, die auf den Contacts Provider zugreifen möchten, müssen die folgenden Berechtigungen anfordern:

Lesezugriff auf eine oder mehrere Tabellen
READ_CONTACTS, angegeben in AndroidManifest.xml mit dem <uses-permission>-Element als <uses-permission android:name="android.permission.READ_CONTACTS">.
Schreibzugriff auf eine oder mehrere Tabellen
WRITE_CONTACTS, angegeben in AndroidManifest.xml mit dem <uses-permission>-Element als <uses-permission android:name="android.permission.WRITE_CONTACTS">.

Diese Berechtigungen gelten nicht für die Nutzerprofildaten. Das Nutzerprofil und die erforderlichen Berechtigungen werden im folgenden Abschnitt Das Nutzerprofil erläutert.

Denken Sie daran, dass die Kontaktdaten des Nutzers personenbezogene und vertrauliche Daten sind. Nutzer sind um ihre Privatsphäre besorgt und möchten nicht, dass Anwendungen Daten über sie oder ihre Kontakte erheben. Wenn nicht offensichtlich ist, warum Sie die Berechtigung für den Zugriff auf die Kontaktdaten benötigen, geben Nutzer Ihrer Anwendung möglicherweise schlechte Bewertungen oder installieren sie einfach nicht.

Das Nutzerprofil

Die Tabelle ContactsContract.Contacts enthält eine einzelne Zeile mit Profildaten für den Nutzer des Geräts. Diese Daten beschreiben die user des Geräts und nicht einen der Kontakte des Nutzers. Die Zeile mit den Profilkontakten ist für jedes System, das ein Profil verwendet, mit einer Zeile mit Rohkontakten verknüpft. Jede Zeile für einen Rohkontakt im Profil kann mehrere Datenzeilen enthalten. Konstanten für den Zugriff auf das Nutzerprofil sind in der Klasse ContactsContract.Profile verfügbar.

Für den Zugriff auf das Nutzerprofil sind besondere Berechtigungen erforderlich. Zusätzlich zu den Berechtigungen READ_CONTACTS und WRITE_CONTACTS, die zum Lesen und Schreiben erforderlich sind, sind für den Zugriff auf das Nutzerprofil die Berechtigungen android.Manifest.permission#READ_PROFILE und android.Manifest.permission#WRITE_PROFILE für den Lese- bzw. Schreibzugriff erforderlich.

Denken Sie daran, dass das Profil eines Nutzers als vertraulich zu behandeln ist. Mit der Berechtigung „android.Manifest.permission#READ_PROFILE“ kannst du auf die personenbezogenen Daten des Gerätenutzers zugreifen. Erläutern Sie dem Nutzer in der Beschreibung Ihrer Anwendung, warum Sie Zugriffsberechtigungen für das Nutzerprofil benötigen.

Rufen Sie ContentResolver.query() auf, um die Kontaktzeile mit dem Profil des Nutzers abzurufen. Legen Sie den Inhalts-URI auf CONTENT_URI fest und geben Sie keine Auswahlkriterien an. Sie können diesen Inhalts-URI auch als Basis-URI zum Abrufen von Rohkontakten oder Daten für das Profil verwenden. Mit diesem Snippet werden beispielsweise Daten für das Profil abgerufen:

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

Hinweis:Wenn du mehrere Kontaktdatensätze abrufst und feststellen möchtest, ob einer davon das Nutzerprofil ist, teste die Spalte IS_USER_PROFILE des Datensatzes. Diese Spalte ist auf „1“ gesetzt, wenn der Kontakt das Nutzerprofil ist.

Contacts Provider-Metadaten

Der Contacts Provider verwaltet Daten, die den Status der Kontaktdaten im Repository verfolgen. Diese Metadaten zum Repository werden an verschiedenen Stellen gespeichert, darunter in den Tabellenzeilen „Rohkontakte“, „Daten“ und „Kontakte“, in der Tabelle ContactsContract.Settings und in der Tabelle ContactsContract.SyncState. In der folgenden Tabelle wird die Auswirkung der einzelnen Metadaten beschrieben:

Tabelle 3 Metadaten im Contacts Provider

Tabelle Spalte Werte Bedeutung
ContactsContract.RawContacts DIRTY „0“: Seit der letzten Synchronisierung nicht geändert. Kennzeichnet Rohkontakte, die auf dem Gerät geändert wurden und mit dem Server synchronisiert werden müssen. Der Wert wird automatisch vom Contacts Provider festgelegt, wenn Android-Apps eine Zeile aktualisieren.

Sync Adapter, die die Rohkontakt- oder Datentabellen ändern, sollten immer den String CALLER_IS_SYNCADAPTER an den von ihnen verwendeten Inhalts-URI anhängen. Dadurch wird verhindert, dass der Anbieter Zeilen als geändert markiert. Andernfalls werden Änderungen am Sync Adapter als lokale Änderungen angezeigt und an den Server gesendet, obwohl der Server die Quelle der Änderung war.

„1“: Seit der letzten Synchronisierung geändert, muss wieder mit dem Server synchronisiert werden.
ContactsContract.RawContacts VERSION Die Versionsnummer dieser Zeile. Der Contacts Provider erhöht diesen Wert automatisch, wenn sich die Zeile oder die zugehörigen Daten ändern.
ContactsContract.Data DATA_VERSION Die Versionsnummer dieser Zeile. Der Contacts Provider erhöht diesen Wert automatisch, wenn die Datenzeile geändert wird.
ContactsContract.RawContacts SOURCE_ID Ein Stringwert, der diesen Rohkontakt eindeutig für das Konto identifiziert, in dem er erstellt wurde. Wenn ein Sync Adapter einen neuen Rohkontakt erstellt, sollte diese Spalte auf die eindeutige ID des Servers für den Rohkontakt festgelegt werden. Wenn eine Android-App einen neuen Rohkontakt erstellt, sollte sie diese Spalte leer lassen. Dadurch wird dem Sync Adapter signalisiert, dass er einen neuen Rohkontakt auf dem Server erstellen und einen Wert für SOURCE_ID abrufen soll.

Insbesondere muss die Quell-ID für jeden Kontotyp eindeutig sein und bei allen Synchronisierungen gleich bleiben:

  • Eindeutig: Jeder Rohkontakt für ein Konto muss eine eigene Quell-ID haben. Wenn Sie dies nicht erzwingen, kommt es zu Problemen in der Kontakte-App. Beachte, dass zwei Rohkontakte für denselben Kontotyp dieselbe Quell-ID haben können. Beispielsweise darf der Rohkontakt „Thomas Higginson“ für das Konto emily.dickinson@gmail.com dieselbe Quell-ID haben wie der Rohkontakt „Thomas Higginson“ für das Konto emilyd@gmail.com.
  • Stabil: Quell-IDs sind ein dauerhafter Bestandteil der Daten des Onlinedienstes für den Rohkontakt. Wenn der Nutzer beispielsweise den Kontaktspeicher in den App-Einstellungen löscht und die Kontakte neu synchronisiert, sollten die wiederhergestellten Rohkontakte dieselben Quell-IDs wie zuvor haben. Wenn Sie dies nicht erzwingen, funktionieren Verknüpfungen nicht mehr.
ContactsContract.Groups GROUP_VISIBLE „0“ – Kontakte in dieser Gruppe sollten in den Benutzeroberflächen von Android-Apps nicht sichtbar sein. Diese Spalte dient der Kompatibilität mit Servern, die es Nutzern ermöglichen, Kontakte in bestimmten Gruppen auszublenden.
„1“: Kontakte in dieser Gruppe dürfen in den Benutzeroberflächen von Anwendungen sichtbar sein.
ContactsContract.Settings UNGROUPED_VISIBLE „0“: Für dieses Konto und diesen Kontotyp sind Kontakte, die keiner Gruppe angehören, für Android-App-UIs unsichtbar. Standardmäßig sind Kontakte unsichtbar, wenn keiner ihrer Rohkontakte zu einer Gruppe gehört. Die Gruppenmitgliedschaft für einen Rohkontakt wird durch eine oder mehrere ContactsContract.CommonDataKinds.GroupMembership-Zeilen in der Tabelle ContactsContract.Data angegeben. Wenn Sie dieses Flag in der Tabellenzeile ContactsContract.Settings für einen Kontotyp und ein Konto festlegen, können Sie erzwingen, dass Kontakte ohne Gruppen sichtbar sind. Dieses Flag kann beispielsweise verwendet werden, um Kontakte von Servern anzuzeigen, die keine Gruppen verwenden.
„1“: Für dieses Konto und diesen Kontotyp sind Kontakte, die keiner Gruppe angehören, in den Benutzeroberflächen der Anwendung sichtbar.
ContactsContract.SyncState (alle) In dieser Tabelle werden Metadaten für den Sync Adapter gespeichert. In dieser Tabelle können Sie den Synchronisierungsstatus und andere synchronisierungsbezogene Daten dauerhaft auf dem Gerät speichern.

Zugriff auf Contacts Provider

In diesem Abschnitt werden Richtlinien für den Zugriff auf Daten vom Contacts Provider beschrieben. Der Schwerpunkt liegt dabei auf Folgendem:

  • Entitätsabfragen.
  • Batchänderung.
  • Abruf und Änderung mit Intents.
  • Datenintegrität:

Änderungen über einen Sync Adapter werden im Abschnitt Sync Adapter für Contacts Provider ausführlicher beschrieben.

Entitäten abfragen

Da die Tabellen des Contacts Providers hierarchisch organisiert sind, ist es oft nützlich, eine Zeile und alle mit ihr verknüpften untergeordneten Zeilen abzurufen. Wenn Sie beispielsweise alle Informationen zu einer Person anzeigen möchten, können Sie alle ContactsContract.RawContacts-Zeilen für eine einzelne ContactsContract.Contacts-Zeile oder alle ContactsContract.CommonDataKinds.Email-Zeilen für eine einzelne ContactsContract.RawContacts-Zeile abrufen. Um dies zu ermöglichen, bietet der Contacts Provider Entitätskonstrukte, die wie Datenbank-Joins zwischen Tabellen funktionieren.

Eine Entität ist wie eine Tabelle, die aus ausgewählten Spalten einer übergeordneten Tabelle und ihrer untergeordneten Tabelle besteht. Wenn Sie eine Entität abfragen, geben Sie eine Projektion und Suchkriterien basierend auf den für die Entität verfügbaren Spalten an. Das Ergebnis ist ein Cursor, der eine Zeile für jede abgerufene untergeordnete Tabellenzeile enthält. Wenn Sie beispielsweise ContactsContract.Contacts.Entity nach einem Kontaktnamen und alle ContactsContract.CommonDataKinds.Email-Zeilen für alle Rohkontakte für diesen Namen abfragen, erhalten Sie einen Cursor mit einer Zeile für jede ContactsContract.CommonDataKinds.Email-Zeile.

Entitäten vereinfachen Abfragen. Mit einer Entität kannst du alle Kontaktdaten für einen Kontakt oder Rohkontakt auf einmal abrufen, anstatt zuerst die übergeordnete Tabelle abzufragen, um eine ID zu erhalten, und dann die untergeordnete Tabelle mit dieser ID abzufragen. Außerdem verarbeitet der Contacts Provider eine Abfrage für eine Entität in einer einzelnen Transaktion, wodurch sichergestellt wird, dass die abgerufenen Daten intern konsistent sind.

Hinweis: Eine Entität enthält normalerweise nicht alle Spalten der über- und untergeordneten Tabelle. Wenn Sie versuchen, mit einem Spaltennamen zu arbeiten, der nicht in der Liste der Spaltennamenkonstanten für die Entität enthalten ist, erhalten Sie einen Exception.

Das folgende Snippet zeigt, wie Sie alle Rohkontaktzeilen für einen Kontakt abrufen. Das Snippet ist Teil einer größeren Anwendung mit zwei Aktivitäten: „main“ und „detail“. Die Hauptaktivität zeigt eine Liste von Kontaktzeilen an. Wenn der Nutzer eine auswählt, sendet die Aktivität ihre ID an die Detailaktivität. In der Detailaktivität wird der ContactsContract.Contacts.Entity verwendet, um alle Datenzeilen aus allen Rohkontakten anzuzeigen, die mit dem ausgewählten Kontakt verknüpft sind.

Dieses Snippet stammt aus der Aktivität „detail“:

Kotlin

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

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

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

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

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

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

Java

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

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

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

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

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

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

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

Wenn der Ladevorgang abgeschlossen ist, ruft LoaderManager einen Callback für onLoadFinished() auf. Eines der eingehenden Argumente für diese Methode ist ein Cursor mit den Ergebnissen der Abfrage. In Ihrer eigenen App können Sie die Daten aus diesem Cursor abrufen, um sie anzuzeigen oder weiter zu verarbeiten.

Batchänderung

Wenn möglich, sollten Sie Daten im Contacts Provider im „Batchmodus“ einfügen, aktualisieren und löschen. Erstellen Sie dazu ein ArrayList von ContentProviderOperation-Objekten und rufen Sie applyBatch() auf. Da der Contacts Provider alle Vorgänge in einem applyBatch() in einer einzigen Transaktion ausführt, bleiben die Kontaktdaten immer in einem konsistenten Zustand. Eine Batchänderung erleichtert auch das gleichzeitige Einfügen eines Rohkontakts und seiner Detaildaten.

Hinweis:Wenn du einen einzelnen Rohkontakt ändern möchtest, solltest du einen Intent an die Kontakte-App des Geräts senden, anstatt die Änderung in deiner App vorzunehmen. Dies wird im Abschnitt Abruf und Änderung mit Intents genauer beschrieben.

Umschaltpunkte

Eine Batchänderung mit einer großen Anzahl von Vorgängen kann andere Prozesse blockieren, was zu einer schlechten Nutzererfahrung führt. Wenn Sie alle gewünschten Änderungen in möglichst wenigen separaten Listen organisieren und gleichzeitig verhindern möchten, dass das System blockiert wird, sollten Sie für einen oder mehrere Vorgänge Umschaltpunkte festlegen. Ein Umschaltpunkt ist ein ContentProviderOperation-Objekt, dessen isYieldAllowed()-Wert auf true gesetzt ist. Wenn der Contacts Provider einen Umschaltpunkt erreicht, wird die Arbeit pausiert, damit andere Prozesse ausgeführt werden können, und die aktuelle Transaktion wird geschlossen. Wenn der Anbieter wieder startet, fährt er mit dem nächsten Vorgang in der ArrayList fort und startet eine neue Transaktion.

Umschaltpunkte führen zu mehr als einer Transaktion pro Aufruf von applyBatch(). Daher sollten Sie für den letzten Vorgang für eine Gruppe zusammengehöriger Zeilen einen Umschaltpunkt festlegen. Du solltest beispielsweise einen Umschaltpunkt für den letzten Vorgang in einer Gruppe festlegen, die eine Rohkontaktzeile und die zugehörigen Datenzeilen hinzufügt, oder für den letzten Vorgang für eine Gruppe von Zeilen, die sich auf einen einzelnen Kontakt beziehen.

Umschaltpunkte sind auch eine Einheit atomarer Vorgänge. Alle Zugriffe zwischen zwei Umschaltpunkten sind entweder erfolgreich oder schlagen als einzelne Einheit fehl. Wenn Sie keine Umschaltpunkte festlegen, ist die kleinste atomare Operation der gesamte Batch von Vorgängen. Wenn Sie Umschaltpunkte verwenden, verhindern Sie, dass Vorgänge die Systemleistung beeinträchtigen, und sorgen gleichzeitig dafür, dass eine Teilmenge der Vorgänge atomar ist.

Änderungsrückverweise

Wenn Sie eine neue Rohkontaktzeile und die zugehörigen Datenzeilen als Satz von ContentProviderOperation-Objekten einfügen, müssen Sie die Datenzeilen mit der Rohkontaktzeile verknüpfen. Dazu fügen Sie den _ID-Wert des Rohkontakts als RAW_CONTACT_ID-Wert ein. Dieser Wert ist jedoch nicht verfügbar, wenn Sie die ContentProviderOperation für die Datenzeile erstellen, da Sie die ContentProviderOperation für die Rohkontaktzeile noch nicht angewendet haben. Zur Umgehung dieses Problems bietet die Klasse ContentProviderOperation.Builder die Methode withValueBackReference(). Mit dieser Methode können Sie eine Spalte mit dem Ergebnis eines vorherigen Vorgangs einfügen oder ändern.

Die Methode withValueBackReference() hat zwei Argumente:

key
Der Schlüssel eines Schlüssel/Wert-Paars. Der Wert dieses Arguments sollte der Name einer Spalte in der Tabelle sein, die Sie ändern.
previousResult
Der nullbasierte Index eines Werts im Array von ContentProviderResult-Objekten aus applyBatch(). Während die Batchvorgänge angewendet werden, wird das Ergebnis jedes Vorgangs in einem Zwischenarray von Ergebnissen gespeichert. Der Wert previousResult ist der Index eines dieser Ergebnisse, der abgerufen und mit dem Wert key gespeichert wird. So kannst du einen neuen Rohkontaktdatensatz einfügen, den zugehörigen _ID-Wert abrufen und dann beim Hinzufügen einer ContactsContract.Data-Zeile einen „Rückverweis“ auf den Wert erstellen.

Das gesamte Ergebnisarray wird beim ersten Aufruf von applyBatch() erstellt. Seine Größe entspricht der Größe des ArrayList der von Ihnen bereitgestellten ContentProviderOperation-Objekte. Alle Elemente im Ergebnisarray sind jedoch auf null gesetzt. Wenn Sie versuchen, einen Rückverweis auf ein Ergebnis für einen Vorgang zu erstellen, der noch nicht angewendet wurde, löst withValueBackReference() einen Exception aus.

Die folgenden Snippets zeigen, wie Sie einen neuen Rohkontakt und Daten im Batch einfügen. Sie enthalten Code, der einen Umschaltpunkt festlegt und einen Rückverweis verwendet.

Das erste Snippet ruft Kontaktdaten aus der Benutzeroberfläche ab. Zu diesem Zeitpunkt hat der Nutzer bereits das Konto ausgewählt, dem der neue Rohkontakt hinzugefügt werden soll.

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

Im nächsten Snippet wird ein Vorgang erstellt, um die Rohkontaktzeile in die Tabelle ContactsContract.RawContacts einzufügen:

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

Als Nächstes werden im Code Datenzeilen für den Anzeigenamen, die Telefonnummer und die E-Mail-Adresse erstellt.

Jedes Vorgangs-Builder-Objekt verwendet withValueBackReference(), um die RAW_CONTACT_ID abzurufen. Der Verweis verweist auf das ContentProviderResult-Objekt aus dem ersten Vorgang, der die Zeile für den Rohkontakt hinzufügt und den neuen _ID-Wert zurückgibt. Daher wird jede Datenzeile automatisch über ihre RAW_CONTACT_ID mit der neuen ContactsContract.RawContacts-Zeile verknüpft, zu der sie gehört.

Das ContentProviderOperation.Builder-Objekt, das die E-Mail-Zeile hinzufügt, ist mit withYieldAllowed() gekennzeichnet, wodurch ein Umschaltpunkt festgelegt wird:

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

Das letzte Snippet zeigt den Aufruf von applyBatch(), mit dem der neue Rohkontakt und die Datenzeilen eingefügt werden.

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

Mit Batchvorgängen können Sie auch die optimistische Nebenläufigkeitserkennung implementieren. Dies ist eine Methode zum Anwenden von Änderungstransaktionen, ohne das zugrunde liegende Repository sperren zu müssen. Bei dieser Methode wenden Sie die Transaktion an und prüfen dann, ob gleichzeitig andere Änderungen vorgenommen wurden. Wenn Sie eine inkonsistente Änderung feststellen, machen Sie die Transaktion rückgängig und versuchen Sie es noch einmal.

Die optimistische Nebenläufigkeitserkennung ist für Mobilgeräte nützlich, da es dort jeweils nur einen Nutzer gibt und gleichzeitige Zugriffe auf ein Daten-Repository selten sind. Da keine Sperren verwendet werden, wird keine Zeit mit dem Festlegen von Sperren oder dem Warten auf die Freigabe von Sperren durch andere Transaktionen verschwendet.

So verwenden Sie die optimistische Nebenläufigkeitserkennung beim Aktualisieren einer einzelnen ContactsContract.RawContacts-Zeile:

  1. Rufe die Spalte VERSION des Rohkontakts zusammen mit den anderen Daten ab, die du abrufst.
  2. Erstellen Sie mit der Methode newAssertQuery(Uri) ein ContentProviderOperation.Builder-Objekt, das zum Erzwingen einer Einschränkung geeignet ist. Verwende für den Inhalts-URI RawContacts.CONTENT_URI mit der angehängten _ID des Rohkontakts.
  3. Rufen Sie für das Objekt ContentProviderOperation.Builder die Methode withValue() auf, um die Spalte VERSION mit der gerade abgerufenen Versionsnummer zu vergleichen.
  4. Rufen Sie für dieselbe ContentProviderOperation.Builder withExpectedCount() auf, um sicherzustellen, dass nur eine Zeile von dieser Assertion getestet wird.
  5. Rufen Sie build() auf, um das ContentProviderOperation-Objekt zu erstellen, und fügen Sie dieses Objekt als erstes Objekt in der ArrayList hinzu, die Sie an applyBatch() übergeben.
  6. Wenden Sie die Batchtransaktion an.

Wenn die Rohkontaktzeile zwischen dem Lesen der Zeile und dem Versuch, sie zu ändern, durch einen anderen Vorgang aktualisiert wird, schlägt die „Assert“-Anweisung ContentProviderOperation fehl und der gesamte Batch von Vorgängen wird rückgängig gemacht. Sie können dann den Batch noch einmal ausführen oder eine andere Aktion ausführen.

Das folgende Snippet zeigt, wie Sie eine „Assert“-ContentProviderOperation erstellen, nachdem Sie mit einem CursorLoader einen einzelnen Rohkontakt abgefragt haben:

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
    }

Abruf und Änderung mit Intents

Wenn du einen Intent an die Kontakte-App des Geräts sendest, kannst du indirekt auf den Contacts Provider zugreifen. Der Intent startet die Benutzeroberfläche der Kontakte-App des Geräts, in der Nutzer kontaktbezogene Aufgaben ausführen können. Mit dieser Art von Zugriff können Nutzer Folgendes tun:

  • Wählen Sie einen Kontakt aus einer Liste aus und lassen Sie ihn zur weiteren Bearbeitung an Ihre App zurückgeben.
  • Daten eines vorhandenen Kontakts bearbeiten
  • Einen neuen Rohkontakt für eines ihrer Konten einfügen.
  • Kontakte oder Kontaktdaten löschen

Wenn der Nutzer Daten einfügt oder aktualisiert, können Sie die Daten zuerst erfassen und als Teil des Intents senden.

Wenn du Intents verwendest, um über die Kontakte-App des Geräts auf den Contacts Provider zuzugreifen, musst du keine eigene Benutzeroberfläche oder keinen eigenen Code für den Zugriff auf den Anbieter schreiben. Außerdem musst du keine Berechtigung zum Lesen oder Schreiben beim Anbieter anfordern. Die Kontakte-App des Geräts kann die Leseberechtigung für einen Kontakt an dich delegieren. Da du Änderungen am Anbieter über eine andere App vornimmst, benötigst du keine Schreibberechtigungen.

Der allgemeine Vorgang zum Senden eines Intents für den Zugriff auf einen Anbieter wird im Leitfaden Grundlagen zu Contentanbietern im Abschnitt „Datenzugriff über Intents“ ausführlich beschrieben. Die Aktion, der MIME-Typ und die Datenwerte, die Sie für die verfügbaren Aufgaben verwenden, sind in Tabelle 4 zusammengefasst. Die Extras-Werte, die Sie mit putExtra() verwenden können, sind in der Referenzdokumentation für ContactsContract.Intents.Insert aufgeführt:

Tabelle 4. Intents für den Contacts Provider.

Aufgabe Aktion Daten MIME‑Typ Hinweise
Kontakt aus einer Liste auswählen ACTION_PICK Einer der folgenden Werte: Nicht verwendet Zeigt eine Liste der Rohkontakte oder eine Liste der Daten eines Rohkontakts an, je nach dem von dir angegebenen Inhalts-URI-Typ.

Rufen Sie startActivityForResult() auf, um den Inhalts-URI der ausgewählten Zeile zurückzugeben. Der URI hat das Format des Inhalts-URI der Tabelle, an den die LOOKUP_ID der Zeile angehängt ist. Die Kontakte App des Geräts delegiert Lese- und Schreibberechtigungen für diesen Inhalts-URI für die Dauer der Aktivität. Weitere Informationen finden Sie im Leitfaden Contentanbieter – Grundlagen.

Neuen Rohkontakt einfügen Insert.ACTION – RawContacts.CONTENT_TYPE, MIME-Typ für eine Gruppe von Rohkontakten. Zeigt den Bildschirm Kontakt hinzufügen der Kontakte-App des Geräts an. Die zusätzlichen Werte, die Sie dem Intent hinzufügen, werden angezeigt. Wenn der Intent mit startActivityForResult() gesendet wird, wird der Inhalts-URI des neu hinzugefügten Rohkontakts an die Callback-Methode onActivityResult() Ihrer Aktivität im Argument Intent im Feld „data“ zurückgegeben. Rufen Sie getData() auf, um den Wert abzurufen.
Kontakt bearbeiten ACTION_EDIT CONTENT_LOOKUP_URI für den Kontakt. Mit der Editor-Aktivität kann der Nutzer alle mit diesem Kontakt verknüpften Daten bearbeiten. Contacts.CONTENT_ITEM_TYPE, ein einzelner Kontakt. Zeigt den Bildschirm „Kontakt bearbeiten“ in der Kontakte-App an. Die zusätzlichen Werte, die du dem Intent hinzufügst, werden angezeigt. Wenn der Nutzer auf Fertig klickt, um die Änderungen zu speichern, wird Ihre Aktivität wieder in den Vordergrund gestellt.
Auswahl anzeigen, mit der auch Daten hinzugefügt werden können ACTION_INSERT_OR_EDIT – CONTENT_ITEM_TYPE Dieser Intent zeigt immer den Auswahlbildschirm der Kontakte App an. Der Nutzer kann entweder einen Kontakt zum Bearbeiten auswählen oder einen neuen Kontakt hinzufügen. Je nach Auswahl des Nutzers wird entweder der Bearbeitungs- oder der Hinzufügebildschirm angezeigt und die im Intent übergebenen zusätzlichen Daten werden angezeigt. Wenn in deiner App Kontaktdaten wie eine E-Mail-Adresse oder Telefonnummer angezeigt werden, kannst du diesen Intent verwenden, damit der Nutzer die Daten einem vorhandenen Kontakt hinzufügen kann. Kontakt,

Hinweis:Es ist nicht erforderlich, einen Namenswert in den Extras dieses Intents zu senden, da der Nutzer immer einen vorhandenen Namen auswählt oder einen neuen hinzufügt. Wenn du einen Namen sendest und der Nutzer eine Änderung vornimmt, wird in der Kontakte App der von dir gesendete Name angezeigt und der vorherige Wert überschrieben. Wenn der Nutzer dies nicht bemerkt und die Änderung speichert, geht der alte Wert verloren.

In der Kontakte App des Geräts ist es nicht möglich, einen Rohkontakt oder seine Daten mit einem Intent zu löschen. Verwende stattdessen ContentResolver.delete() oder ContentProviderOperation.newDelete(), um einen Rohkontakt zu löschen.

Das folgende Snippet zeigt, wie ein Intent erstellt und gesendet wird, der einen neuen Rohkontakt und neue Daten einfügt:

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

Datenintegrität

Da das Kontaktrepository wichtige und sensible Daten enthält, von denen Nutzer erwarten, dass sie korrekt und aktuell sind, gelten für den Contacts Provider klar definierte Regeln für die Datenintegrität. Es liegt in Ihrer Verantwortung, diese Regeln einzuhalten, wenn Sie Kontaktdaten ändern. Die wichtigsten Regeln sind hier aufgeführt:

Fügen Sie für jede ContactsContract.RawContacts-Zeile, die Sie hinzufügen, immer eine ContactsContract.CommonDataKinds.StructuredName-Zeile hinzu.
Eine ContactsContract.RawContacts-Zeile ohne eine ContactsContract.CommonDataKinds.StructuredName-Zeile in der ContactsContract.Data-Tabelle kann bei der Aggregation zu Problemen führen.
Verknüpfen Sie neue ContactsContract.Data-Zeilen immer mit der übergeordneten ContactsContract.RawContacts-Zeile.
Eine ContactsContract.Data-Zeile, die nicht mit einer ContactsContract.RawContacts verknüpft ist, wird in der Kontakte-App des Geräts nicht angezeigt und kann Probleme mit Sync Adaptern verursachen.
Ändern Sie nur Daten für die Rohkontakte, die Sie besitzen.
Der Contacts Provider verwaltet in der Regel Daten von verschiedenen Kontotypen/Onlinediensten. Sie müssen dafür sorgen, dass Ihre Anwendung nur Daten für Zeilen ändert oder löscht, die Ihnen gehören, und dass sie nur Daten mit einem Kontotyp und einem Namen einfügt, die Sie kontrollieren.
Verwende immer die in ContactsContract und den zugehörigen Unterklassen definierten Konstanten für Authorities, Inhalts-URIs, URI-Pfade, Spaltennamen, MIME-Typen und TYPE-Werte.
Durch die Verwendung dieser Konstanten können Sie Fehler vermeiden. Sie werden auch durch Compilerwarnungen benachrichtigt, wenn eine der Konstanten verworfen wurde.

Benutzerdefinierte Datenzeilen

Wenn du eigene benutzerdefinierte MIME-Typen erstellst und verwendest, kannst du deine eigenen Datenzeilen in die Tabelle ContactsContract.Data einfügen, bearbeiten, löschen und abrufen. Ihre Zeilen sind auf die in ContactsContract.DataColumns definierte Spalte beschränkt. Sie können jedoch Ihre eigenen typspezifischen Spaltennamen den Standardspaltennamen zuordnen. In der Kontakte-App des Geräts werden die Daten für Ihre Zeilen angezeigt, können aber nicht bearbeitet oder gelöscht werden. Nutzer können auch keine zusätzlichen Daten hinzufügen. Damit Nutzer deine benutzerdefinierten Datenzeilen ändern können, musst du in deiner eigenen Anwendung eine Editor-Aktivität bereitstellen.

Wenn Sie Ihre benutzerdefinierten Daten anzeigen möchten, stellen Sie eine contacts.xml-Datei mit einem <ContactsAccountType>-Element und einem oder mehreren untergeordneten <ContactsDataKind>-Elementen bereit. Dies wird im Abschnitt <ContactsDataKind> element ausführlicher beschrieben.

Weitere Informationen zu benutzerdefinierten MIME-Typen findest du im Leitfaden Contentanbieter erstellen.

Sync Adapter für Contacts Provider

Der Contacts Provider ist speziell für die Synchronisierung von Kontaktdaten zwischen einem Gerät und einem Onlinedienst konzipiert. So können Nutzer vorhandene Daten auf ein neues Gerät herunterladen und vorhandene Daten in ein neues Konto hochladen. Durch die Synchronisierung wird auch sichergestellt, dass Nutzer die neuesten Daten zur Hand haben, unabhängig von der Quelle der Ergänzungen und Änderungen. Ein weiterer Vorteil der Synchronisierung besteht darin, dass Kontaktdaten auch dann verfügbar sind, wenn das Gerät nicht mit dem Netzwerk verbunden ist.

Die Synchronisierung lässt sich auf verschiedene Arten implementieren. Das Android-System bietet jedoch ein Plug-in-Synchronisierungs-Framework, mit dem die folgenden Aufgaben automatisiert werden:

  • Netzwerkverfügbarkeit wird geprüft.
  • Planung und Ausführung der Synchronisierung basierend auf den Nutzereinstellungen
  • Synchronisierungen neu starten, die beendet wurden

Wenn Sie dieses Framework verwenden möchten, müssen Sie ein Sync Adapter-Plug-in bereitstellen. Jeder Sync Adapter ist für einen Dienst und einen Contentanbieter eindeutig, kann aber mehrere Kontonamen für denselben Dienst verarbeiten. Das Framework ermöglicht auch mehrere Sync Adapter für denselben Dienst und Anbieter.

Sync Adapter-Klassen und -Dateien

Du implementierst einen Sync Adapter als Unterklasse von AbstractThreadedSyncAdapter und installierst ihn als Teil einer Android-App. Das System erfährt von den Elementen in Ihrem App-Manifest und von einer speziellen XML-Datei, auf die das Manifest verweist, vom Sync Adapter. In der XML-Datei werden der Kontotyp für den Onlinedienst und die Autorität für den Contentanbieter definiert, die zusammen den Adapter eindeutig identifizieren. Der Sync Adapter wird erst aktiv, wenn der Nutzer ein Konto für den Kontotyp des Sync Adapters hinzufügt und die Synchronisierung für den Contentanbieter aktiviert, mit dem der Sync Adapter synchronisiert wird. Ab diesem Zeitpunkt wird der Adapter vom System verwaltet und bei Bedarf aufgerufen, um die Synchronisierung zwischen dem Contentanbieter und dem Server durchzuführen.

Hinweis:Wenn Sie einen Kontotyp als Teil der Identifikation des Sync Adapters verwenden, kann das System Sync Adapter erkennen und gruppieren, die auf verschiedene Dienste derselben Organisation zugreifen. Beispielsweise haben Sync Adapter für Google-Onlinedienste alle denselben Kontotyp com.google. Wenn Nutzer ihren Geräten ein Google-Konto hinzufügen, werden alle installierten Sync Adapter für Google-Dienste zusammen aufgeführt. Jeder aufgeführte Sync Adapter wird mit einem anderen Contentanbieter auf dem Gerät synchronisiert.

Da die meisten Dienste von Nutzern verlangen, dass sie ihre Identität bestätigen, bevor sie auf Daten zugreifen, bietet das Android-System ein Authentifizierungs-Framework, das dem Sync Adapter-Framework ähnelt und häufig in Verbindung mit diesem verwendet wird. Das Authentifizierungs-Framework verwendet Plug-in-Authentifikatoren, die Unterklassen von AbstractAccountAuthenticator sind. Ein Authenticator überprüft die Identität des Nutzers in den folgenden Schritten:

  1. Erfasst den Namen, das Passwort oder ähnliche Informationen des Nutzers (die Anmeldedaten des Nutzers).
  2. Sendet die Anmeldedaten an den Dienst.
  3. Prüft die Antwort des Dienstes.

Wenn der Dienst die Anmeldedaten akzeptiert, kann der Authenticator die Anmeldedaten zur späteren Verwendung speichern. Aufgrund des Plug-in-Authentifizierungs-Frameworks kann der AccountManager Zugriff auf alle Authentifizierungstokens gewähren, die ein Authentifikator unterstützt und verfügbar macht, z. B. OAuth2-Authentifizierungstokens.

Die Authentifizierung ist zwar nicht erforderlich, wird aber von den meisten Kontaktdiensten verwendet. Sie sind jedoch nicht verpflichtet, das Android-Authentifizierungs-Framework für die Authentifizierung zu verwenden.

Implementierung des Sync Adapters

Wenn du einen Sync Adapter für den Contacts Provider implementieren möchtest, musst du zuerst eine Android-App erstellen, die Folgendes enthält:

Eine Service-Komponente, die auf Anfragen des Systems zur Bindung an den Sync Adapter reagiert.
Wenn das System eine Synchronisierung ausführen möchte, ruft es die Methode onBind() des Dienstes auf, um einen IBinder für den Sync Adapter abzurufen. So kann das System prozessübergreifende Aufrufe der Adaptermethoden ausführen.
Der eigentliche Sync Adapter, der als konkrete Unterklasse von AbstractThreadedSyncAdapter implementiert ist.
Diese Klasse übernimmt das Herunterladen von Daten vom Server, das Hochladen von Daten vom Gerät und das Lösen von Konflikten. Die Hauptarbeit des Adapters wird in der Methode onPerformSync() erledigt. Diese Klasse muss als Singleton instanziiert werden.
Eine abgeleitete Klasse von Application.
Diese Klasse fungiert als Factory für das Sync Adapter-Singleton. Verwende die Methode onCreate(), um den Sync Adapter zu instanziieren, und stelle eine statische „Getter“-Methode bereit, um das Singleton an die Methode onBind() des Dienstes des Sync Adapters zurückzugeben.
Optional: Eine Service-Komponente, die auf Anfragen des Systems zur Nutzerauthentifizierung reagiert.
AccountManager startet diesen Dienst, um den Authentifizierungsprozess zu beginnen. Die Methode onCreate() des Dienstes instanziiert ein Authentifizierungsobjekt. Wenn das System ein Nutzerkonto für den Sync Adapter der Anwendung authentifizieren möchte, ruft es die Methode onBind() des Dienstes auf, um ein IBinder für den Authentifikator abzurufen. So kann das System prozessübergreifende Aufrufe an die Methoden des Authentifikators senden.
Optional: Eine konkrete Unterklasse von AbstractAccountAuthenticator, die Anfragen zur Authentifizierung verarbeitet.
Diese Klasse stellt Methoden bereit, die von der AccountManager aufgerufen werden, um die Anmeldedaten des Nutzers beim Server zu authentifizieren. Die Details des Authentifizierungsprozesses variieren je nach verwendeter Servertechnologie stark. Weitere Informationen zur Authentifizierung finden Sie in der Dokumentation zu Ihrer Serversoftware.
XML-Dateien, die den Sync Adapter und den Authenticator für das System definieren.
Die zuvor beschriebenen Sync Adapter- und Authentifizierungsdienst-Komponenten werden in <service>-Elementen im Anwendungsmanifest definiert. Diese Elemente enthalten untergeordnete <meta-data>-Elemente, die dem System bestimmte Daten bereitstellen:
  • Das Element <meta-data> für den Sync Adapter-Dienst verweist auf die XML-Datei res/xml/syncadapter.xml. In dieser Datei wird wiederum ein URI für den Webdienst angegeben, der mit dem Contacts Provider synchronisiert wird, sowie ein Kontotyp für den Webdienst.
  • Optional: Das Element <meta-data> für den Authenticator verweist auf die XML-Datei res/xml/authenticator.xml. In dieser Datei wird wiederum der Kontotyp angegeben, den dieser Authentifikator unterstützt, sowie UI-Ressourcen, die während des Authentifizierungsprozesses angezeigt werden. Der in diesem Element angegebene Kontotyp muss mit dem für den Sync Adapter angegebenen Kontotyp übereinstimmen.

Daten aus sozialen Netzwerken

Die Tabellen „android.provider.ContactsContract.StreamItems“ und „android.provider.ContactsContract.StreamItemPhotos“ verwalten eingehende Daten aus sozialen Netzwerken. Du kannst einen Sync Adapter schreiben, der diesen Tabellen Streamdaten aus deinem eigenen Netzwerk hinzufügt, oder du kannst Streamdaten aus diesen Tabellen lesen und in deiner eigenen Anwendung anzeigen lassen – oder beides. Mit diesen Funktionen können deine Dienste und Anwendungen für soziale Netzwerke in die Android-Umgebung für soziale Netzwerke eingebunden werden.

Text für Social-Media-Stream

Stream-Elemente sind immer mit einem Rohkontakt verknüpft. Die android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID ist mit dem Wert _ID für den Rohkontakt verknüpft. Der Kontotyp und der Kontoname des Rohkontakts werden ebenfalls in der Zeile des Stream-Elements gespeichert.

Speichern Sie die Daten aus Ihrem Stream in den folgenden Spalten:

android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
Erforderlich. Der Kontotyp des Nutzers für den Rohkontakt, der mit diesem Stream-Element verknüpft ist. Denken Sie daran, diesen Wert festzulegen, wenn Sie ein Stream-Element einfügen.
android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
Erforderlich. Der Kontoname des Nutzers für den Rohkontakt, der mit diesem Stream-Element verknüpft ist. Denken Sie daran, diesen Wert festzulegen, wenn Sie ein Stream-Element einfügen.
ID-Spalten
Erforderlich. Sie müssen die folgenden Kennungsspalten einfügen, wenn Sie ein Stream-Element einfügen:
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: Der android.provider.BaseColumns#_ID-Wert des Kontakts, mit dem dieses Stream-Element verknüpft ist.
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: Der Wert android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY des Kontakts, mit dem dieses Stream-Element verknüpft ist.
  • android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: Der android.provider.BaseColumns#_ID-Wert des Rohkontakts, mit dem dieses Stream-Element verknüpft ist.
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
Optional. Speichert Zusammenfassungsinformationen, die Sie am Anfang eines Stream-Elements anzeigen können.
android.provider.ContactsContract.StreamItemsColumns#TEXT
Der Text des Stream-Elements, entweder der von der Quelle des Elements gepostete Inhalt oder eine Beschreibung einer Aktion, die das Stream-Element generiert hat. Diese Spalte kann beliebige Formatierungen und eingebettete Ressourcenbilder enthalten, die von fromHtml() gerendert werden können. Der Anbieter kann lange Inhalte kürzen oder mit Auslassungspunkten versehen, versucht aber, Tags nicht zu unterbrechen.
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
Ein Textstring, der die Zeit enthält, zu der das Stream-Element eingefügt oder aktualisiert wurde, in Form von Millisekunden seit der Epoche. Anwendungen, die Stream-Elemente einfügen oder aktualisieren, sind für die Pflege dieser Spalte verantwortlich. Sie wird nicht automatisch vom Contacts Provider gepflegt.

Wenn du identifizierende Informationen für deine Stream-Elemente anzeigen möchtest, verwende android.provider.ContactsContract.StreamItemsColumns#RES_ICON, android.provider.ContactsContract.StreamItemsColumns#RES_LABEL und android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE, um auf Ressourcen in deiner Anwendung zu verlinken.

Die Tabelle „android.provider.ContactsContract.StreamItems“ enthält auch die Spalten „android.provider.ContactsContract.StreamItemsColumns#SYNC1“ bis „android.provider.ContactsContract.StreamItemsColumns#SYNC4“, die ausschließlich für Sync Adapter vorgesehen sind.

Fotos im sozialen Stream

In der Tabelle „android.provider.ContactsContract.StreamItemPhotos“ werden Fotos gespeichert, die mit einem Stream-Element verknüpft sind. Die Spalte „android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID“ der Tabelle ist mit Werten in der Spalte „_ID“ der Tabelle „android.provider.ContactsContract.StreamItems“ verknüpft. Fotoreferenzen werden in der Tabelle in den folgenden Spalten gespeichert:

android.provider.ContactsContract.StreamItemPhotos#PHOTO-Spalte (ein BLOB).
Eine binäre Darstellung des Fotos, die vom Anbieter für die Speicherung und Anzeige in der Größe angepasst wurde. Diese Spalte ist für die Abwärtskompatibilität mit früheren Versionen des Contacts Provider verfügbar, in denen sie zum Speichern von Fotos verwendet wurde. In der aktuellen Version sollten Sie diese Spalte jedoch nicht zum Speichern von Fotos verwenden. Verwende stattdessen entweder android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID oder android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI (beide werden in den folgenden Punkten beschrieben), um Fotos in einer Datei zu speichern. Diese Spalte enthält jetzt eine Miniaturansicht des Fotos, die gelesen werden kann.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
Eine numerische Kennung eines Fotos für einen Rohkontakt. Hänge diesen Wert an die Konstante DisplayPhoto.CONTENT_URI an, um einen Inhalts-URI zu erhalten, der auf eine einzelne Fotodatei verweist. Rufe dann openAssetFileDescriptor() auf, um ein Handle für die Fotodatei zu erhalten.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
Ein Inhalts-URI, der direkt auf die Fotodatei für das Foto verweist, das durch diese Zeile dargestellt wird. Rufen Sie openAssetFileDescriptor() mit diesem URI auf, um ein Handle für die Fotodatei zu erhalten.

Tabellen für soziale Streams verwenden

Diese Tabellen funktionieren genauso wie die anderen Haupttabellen im Contacts Provider, mit folgenden Ausnahmen:

  • Für diese Tabellen sind zusätzliche Zugriffsberechtigungen erforderlich. Damit deine App sie lesen kann, muss sie die Berechtigung „android.Manifest.permission#READ_SOCIAL_STREAM“ haben. Wenn Sie sie ändern möchten, muss Ihre Anwendung die Berechtigung android.Manifest.permission#WRITE_SOCIAL_STREAM haben.
  • In der Tabelle „android.provider.ContactsContract.StreamItems“ ist die Anzahl der Zeilen, die für jeden Rohkontakt gespeichert werden, begrenzt. Wenn dieses Limit erreicht ist, schafft der Contacts Provider Platz für neue Stream-Elementzeilen, indem er automatisch die Zeilen mit dem ältesten android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP löscht. Um das Limit abzurufen, sende eine Abfrage an den Content-URI android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI. Sie können alle Argumente außer dem Inhalts-URI auf null setzen. Die Abfrage gibt einen Cursor mit einer einzelnen Zeile und der einzelnen Spalte android.provider.ContactsContract.StreamItems#MAX_ITEMS zurück.

Die Klasse android.provider.ContactsContract.StreamItems.StreamItemPhotos definiert eine Untertabelle von android.provider.ContactsContract.StreamItemPhotos, die die Fotozeilen für ein einzelnes Stream-Element enthält.

Interaktionen im sozialen Stream

Die vom Contacts Provider verwalteten Social-Stream-Daten bieten in Verbindung mit der Kontakte-App des Geräts eine leistungsstarke Möglichkeit, dein soziales Netzwerk mit vorhandenen Kontakten zu verbinden. Folgende Funktionen sind verfügbar:

  • Wenn du deinen sozialen Netzwerkdienst mit einem Sync Adapter mit dem Contacts Provider synchronisierst, kannst du die letzten Aktivitäten für die Kontakte eines Nutzers abrufen und in den Tabellen android.provider.ContactsContract.StreamItems und android.provider.ContactsContract.StreamItemPhotos zur späteren Verwendung speichern.
  • Neben der regulären Synchronisierung kannst du den Sync Adapter auch so einrichten, dass er zusätzliche Daten abruft, wenn der Nutzer einen Kontakt auswählt. So kann dein Sync Adapter hochauflösende Fotos und die neuesten Stream-Elemente für den Kontakt abrufen.
  • Wenn du eine Benachrichtigung bei der Kontakte-App des Geräts und dem Contacts Provider registrierst, kannst du einen Intent empfangen, wenn ein Kontakt aufgerufen wird. Zu diesem Zeitpunkt kannst du den Status des Kontakts über deinen Dienst aktualisieren. Dieser Ansatz ist möglicherweise schneller und verbraucht weniger Bandbreite als eine vollständige Synchronisierung mit einem Sync Adapter.
  • Nutzer können einen Kontakt zu deinem sozialen Netzwerk hinzufügen, während sie sich den Kontakt in der Kontakte-App des Geräts ansehen. Dazu verwendest du die Funktion „Kontakt einladen“, die du mit einer Kombination aus einer Aktivität, die deinem Netzwerk einen vorhandenen Kontakt hinzufügt, und einer XML-Datei aktivierst, die der Kontakte-App des Geräts und dem Contacts Provider die Details deiner App zur Verfügung stellt.

Die regelmäßige Synchronisierung von Stream-Elementen mit dem Contacts Provider erfolgt wie andere Synchronisierungen. Weitere Informationen zur Synchronisierung findest du im Abschnitt Sync Adapter für Contacts Provider. Die Registrierung von Benachrichtigungen und das Einladen von Kontakten werden in den nächsten beiden Abschnitten behandelt.

Registrieren, um Aufrufe aus sozialen Netzwerken zu verarbeiten

So registrierst du deinen Sync Adapter, um Benachrichtigungen zu erhalten, wenn der Nutzer einen Kontakt aufruft, der von deinem Sync Adapter verwaltet wird:

  1. Erstellen Sie eine Datei mit dem Namen contacts.xml im Verzeichnis res/xml/ Ihres Projekts. Wenn Sie diese Datei bereits haben, können Sie diesen Schritt überspringen.
  2. Fügen Sie in dieser Datei das Element <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> hinzu. Wenn dieses Element bereits vorhanden ist, können Sie diesen Schritt überspringen.
  3. Wenn Sie einen Dienst registrieren möchten, der benachrichtigt wird, wenn der Nutzer die Detailseite eines Kontakts in der Kontakte-App des Geräts öffnet, fügen Sie dem Element das Attribut viewContactNotifyService="serviceclass" hinzu. Dabei ist serviceclass der vollständig qualifizierte Klassenname des Dienstes, der den Intent von der Kontakte-App des Geräts empfangen soll. Verwenden Sie für den Benachrichtigungsdienst eine Klasse, die IntentService erweitert, damit der Dienst Intents empfangen kann. Die Daten im eingehenden Intent enthalten den Inhalts-URI des Rohkontakts, auf den der Nutzer geklickt hat. Über den Benachrichtigungsdienst kannst du eine Bindung an deinen Sync Adapter herstellen und ihn dann aufrufen, um die Daten für den Rohkontakt zu aktualisieren.

So registrieren Sie eine Aktivität, die aufgerufen wird, wenn der Nutzer auf ein Stream-Element, ein Foto oder beides klickt:

  1. Erstellen Sie eine Datei mit dem Namen contacts.xml im Verzeichnis res/xml/ Ihres Projekts. Wenn Sie diese Datei bereits haben, können Sie diesen Schritt überspringen.
  2. Fügen Sie in dieser Datei das Element <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> hinzu. Wenn dieses Element bereits vorhanden ist, können Sie diesen Schritt überspringen.
  3. Wenn du eine deiner Aktivitäten registrieren möchtest, um das Klicken des Nutzers auf ein Stream-Element in der Kontakte-App des Geräts zu verarbeiten, füge dem Element das Attribut viewStreamItemActivity="activityclass" hinzu. Dabei ist activityclass der vollständig qualifizierte Klassenname der Aktivität, die den Intent von der Kontakte-App des Geräts empfangen soll.
  4. Wenn du eine deiner Aktivitäten registrieren möchtest, um das Klicken des Nutzers auf ein Stream-Foto in der Kontakte-App des Geräts zu verarbeiten, füge dem Element das Attribut viewStreamItemPhotoActivity="activityclass" hinzu. Dabei ist activityclass der vollständig qualifizierte Klassenname der Aktivität, die den Intent von der Kontakte-App des Geräts empfangen soll.

Das Element <ContactsAccountType> wird im Abschnitt <ContactsAccountType>-Element ausführlicher beschrieben.

Der eingehende Intent enthält den Inhalts-URI des Elements oder Fotos, auf das der Nutzer geklickt hat. Wenn Sie separate Aktivitäten für Textelemente und Fotos haben möchten, verwenden Sie beide Attribute in derselben Datei.

Interaktion mit Ihrem sozialen Netzwerk

Nutzer müssen die Kontakte-App des Geräts nicht verlassen, um einen Kontakt zu Ihrer sozialen Netzwerkwebsite einzuladen. Stattdessen kannst du die Kontakte-App des Geräts einen Intent senden lassen, um den Kontakt zu einer deiner Aktivitäten einzuladen. So richtest du ein Konto mit Elternaufsicht ein:

  1. Erstellen Sie eine Datei mit dem Namen contacts.xml im Verzeichnis res/xml/ Ihres Projekts. Wenn Sie diese Datei bereits haben, können Sie diesen Schritt überspringen.
  2. Fügen Sie in dieser Datei das Element <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> hinzu. Wenn dieses Element bereits vorhanden ist, können Sie diesen Schritt überspringen.
  3. Fügen Sie die folgenden Attribute hinzu:
    • inviteContactActivity="activityclass"
    • inviteContactActionLabel="@string/invite_action_label"
    Der Wert activityclass ist der voll qualifizierte Klassenname der Aktivität, die den Intent empfangen soll. Der Wert invite_action_label ist ein Textstring, der in der Kontakte-App des Geräts im Menü Verbindung hinzufügen angezeigt wird.

Hinweis: ContactsSource ist ein veralteter Tag-Name für ContactsAccountType.

contacts.xml-Referenz

Die Datei contacts.xml enthält XML-Elemente, die die Interaktion des Sync Adapters und der Anwendung mit der Kontakte-App und dem Contacts Provider steuern. Diese Elemente werden in den folgenden Abschnitten beschrieben.

<ContactsAccountType>-Element

Das Element <ContactsAccountType> steuert die Interaktion Ihrer Anwendung mit der Kontakte-App. Die Syntax lautet:

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

enthalten in:

res/xml/contacts.xml

kann Folgendes enthalten:

<ContactsDataKind>

Beschreibung:

Deklariert Android-Komponenten und UI-Labels, mit denen Nutzer einen ihrer Kontakte in ein soziales Netzwerk einladen, Nutzer benachrichtigen können, wenn einer ihrer Streams in sozialen Netzwerken aktualisiert wird, und so weiter.

Beachten Sie, dass das Attributpräfix android: für die Attribute von <ContactsAccountType> nicht erforderlich ist.

Attribute:

inviteContactActivity
Der vollständig qualifizierte Klassenname der Aktivität in deiner App, die aktiviert werden soll, wenn der Nutzer in der Kontakte-App des Geräts Verbindung hinzufügen auswählt.
inviteContactActionLabel
Ein Textstring, der für die in inviteContactActivity angegebene Aktivität im Menü Verbindung hinzufügen angezeigt wird. Sie können beispielsweise den String „In meinem Netzwerk folgen“ verwenden. Sie können für dieses Label eine String-Ressourcen-ID verwenden.
viewContactNotifyService
Der vollständig qualifizierte Klassenname eines Dienstes in Ihrer Anwendung, der Benachrichtigungen erhalten soll, wenn der Nutzer einen Kontakt aufruft. Diese Benachrichtigung wird von der Kontakte-App des Geräts gesendet. Sie ermöglicht es Ihrer Anwendung, datenintensive Vorgänge zu verschieben, bis sie benötigt werden. Deine Anwendung kann beispielsweise auf diese Benachrichtigung reagieren, indem sie das hochauflösende Foto des Kontakts und die neuesten Social-Stream-Elemente einliest und anzeigt. Diese Funktion wird im Abschnitt Interaktionen mit sozialen Streams ausführlicher beschrieben.
viewGroupActivity
Der vollständig qualifizierte Klassenname einer Aktivität in Ihrer Anwendung, die Gruppeninformationen anzeigen kann. Wenn der Nutzer in der Kontakte-App des Geräts auf das Gruppenlabel klickt, wird die Benutzeroberfläche für diese Aktivität angezeigt.
viewGroupActionLabel
Das Label, das die Kontakte-App für ein UI-Steuerelement anzeigt, mit dem der Nutzer Gruppen in deiner App ansehen kann.

Für dieses Attribut ist eine String-Ressourcen-ID zulässig.

viewStreamItemActivity
Der voll qualifizierte Klassenname einer Aktivität in Ihrer Anwendung, die von der Kontakte-App des Geräts gestartet wird, wenn der Nutzer auf ein Streamelement für einen Rohkontakt klickt.
viewStreamItemPhotoActivity
Der voll qualifizierte Klassenname einer Aktivität in deiner Anwendung, die von der Kontakte-App des Geräts gestartet wird, wenn der Nutzer auf ein Foto im Stream-Element für einen Rohkontakt klickt.

<ContactsDataKind>-Element

Das Element <ContactsDataKind> steuert die Anzeige der benutzerdefinierten Datenzeilen deiner App in der Benutzeroberfläche der Kontakte-App. Die Syntax lautet:

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

enthalten in:

<ContactsAccountType>

Beschreibung:

Mit diesem Element kannst du die Kontakte-App anweisen, den Inhalt einer benutzerdefinierten Datenzeile als Teil der Details eines Rohkontakts anzuzeigen. Jedes untergeordnete <ContactsDataKind>-Element von <ContactsAccountType> stellt einen Typ von benutzerdefinierter Datenzeile dar, die der Sync Adapter der Tabelle ContactsContract.Data hinzufügt. Fügen Sie für jeden benutzerdefinierten MIME-Typ, den Sie verwenden, ein <ContactsDataKind>-Element hinzu. Sie müssen das Element nicht hinzufügen, wenn Sie eine benutzerdefinierte Datenzeile haben, für die Sie keine Daten anzeigen möchten.

Attribute:

android:mimeType
Der benutzerdefinierte MIME-Typ, den Sie für einen Ihrer benutzerdefinierten Datenzeilentypen in der Tabelle ContactsContract.Data definiert haben. Der Wert vnd.android.cursor.item/vnd.example.locationstatus könnte beispielsweise ein benutzerdefinierter MIME-Typ für eine Datenzeile sein, in der der letzte bekannte Standort eines Kontakts aufgezeichnet wird.
android:icon
Eine zeichenfähige Android-Ressource, die die Kontakte-App neben Ihren Daten anzeigt. Damit kannst du dem Nutzer zeigen, dass die Daten von deinem Dienst stammen.
android:summaryColumn
Der Spaltenname für den ersten von zwei Werten, die aus der Datenzeile abgerufen werden. Der Wert wird als erste Zeile des Eintrags für diese Datenzeile angezeigt. Die erste Zeile soll als Zusammenfassung der Daten dienen, ist aber optional. Siehe auch android:detailColumn.
android:detailColumn
Der Spaltenname für den zweiten der beiden Werte, die aus der Datenzeile abgerufen werden. Der Wert wird als zweite Zeile des Eintrags für diese Datenzeile angezeigt. Siehe auch android:summaryColumn.

Zusätzliche Funktionen des Contacts Providers

Neben den in den vorherigen Abschnitten beschriebenen Hauptfunktionen bietet der Contacts Provider die folgenden nützlichen Funktionen für die Arbeit mit Kontaktdaten:

  • Kontaktgruppen
  • Fotofunktionen

Kontaktgruppen

Der Contacts Provider kann Sammlungen ähnlicher Kontakte optional mit Gruppendaten kennzeichnen. Wenn der mit einem Nutzerkonto verknüpfte Server Gruppen verwalten soll, muss der Sync Adapter für den Kontotyp des Kontos Gruppendaten zwischen dem Contacts Provider und dem Server übertragen. Wenn Nutzer dem Server einen neuen Kontakt hinzufügen und diesen dann in eine neue Gruppe einfügen, muss der Sync Adapter die neue Gruppe der Tabelle ContactsContract.Groups hinzufügen. Die Gruppe oder Gruppen, zu denen ein Rohkontakt gehört, werden in der Tabelle ContactsContract.Data mit dem MIME-Typ ContactsContract.CommonDataKinds.GroupMembership gespeichert.

Wenn du einen Sync Adapter entwirfst, der Rohkontaktdaten vom Server zum Contacts Provider hinzufügt, und du keine Gruppen verwendest, musst du den Provider anweisen, deine Daten sichtbar zu machen. Aktualisiere im Code, der ausgeführt wird, wenn ein Nutzer dem Gerät ein Konto hinzufügt, die Zeile ContactsContract.Settings, die der Contacts Provider für das Konto hinzufügt. Legen Sie in dieser Zeile den Wert der Spalte Settings.UNGROUPED_VISIBLE auf 1 fest. In diesem Fall macht der Contacts Provider deine Kontaktdaten immer sichtbar, auch wenn du keine Gruppen verwendest.

Fotos des Kontakts

In der Tabelle ContactsContract.Data werden Fotos als Zeilen mit dem MIME-Typ Photo.CONTENT_ITEM_TYPE gespeichert. Die Spalte CONTACT_ID der Zeile ist mit der Spalte _ID des Rohkontakts verknüpft, zu dem sie gehört. Die Klasse ContactsContract.Contacts.Photo definiert eine Untertabelle von ContactsContract.Contacts, die Fotoinformationen für das primäre Foto eines Kontakts enthält. Dies ist das primäre Foto des primären Rohkontakts des Kontakts. Ebenso definiert die Klasse ContactsContract.RawContacts.DisplayPhoto eine Untertabelle von ContactsContract.RawContacts, die Fotoinformationen für das primäre Foto eines Rohkontakts enthält.

Die Referenzdokumentation für ContactsContract.Contacts.Photo und ContactsContract.RawContacts.DisplayPhoto enthält Beispiele zum Abrufen von Fotoinformationen. Es gibt keine Convenience-Klasse zum Abrufen der primären Miniaturansicht für einen Rohkontakt. Sie können jedoch eine Abfrage an die Tabelle ContactsContract.Data senden und dabei die Spalten _ID, Photo.CONTENT_ITEM_TYPE und IS_PRIMARY des Rohkontakts auswählen, um die Zeile mit dem primären Foto des Rohkontakts zu finden.

Die Daten aus dem sozialen Stream einer Person können auch Fotos enthalten. Diese werden in der Tabelle „android.provider.ContactsContract.StreamItemPhotos“ gespeichert, die im Abschnitt Fotos im sozialen Stream ausführlicher beschrieben wird.