Contacts Provider

Der Contacts Provider ist eine leistungsstarke und flexible Android-Komponente, die das zentrale Repository für Daten zu Personen auf dem Gerät verwaltet. Der Kontakteanbieter ist die Datenquelle, die in der Kontakte-App des Geräts angezeigt wird. Sie können auch in Ihrer 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 Ergebnis ist eine komplexe Organisation. Aus diesem Grund enthält die API des Anbieters eine Vielzahl von Vertragsklassen und ‑schnittstellen, die sowohl das Abrufen als auch das Ändern von Daten erleichtern.

In diesem Leitfaden wird Folgendes beschrieben:

  • Die grundlegende Anbieterstruktur.
  • So rufen Sie Daten vom Anbieter ab.
  • So ändern Sie Daten im Anbieter.
  • So schreiben Sie einen Synchronisierungsadapter zum Synchronisieren von Daten von Ihrem Server mit dem Kontakteanbieter.

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

Organisation des Contacts Provider

Der Contacts Provider ist eine Android-Contentanbieterkomponente. Es werden drei Arten von Daten zu einer Person verwaltet, die jeweils einer vom Anbieter bereitgestellten Tabelle entsprechen (siehe Abbildung 1):

Abbildung 1: Tabellenstruktur des Kontakteanbieters.

Die drei Tabellen werden häufig nach den Namen ihrer Vertragsklassen benannt. 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 Rohkontaktdatenzeilen.
ContactsContract.RawContacts-Tabelle
Zeilen mit einer Zusammenfassung der Daten einer Person, die sich auf ein Nutzerkonto und einen Typ beziehen.
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 Kontakteanbieter zur Verwaltung seiner Vorgänge oder zur Unterstützung bestimmter Funktionen in den Kontakt- oder Telefonieanwendungen des Geräts verwendet.

Rohkontakte

Ein Rohkontakt stellt die Daten einer Person aus einem einzelnen Kontotyp und Kontonamen dar. Da der Kontakteanbieter mehr als einen Onlinedienst als Datenquelle für eine Person zulässt, sind mehrere Rohkontakte für dieselbe Person möglich. Mit mehreren Rohkontakten kann ein Nutzer auch die Daten einer Person aus mehreren Konten desselben Kontotyps kombinieren.

Die meisten Daten für einen Rohkontakt werden nicht in der Tabelle ContactsContract.RawContacts gespeichert. Stattdessen wird 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 Anmerkungen 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äteinhabers. Weitere Informationen finden Sie im nächsten Eintrag für ACCOUNT_TYPE. Das Format dieses Namens ist für den jeweiligen Kontotyp spezifisch. Es muss nicht unbedingt eine E‑Mail-Adresse sein.
ACCOUNT_TYPE Der Kontotyp, aus dem dieser Rohkontakt stammt. Der Kontotyp eines Google-Kontos ist beispielsweise com.google. Geben Sie immer den Kontotyp mit einer Domain-ID für eine Domain an, deren Inhaber Sie sind oder die Sie verwalten. So wird sichergestellt, dass Ihr Kontotyp eindeutig ist. Für einen Kontotyp, der Kontaktdaten bietet, ist in der Regel ein Synchronisierungsadapter vorhanden, der mit dem Kontakteanbieter synchronisiert wird.
DELETED Das Flag „deleted“ für einen Rohkontakt. Mit diesem Flag kann der Kontakteanbieter die Zeile intern beibehalten, bis Synchronisierungsadapter die Zeile von ihren Servern löschen und sie dann endgültig aus dem Repository gelöscht wird.

Hinweise

Wichtige Hinweise zur Tabelle ContactsContract.RawContacts:

  • Der Name eines Rohkontakts wird nicht in seiner Zeile in ContactsContract.RawContacts gespeichert. Stattdessen wird sie in der Tabelle ContactsContract.Data in einer Zeile ContactsContract.CommonDataKinds.StructuredName gespeichert. Ein Rohkontakt hat nur eine Zeile dieses Typs in der Tabelle ContactsContract.Data.
  • Achtung:Wenn Sie Ihre eigenen Kontodaten in einer Rohkontaktdatenzeile verwenden möchten, müssen diese zuerst bei AccountManager registriert werden. Fordern Sie Nutzer dazu auf, den Kontotyp und den Kontonamen der Liste der Konten hinzuzufügen. Wenn Sie das nicht tun, wird die Rohkontaktdatenzeile automatisch vom Kontakteanbieter gelöscht.

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

Quellen für Rohkontaktdaten

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

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

Dieser Nutzer hat in den Kontoeinstellungen die Option Kontakte synchronisieren für alle drei Konten 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 in Gmail als emilyd@gmail.com an und sendet eine E‑Mail an „Thomas Higginson“, wodurch er automatisch als Kontakt hinzugefügt wird. Sie folgt auch „colonel_tom“ (Twitter-ID von Thomas Higginson) auf Twitter.

Der Kontakteanbieter erstellt dadurch 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 wie E-Mail-Adressen oder Telefonnummern haben. Wenn beispielsweise „Thomas Higginson“ für emilyd@gmail.com (die Rohkontaktdatenzeile 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 Kontakteanbieter die beiden E-Mail-Adresszeilen und verknüpft sie beide mit dem Rohkontakt.

Beachten Sie, dass in dieser einzelnen Tabelle verschiedene Datentypen gespeichert sind. Die Zeilen für Anzeigename, Telefonnummer, E‑Mail-Adresse, Postanschrift, Foto und Website finden Sie 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 beschreibendem Namen hat unabhängig vom Datentyp in der Zeile dieselbe Bedeutung. Der Inhalt einer Spalte mit generischem Namen hat je nach Datentyp unterschiedliche Bedeutungen.

Beschreibende Spaltennamen

Beispiele für aussagekräftige 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 Synchronisierungsadapter verwendet werden, der mit dem Kontakteanbieter funktioniert.
IS_PRIMARY
Wenn dieser Typ von Datenzeile für einen Rohkontakt mehrmals vorkommen kann, wird in der Spalte IS_PRIMARY die Datenzeile mit den primären Daten für den Typ gekennzeichnet. Wenn der Nutzer beispielsweise lange auf eine Telefonnummer für einen Kontakt tippt und Als Standard festlegen auswählt, wird die Spalte IS_PRIMARY in der Zeile ContactsContract.Data, die diese Nummer enthält, auf einen Wert ungleich null gesetzt.

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 Synchronisierungsadaptern verwendet werden sollten. Die generischen Spaltennamenskonstanten funktionieren immer, unabhängig davon, welche Art von Daten die Zeile enthält.

Die Spalte DATA1 ist indexiert. Der Kontakteanbieter verwendet diese Spalte immer für die Daten, die seiner Erwartung nach am häufigsten Ziel einer Abfrage sein 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 (Binary Large Object) wie z. B. Fotominiaturen reserviert.

Typspezifische Spaltennamen

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

Die Klasse ContactsContract.CommonDataKinds.Email definiert beispielsweise typspezifische Spaltennamenskonstanten 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 den E-Mail-Adressen. 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 mit einer Zeile hinzu, die einen der vordefinierten MIME-Typen des Anbieters enthält. Andernfalls können die Daten verloren gehen oder der Anbieter kann nicht mehr richtig funktionieren. 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 Sie einen eigenen benutzerdefinierten MIME-Typ für die Zeile verwenden, können Sie eigene typspezifische Spaltennamen definieren und die Spalten nach Belieben verwenden.

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

Zuordnung von typspezifischen Spaltennamen zu generischen Spaltennamen

Abbildung 2: Typspezifische und generische Spaltennamen.

Typspezifische Spaltennamensklassen

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

Tabelle 2 Typspezifische Spaltennamensklassen

Zuordnungsklasse Datentyp Hinweise
ContactsContract.CommonDataKinds.StructuredName Die Namensdaten für den Rohkontakt, der mit dieser Datenzeile verknüpft 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 Postadresse 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 Rohkontaktdatenzeilen für alle Kontotypen und Kontonamen zu einem Kontakt. So können alle Daten, die ein Nutzer für eine Person erhoben hat, einfacher angezeigt und geändert werden. Der Contacts Provider verwaltet das Erstellen neuer Kontaktzeilen und das Zusammenführen von Rohkontakten mit einer vorhandenen Kontaktzeile. Weder Anwendungen noch Synchronisierungsadapter dürfen Kontakte hinzufügen und einige Spalten in einer Kontaktzeile sind schreibgeschützt.

Hinweis:Wenn Sie versuchen, dem Contacts Provider einen Kontakt mit einem 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 keinem vorhandenen Kontakt entspricht. Der Anbieter geht auch so vor, 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 Synchronisierungsadapter einen neuen Rohkontakt erstellt, der nicht mit einem vorhandenen Kontakt übereinstimmt, wird der neue Rohkontakt mit dem vorhandenen Kontakt zusammengefasst.

Der Contacts Provider verknüpft eine Kontaktzeile mit ihren 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 jeder Zeile mit Rohkontakten zugeordnet ist.

Die Tabelle ContactsContract.Contacts enthält auch die Spalte LOOKUP_KEY, die einen „permanenten“ Link zur Kontaktzeile enthält. Da der Kontakteanbieter Kontakte automatisch verwaltet, kann er den _ID-Wert einer Kontaktzeile als Reaktion auf eine Aggregation oder Synchronisierung ändern. Auch wenn das passiert, verweist der Content-URI CONTENT_LOOKUP_URI in Kombination mit LOOKUP_KEY des Kontakts weiterhin auf die Kontaktzeile. Sie können also LOOKUP_KEY verwenden, um Links zu „Favoriten“-Kontakten usw. beizubehalten. Diese Spalte hat ein eigenes Format, das nicht mit dem Format der Spalte _ID zusammenhängt.

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

Haupttabellen des Contacts Provider

Abbildung 3: Beziehungen zwischen den Tabellen „Kontakte“, „Rohkontakte“ und „Details“.

Achtung : Wenn Sie Ihre App im Google Play Store veröffentlichen oder wenn Ihre App auf einem Gerät mit Android 10 (API-Level 29) oder höher ausgeführt wird, sind bestimmte Datenfelder und Methoden für Kontakte veraltet.

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

Auch die APIs, mit denen die oben genannten Datenfelder festgelegt werden, sind veraltet:

Außerdem werden in den folgenden Feldern keine häufigen Kontakte mehr zurückgegeben. Hinweis: Einige dieser Felder wirken sich nur dann auf das Ranking von Kontakten aus, wenn die Kontakte zu einer bestimmten Art von Daten gehören.

Wenn Ihre Apps auf diese Felder oder APIs zugreifen oder sie aktualisieren, verwenden Sie alternative Methoden. So können Sie beispielsweise bestimmte Anwendungsfälle mit privaten Contentanbietern oder anderen Daten abdecken, die in Ihrer App oder in Backend-Systemen gespeichert sind.

Sie können diese Datenfelder manuell löschen, um zu prüfen, ob die Funktionalität Ihrer App durch diese Änderung beeinträchtigt wird. Führen Sie dazu den folgenden ADB-Befehl auf einem Gerät mit Android 4.1 (API-Ebene 16) oder höher aus:

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

Daten aus Synchronisierungsadaptern

Nutzer geben Kontaktdaten direkt auf dem Gerät ein. Daten werden aber auch über Synchronisierungsadapter aus Webdiensten in den Kontakte-Provider übertragen. Diese Adapter automatisieren die Übertragung von Daten zwischen dem Gerät und den Diensten. Synchronisierungsadapter 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 Synchronisierungsadapter zusammenarbeitet, durch einen Kontotyp identifiziert. Jeder Synchronisierungsadapter 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 bieten weitere Details und beschreiben, wie sich Kontotyp und Name auf Synchronisierungsadapter und Dienste beziehen.

Kontotyp
Gibt einen Dienst an, 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
Kennzeichnet ein bestimmtes Konto oder eine bestimmte Anmeldung für einen Kontotyp. Google-Kontakte-Konten sind dasselbe wie Google-Konten, die eine E‑Mail-Adresse als Kontonamen haben. Andere Dienste verwenden möglicherweise einen Nutzernamen mit nur einem Wort oder eine numerische ID.

Kontotypen müssen nicht eindeutig sein. Ein Nutzer kann mehrere Google-Konten für Kontakte konfigurieren und seine Daten in den Kontakteanbieter herunterladen. Das kann passieren, wenn der Nutzer einen Satz persönlicher Kontakte für einen privaten Kontonamen und einen anderen Satz für die Arbeit hat. Kontonamen sind in der Regel eindeutig. Zusammen identifizieren sie einen bestimmten Datenfluss zwischen dem Kontakteanbieter und einem externen Dienst.

Wenn Sie die Daten Ihres Dienstes an den Kontakteanbieter übertragen möchten, müssen Sie einen eigenen Synchronisierungsadapter schreiben. Dies wird im Abschnitt Synchronisierungsadapter für den Kontakteanbieter ausführlicher beschrieben.

Abbildung 4 zeigt, wie der Contacts Provider in den Datenfluss von Informationen über Personen eingebunden ist. Im Feld „Synchronisierungsadapter“ ist jeder Adapter mit seinem Kontotyp gekennzeichnet.

Fluss von Daten über Personen

Abbildung 4: Der Datenfluss des Contacts Provider.

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 Daten des Nutzerprofils. Das Nutzerprofil und die erforderlichen Berechtigungen werden im folgenden Abschnitt Das Nutzerprofil beschrieben.

Die Kontaktdaten des Nutzers sind personenbezogene und vertrauliche Daten. Nutzer machen sich Sorgen um ihre Privatsphäre 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 App möglicherweise schlechte Bewertungen oder weigern sich einfach, sie zu installieren.

Das Nutzerprofil

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

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

Denken Sie daran, dass das Profil eines Nutzers als vertraulich gilt. Mit der Berechtigung android.Manifest.permission#READ_PROFILE können Sie auf die personenbezogenen Daten des Gerätenutzers zugreifen. Erklären Sie dem Nutzer in der Beschreibung Ihrer Anwendung, warum Sie Berechtigungen für den Zugriff auf das Nutzerprofil benötigen.

Rufen Sie ContentResolver.query() auf, um die Kontaktzeile mit dem Profil des Nutzers abzurufen. Legen Sie den Content-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 Sie mehrere Kontaktzeilen abrufen und feststellen möchten, ob eine davon das Nutzerprofil ist, testen Sie die Spalte IS_USER_PROFILE der Zeile. In dieser Spalte wird „1“ angezeigt, wenn der Kontakt das Nutzerprofil ist.

Metadaten des Contacts Provider

Der Contacts Provider verwaltet Daten, mit denen der Status von Kontaktdaten im Repository verfolgt wird. Diese Metadaten zum Repository werden an verschiedenen Stellen gespeichert, z. B. in den Tabellenzeilen „Raw Contacts“, „Data“ und „Contacts“, in der Tabelle ContactsContract.Settings und in der Tabelle ContactsContract.SyncState. In der folgenden Tabelle sehen Sie die Auswirkungen der einzelnen Metadaten:

Tabelle 3 Metadaten im Contacts Provider

Tabelle Spalte Werte Bedeutung
ContactsContract.RawContacts DIRTY „0“ – seit der letzten Synchronisierung nicht geändert. Markiert 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-Anwendungen eine Zeile aktualisieren.

Synchronisierungsadapter, die die Tabellen für Rohkontakte oder Daten ändern, sollten immer den String CALLER_IS_SYNCADAPTER an den verwendeten Inhalts-URI anhängen. So wird verhindert, dass der Anbieter Zeilen als „dirty“ markiert. Andernfalls werden Änderungen am Synchronisierungsadapter als lokale Änderungen betrachtet und an den Server gesendet, obwohl der Server die Quelle der Änderung war.

„1“ – seit der letzten Synchronisierung geändert, muss 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 sich die Datenzeile ändert.
ContactsContract.RawContacts SOURCE_ID Ein Stringwert, der diesen Rohkontakt eindeutig für das Konto identifiziert, in dem er erstellt wurde. Wenn ein Synchronisierungsadapter einen neuen Rohkontakt erstellt, sollte diese Spalte auf die eindeutige ID des Servers für den Rohkontakt festgelegt werden. Wenn eine Android-Anwendung einen neuen Rohkontakt erstellt, sollte sie diese Spalte leer lassen. Dadurch wird dem Synchronisierungsadapter 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 sollte bei Synchronisierungen stabil bleiben:

  • Eindeutig: Jeder Rohkontakt für ein Konto muss eine eigene Quell-ID haben. Wenn Sie dies nicht erzwingen, treten Probleme in der Kontakte-App auf. Zwei Rohkontakte für denselben Kontotyp können dieselbe Quell-ID haben. Der Rohkontakt „Thomas Higginson“ für das Konto emily.dickinson@gmail.com darf beispielsweise dieselbe Quell-ID wie der Rohkontakt „Thomas Higginson“ für das Konto emilyd@gmail.com haben.
  • Stabil: Quell-IDs sind ein fester Bestandteil der Daten des Onlinedienstes für den Rohkontakt. Wenn der Nutzer beispielsweise den Kontaktspeicher in den App-Einstellungen löscht und die Synchronisierung wiederholt, sollten die wiederhergestellten Rohkontakte dieselben Quell-IDs wie zuvor haben. Andernfalls funktionieren die Verknüpfungen nicht mehr.
ContactsContract.Groups GROUP_VISIBLE „0“: Kontakte in dieser Gruppe sollten in den Benutzeroberflächen von Android-Anwendungen nicht sichtbar sein. Diese Spalte dient der Kompatibilität mit Servern, auf denen Nutzer Kontakte in bestimmten Gruppen ausblenden können.
„1“: Kontakte in dieser Gruppe dürfen in Anwendungs-UIs sichtbar sein.
ContactsContract.Settings UNGROUPED_VISIBLE „0“: Bei diesem Konto und Kontotyp sind Kontakte, die keiner Gruppe angehören, für Android-Anwendungs-UIs nicht sichtbar. Standardmäßig sind Kontakte unsichtbar, wenn keiner ihrer Rohkontakte zu einer Gruppe gehört. Die Gruppenzugehörigkeit eines Rohkontakts wird durch eine oder mehrere ContactsContract.CommonDataKinds.GroupMembership-Zeilen in der Tabelle ContactsContract.Data angegeben. Wenn Sie dieses Flag in der ContactsContract.Settings-Tabellenzeile für einen Kontotyp und ein Konto festlegen, können Sie erzwingen, dass Kontakte ohne Gruppen sichtbar sind. Dieses Flag kann verwendet werden, um Kontakte von Servern anzuzeigen, auf denen keine Gruppen verwendet werden.
„1“: Für dieses Konto und diesen Kontotyp sind Kontakte, die keiner Gruppe angehören, in der Benutzeroberfläche der Anwendung sichtbar.
ContactsContract.SyncState (alle) In dieser Tabelle werden Metadaten für Ihren Synchronisierungsadapter 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 des Contacts Provider beschrieben. Dabei geht es hauptsächlich um Folgendes:

  • Entitätsanfragen.
  • Batch-Änderung.
  • Abrufen und Ändern mit Intents.
  • Datenintegrität.

Änderungen über einen Synchronisierungsadapter werden auch im Abschnitt Synchronisierungsadapter für den Contacts-Provider ausführlicher behandelt.

Entitäten abfragen

Da die Tabellen des Contacts Provider hierarchisch organisiert sind, ist es oft nützlich, eine Zeile und alle damit verknüpften untergeordneten Zeilen abzurufen. Wenn Sie beispielsweise alle Informationen für eine Person anzeigen möchten, rufen Sie möglicherweise alle ContactsContract.RawContacts-Zeilen für eine einzelne ContactsContract.Contacts-Zeile oder alle ContactsContract.CommonDataKinds.Email-Zeilen für eine einzelne ContactsContract.RawContacts-Zeile ab. Dazu bietet der Contacts Provider entity-Konstrukte an, 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 Abfrage für eine Entität ausführen, geben Sie eine Projektion und Suchkriterien basierend auf den für die Entität verfügbaren Spalten an. Das Ergebnis ist eine Cursor, die eine Zeile für jede abgerufene Zeile der untergeordneten Tabelle enthält. Wenn Sie beispielsweise ContactsContract.Contacts.Entity für einen Kontaktnamen und alle ContactsContract.CommonDataKinds.Email-Zeilen für alle Rohkontakte für diesen Namen abfragen, erhalten Sie ein Cursor mit einer Zeile für jede ContactsContract.CommonDataKinds.Email-Zeile zurück.

Entitäten vereinfachen Abfragen. Mit einer Entität können Sie 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 in der Regel nicht alle Spalten der über- und untergeordneten Tabelle. Wenn Sie versuchen, mit einem Spaltennamen zu arbeiten, der nicht in der Liste der Spaltennamenskonstanten für die Einheit enthalten ist, erhalten Sie eine Exception.

Das folgende Snippet zeigt, wie Sie alle Rohkontaktdatenzeilen 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 Zeile auswählt, wird die ID der Aktivität an die Detailaktivität gesendet. In der Detailaktivität werden mit ContactsContract.Contacts.Entity alle Datenzeilen aus allen Rohkontakten angezeigt, 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 weiterzuverarbeiten.

Batch-Änderung

Wenn möglich, sollten Sie Daten im Contacts Provider im Batchmodus einfügen, aktualisieren und löschen. Erstellen Sie dazu ein ArrayList mit ContentProviderOperation-Objekten und rufen Sie applyBatch() auf. Da der Kontakteanbieter alle Vorgänge in einem applyBatch() in einer einzigen Transaktion ausführt, befinden sich Ihre Änderungen nie in einem inkonsistenten Zustand. Eine Batch-Änderung erleichtert auch das gleichzeitige Einfügen eines Rohkontakts und seiner Detaildaten.

Hinweis:Wenn Sie einen einzelnen Rohkontakt ändern möchten, sollten Sie eine Intent an die Kontakte-App des Geräts senden, anstatt die Änderung in Ihrer App vorzunehmen. Dies wird im Abschnitt Abrufen und Ändern mit Intents genauer beschrieben.

Ertragspunkte

Eine Batch-Änderung mit einer großen Anzahl von Vorgängen kann andere Prozesse blockieren und so zu einer schlechten Nutzererfahrung führen. Damit Sie alle Änderungen, die Sie vornehmen möchten, in möglichst wenigen separaten Listen organisieren und gleichzeitig verhindern können, dass sie das System blockieren, sollten Sie Yield-Punkte für einen oder mehrere Vorgänge festlegen. Ein Yield-Punkt ist ein ContentProviderOperation-Objekt, dessen isYieldAllowed()-Wert auf true festgelegt ist. Wenn der Kontakteanbieter einen Yield-Punkt erreicht, pausiert er seine Arbeit, damit andere Prozesse ausgeführt werden können, und schließt die aktuelle Transaktion. Wenn der Anbieter wieder gestartet wird, wird mit dem nächsten Vorgang in der ArrayList fortgefahren und eine neue Transaktion gestartet.

Durch Yield-Punkte kann es zu mehr als einer Transaktion pro Aufruf von applyBatch() kommen. Aus diesem Grund sollten Sie für die letzte Operation für eine Gruppe zusammengehöriger Zeilen einen Yield-Punkt festlegen. Sie sollten beispielsweise einen Yield-Punkt für den letzten Vorgang in einem Satz festlegen, der Rohkontaktdatenzeilen und die zugehörigen Datenzeilen hinzufügt, oder für den letzten Vorgang für einen Satz von Zeilen, die sich auf einen einzelnen Kontakt beziehen.

Yield-Punkte sind auch eine Einheit für atomare Vorgänge. Alle Zugriffe zwischen zwei Yield-Punkten sind entweder als Einheit erfolgreich oder schlagen als solche fehl. Wenn Sie keine Yield-Punkte festlegen, ist die kleinste atomare Operation der gesamte Batch mit Vorgängen. Wenn Sie Yield-Punkte verwenden, verhindern Sie, dass Vorgänge die Systemleistung beeinträchtigen, und sorgen gleichzeitig dafür, dass eine Teilmenge der Vorgänge atomar ist.

Rückverweise auf Änderungen

Wenn Sie eine neue Rohkontaktdatenzeile und die zugehörigen Datenzeilen als Gruppe von ContentProviderOperation-Objekten einfügen, müssen Sie die Datenzeilen mit der Rohkontaktdatenzeile verknüpfen, indem Sie den _ID-Wert des Rohkontakts als RAW_CONTACT_ID-Wert einfügen. Dieser Wert ist jedoch nicht verfügbar, wenn Sie die ContentProviderOperation für die Datenzeile erstellen, da Sie die ContentProviderOperation für die Rohkontaktezeile noch nicht angewendet haben. Um dieses Problem zu umgehen, hat 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 0-basierte Index eines Werts im Array der ContentProviderResult-Objekte aus applyBatch(). Während die Batchvorgänge angewendet werden, wird das Ergebnis jedes Vorgangs in einem Zwischenergebnis-Array gespeichert. Der previousResult-Wert ist der Index eines dieser Ergebnisse, der abgerufen und mit dem key-Wert gespeichert wird. So können Sie einen neuen Rohkontaktdatensatz einfügen und den zugehörigen _ID-Wert abrufen. Wenn Sie dann eine ContactsContract.Data-Zeile hinzufügen, können Sie auf den Wert zurückverweisen.

Das gesamte Ergebnis-Array wird beim ersten Aufruf von applyBatch() erstellt. Es hat die gleiche Größe wie die ArrayList der bereitgestellten ContentProviderOperation-Objekte. Alle Elemente im Ergebnis-Array sind jedoch auf null gesetzt. Wenn Sie versuchen, einen Rückverweis auf ein Ergebnis für eine Operation zu erstellen, die noch nicht angewendet wurde, löst withValueBackReference() eine Exception aus.

Die folgenden Snippets zeigen, wie Sie einen neuen Rohkontakt und Daten im Batch einfügen. Sie enthalten Code, mit dem ein Yield-Punkt festgelegt und eine Rückreferenz verwendet wird.

Im ersten Snippet werden Kontaktdaten aus der Benutzeroberfläche abgerufen. An diesem Punkt 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 zum Einfügen der Rohkontaktdatenzeile in die Tabelle ContactsContract.RawContacts erstellt:

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 mit dem Code Datenzeilen für die Zeilen für Anzeigename, Telefonnummer und E-Mail-Adresse erstellt.

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

Das ContentProviderOperation.Builder-Objekt, mit dem die E-Mail-Zeile hinzugefügt wird, ist mit withYieldAllowed() gekennzeichnet, wodurch ein Ertragspunkt 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 Parallelitätssteuerung implementieren, eine Methode zum Anwenden von Änderungs-Transaktionen, 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, führen Sie ein Rollback der Transaktion durch und versuchen Sie es noch einmal.

Die optimistische Parallelitätssteuerung ist für ein Mobilgerät nützlich, da es jeweils nur einen Nutzer gibt und gleichzeitige Zugriffe auf ein Daten-Repository selten sind. Da keine Sperren verwendet werden, wird keine Zeit für das Festlegen von Sperren oder das Warten darauf verschwendet, dass andere Transaktionen ihre Sperren aufheben.

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

  1. Rufen Sie die Spalte VERSION des Rohkontakts zusammen mit den anderen abgerufenen Daten ab.
  2. Erstellen Sie mit der Methode newAssertQuery(Uri) ein ContentProviderOperation.Builder-Objekt, das sich zum Erzwingen einer Einschränkung eignet. Verwenden Sie für den Inhalts-URI RawContacts.CONTENT_URI mit der angehängten _ID des Rohkontakts.
  3. Rufen Sie für das ContentProviderOperation.Builder-Objekt 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 mit dieser Assertion nur eine Zeile getestet wird.
  5. Rufen Sie build() auf, um das ContentProviderOperation-Objekt zu erstellen. Fügen Sie dieses Objekt dann als erstes Objekt in das ArrayList ein, das Sie an applyBatch() übergeben.
  6. Wenden Sie die Batch-Transaktion an.

Wenn die Rohkontaktdatenzeile durch einen anderen Vorgang aktualisiert wird, nachdem Sie die Zeile gelesen haben und bevor Sie versuchen, sie zu ändern, schlägt die „assert“-Anweisung ContentProviderOperation fehl und die gesamte Gruppe von Vorgängen wird rückgängig gemacht. Sie können dann versuchen, den Batch noch einmal zu senden, oder eine andere Aktion ausführen.

Das folgende Snippet zeigt, wie Sie eine ContentProviderOperation-„assert“-Anweisung erstellen, nachdem Sie mit einer CursorLoader nach einem einzelnen Rohkontakt gesucht 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
    }

Abrufen und Ändern mit Intents

Wenn Sie einen Intent an die Kontakte-App des Geräts senden, können Sie indirekt auf den Contacts Provider zugreifen. Mit dem Intent wird die Benutzeroberfläche der Kontakte-App des Geräts gestartet, in der Nutzer kontaktebezogene Aufgaben ausführen können. Mit dieser Art von Zugriff können Nutzer:

  • 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
  • Fügen Sie für eines ihrer Konten einen neuen Rohkontakt ein.
  • Kontakte oder Kontaktdaten löschen

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

Wenn Sie Intents verwenden, um über die Kontakte-App des Geräts auf den Contacts Provider zuzugreifen, müssen Sie keine eigene Benutzeroberfläche oder keinen eigenen Code für den Zugriff auf den Provider schreiben. Sie müssen auch keine Berechtigung zum Lesen oder Schreiben für den Anbieter anfordern. Die Kontakte-App des Geräts kann Ihnen die Leseberechtigung für einen Kontakt zuweisen. Da Sie den Anbieter über eine andere App ändern, benötigen Sie keine Schreibberechtigungen.

Der allgemeine Prozess zum Senden einer Absicht für den Zugriff auf einen Anbieter wird im Leitfaden Grundlagen von 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 zusätzlichen Werte, die Sie mit putExtra() verwenden können, sind in der Referenzdokumentation für ContactsContract.Intents.Insert aufgeführt:

Tabelle 4. Intents für Contacts Provider.

Aufgabe Aktion Daten MIME-Typ Hinweise
Einen Kontakt aus einer Liste auswählen ACTION_PICK Eine der folgenden Möglichkeiten: Nicht verwendet Zeigt eine Liste von Rohkontakten oder eine Liste von Daten aus einem Rohkontakt an, je nach dem von Ihnen angegebenen Content-URI-Typ.

Rufen Sie startActivityForResult() auf, um den Inhalts-URI der ausgewählten Zeile zurückzugeben. Der URI ist der Inhalts-URI der Tabelle mit der angehängten LOOKUP_ID der Zeile. Die Kontakte-App des Geräts delegiert Lese- und Schreibberechtigungen für diesen Inhalts-URI für die gesamte Dauer Ihrer Aktivität. Weitere Informationen finden Sie im Leitfaden Grundlagen für Content-Anbieter.

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 Extras-Werte, die Sie dem Intent hinzufügen, werden angezeigt. Wenn die Anfrage mit startActivityForResult() gesendet wird, wird der Content-URI des neu hinzugefügten Rohkontakts im Argument Intent im Feld „data“ an die Callback-Methode onActivityResult() Ihrer Aktivität zurückgegeben. Rufen Sie getData() auf, um den Wert abzurufen.
Kontakt bearbeiten ACTION_EDIT CONTENT_LOOKUP_URI für den Kontakt. Durch die Bearbeitungsaktivitä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 Sie dem Intent hinzufügen, werden angezeigt. Wenn der Nutzer auf Fertig klickt, um die Änderungen zu speichern, wird Ihre Aktivität wieder in den Vordergrund gerückt.
Eine Auswahl anzeigen, über die auch Daten hinzugefügt werden können. ACTION_INSERT_OR_EDIT CONTENT_ITEM_TYPE Bei dieser Absicht wird immer die Auswahlseite der Kontakte-App angezeigt. 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ügungsbildschirm angezeigt. Die Extras-Daten, die Sie im Intent übergeben, werden angezeigt. Wenn in Ihrer App Kontaktdaten wie eine E‑Mail-Adresse oder Telefonnummer angezeigt werden, verwenden Sie diesen Intent, damit der Nutzer die Daten einem vorhandenen Kontakt hinzufügen kann. Kontakt

Hinweis:Es ist nicht erforderlich, einen Namen in den Extras dieses Intents zu senden, da der Nutzer immer einen vorhandenen Namen auswählt oder einen neuen hinzufügt. Wenn Sie einen Namen senden und der Nutzer ihn bearbeitet, wird in der Kontakte App der von Ihnen 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 können Sie keinen Rohkontakt oder seine Daten mit einem Intent löschen. Verwenden Sie stattdessen ContentResolver.delete() oder ContentProviderOperation.newDelete(), um einen Rohkontakt zu löschen.

Das folgende Snippet zeigt, wie ein Intent erstellt und gesendet wird, mit dem ein neuer Rohkontakt und neue Daten eingefügt werden:

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 Kontakt-Repository wichtige und sensible Daten enthält, von denen Nutzer erwarten, dass sie korrekt und aktuell sind, hat der Contacts Provider genau 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 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 zugehörigen übergeordneten ContactsContract.RawContacts-Zeile.
Eine ContactsContract.Data-Zeile, die nicht mit einem ContactsContract.RawContacts verknüpft ist, ist in der Kontakte-App des Geräts nicht sichtbar und kann Probleme mit Synchronisierungsadaptern verursachen.
Ändern Sie Daten nur für die Rohkontakte, deren Inhaber Sie sind.
Der Kontakteanbieter verwaltet in der Regel Daten aus verschiedenen Kontotypen/Onlinediensten. Ihre Anwendung darf nur Daten für Zeilen ändern oder löschen, die Ihnen gehören, und nur Daten mit einem Kontotyp und Namen einfügen, die Sie kontrollieren.
Verwenden Sie immer die in ContactsContract und seinen Unterklassen definierten Konstanten für Authorities, Content-URIs, URI-Pfade, Spaltennamen, MIME-Typen und TYPE-Werte.
Die Verwendung dieser Konstanten hilft Ihnen, Fehler zu vermeiden. Sie werden auch mit Compilerwarnungen benachrichtigt, wenn eine der Konstanten veraltet ist.

Benutzerdefinierte Datenzeilen

Wenn Sie eigene benutzerdefinierte MIME-Typen erstellen und verwenden, können Sie Ihre eigenen Datenzeilen in die Tabelle ContactsContract.Data einfügen, bearbeiten, löschen und abrufen. Ihre Zeilen sind auf die Verwendung der in ContactsContract.DataColumns definierten 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 keine zusätzlichen Daten hinzufügen. Wenn Nutzer Ihre benutzerdefinierten Datenzeilen ändern dürfen, müssen Sie in Ihrer eigenen Anwendung eine Editoraktivität bereitstellen.

Damit Ihre benutzerdefinierten Daten angezeigt werden, müssen Sie eine contacts.xml-Datei mit einem <ContactsAccountType>-Element und einem oder mehreren seiner untergeordneten <ContactsDataKind>-Elemente bereitstellen. Dies wird im Abschnitt <ContactsDataKind> element ausführlicher beschrieben.

Weitere Informationen zu benutzerdefinierten MIME-Typen finden Sie im Leitfaden Content-Provider erstellen.

Synchronisierungsadapter für Contacts Provider

Der Contacts Provider wurde speziell für die Synchronisierung von Kontaktdaten zwischen einem Gerät und einem Onlinedienst entwickelt. So können Nutzer vorhandene Daten auf ein neues Gerät herunterladen und in ein neues Konto hochladen. Durch die Synchronisierung haben Nutzer außerdem immer die neuesten Daten zur Hand, unabhängig davon, wo Ergänzungen und Änderungen vorgenommen wurden. Ein weiterer Vorteil der Synchronisierung ist, dass Kontaktdaten auch dann verfügbar sind, wenn das Gerät nicht mit dem Netzwerk verbunden ist.

Sie können die Synchronisierung auf verschiedene Arten implementieren. Das Android-System bietet jedoch ein Plug-in-Synchronisierungsframework, das die folgenden Aufgaben automatisiert:

  • Netzwerkverfügbarkeit wird geprüft.
  • Synchronisierung planen und ausführen, basierend auf den Nutzereinstellungen.
  • Synchronisierungen neu starten, die beendet wurden.

Um dieses Framework zu verwenden, stellen Sie ein Synchronisierungsadapter-Plug-in bereit. Jeder Synchronisierungsadapter ist für einen Dienst und einen Content-Anbieter eindeutig, kann aber mehrere Kontonamen für denselben Dienst verarbeiten. Das Framework unterstützt auch mehrere Synchronisierungsadapter für denselben Dienst und Anbieter.

Sync Adapter-Klassen und -Dateien

Sie implementieren einen Synchronisierungsadapter als Unterklasse von AbstractThreadedSyncAdapter und installieren ihn als Teil einer Android-Anwendung. Das System erfährt vom Synchronisierungsadapter durch Elemente im Anwendungsmanifest und durch eine spezielle XML-Datei, auf die das Manifest verweist. Die XML-Datei definiert den Kontotyp für den Onlinedienst und die Berechtigung für den Content-Anbieter. Zusammen identifizieren diese den Adapter eindeutig. Der Synchronisierungsadapter wird erst aktiv, wenn der Nutzer ein Konto für den Kontotyp des Synchronisierungsadapters hinzufügt und die Synchronisierung für den Contentanbieter aktiviert, mit dem der Synchronisierungsadapter synchronisiert wird. Ab diesem Zeitpunkt verwaltet das System den Adapter und ruft ihn bei Bedarf auf, um den Contentanbieter und den Server zu synchronisieren.

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

Da die meisten Dienste erfordern, dass Nutzer ihre Identität bestätigen, bevor sie auf Daten zugreifen können, bietet das Android-System ein Authentifizierungs-Framework, das dem Sync-Adapter-Framework ähnelt und häufig in Verbindung damit verwendet wird. Das Authentifizierungs-Framework verwendet Plug-in-Authentifizierer, die Unterklassen von AbstractAccountAuthenticator sind. Ein Authentifikator überprüft die Identität des Nutzers in den folgenden Schritten:

  1. Erhebung des Namens, des Passworts oder ähnlicher Informationen des Nutzers (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 sie für die spätere Verwendung speichern. Aufgrund des Plug-in-Authentifizierungs-Frameworks kann AccountManager Zugriff auf alle Authtokens gewähren, die von einem Authentifikator unterstützt und bereitgestellt werden, z. B. OAuth2-Authtokens.

Obwohl die Authentifizierung nicht erforderlich ist, wird sie von den meisten Kontaktdiensten verwendet. Sie müssen jedoch nicht das Android-Authentifizierungs-Framework für die Authentifizierung verwenden.

Synchronisierungsadapter implementieren

Wenn Sie einen Synchronisierungsadapter für den Kontakteanbieter implementieren möchten, erstellen Sie zuerst eine Android-Anwendung, die Folgendes enthält:

Eine Service-Komponente, die auf Anfragen des Systems reagiert, um an den Synchronisierungsadapter gebunden zu werden.
Wenn das System eine Synchronisierung ausführen möchte, ruft es die onBind()-Methode des Dienstes auf, um ein IBinder für den Synchronisierungsadapter abzurufen. So kann das System prozessübergreifende Aufrufe der Methoden des Adapters ausführen.
Der eigentliche Synchronisierungsadapter, der als konkrete Unterklasse von AbstractThreadedSyncAdapter implementiert ist.
Diese Klasse lädt Daten vom Server herunter, lädt Daten vom Gerät hoch und löst Konflikte. 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 Singleton des Synchronisierungsadapters. Verwenden Sie die Methode onCreate(), um den Synchronisierungsadapter zu instanziieren, und stellen Sie eine statische „Getter“-Methode bereit, um das Singleton an die Methode onBind() des Dienstes des Synchronisierungsadapters zurückzugeben.
Optional:Eine Service-Komponente, die auf Anfragen des Systems zur Nutzerauthentifizierung reagiert.
AccountManager startet diesen Dienst, um den Authentifizierungsvorgang zu starten. Mit der onCreate()-Methode des Dienstes wird ein Authentifizierungsobjekt instanziiert. Wenn das System ein Nutzerkonto für den Synchronisierungsadapter der Anwendung authentifizieren möchte, ruft es die onBind()-Methode des Dienstes auf, um ein IBinder für den Authentifikator abzurufen. Dadurch kann das System prozessübergreifende Aufrufe der Methoden des Authentifikators ausführen.
Optional:Eine konkrete Unterklasse von AbstractAccountAuthenticator, die Anfragen zur Authentifizierung verarbeitet.
Diese Klasse stellt Methoden bereit, die von AccountManager aufgerufen werden, um die Anmeldedaten des Nutzers beim Server zu authentifizieren. Die Details des Authentifizierungsprozesses variieren je nach verwendeter Servertechnologie. Weitere Informationen zur Authentifizierung finden Sie in der Dokumentation Ihrer Serversoftware.
XML-Dateien, die den Synchronisierungsadapter und den Authentifikator für das System definieren.
Die zuvor beschriebenen Komponenten für den Synchronisierungsadapter und den Authentifizierungsdienst werden in <service>-Elementen im Anwendungsmanifest definiert. Diese Elemente enthalten untergeordnete <meta-data>-Elemente, die dem System bestimmte Daten zur Verfügung stellen:
  • Das Element <meta-data> für den Synchronisierungsadapterdienst verweist auf die XML-Datei res/xml/syncadapter.xml. Diese Datei gibt wiederum einen URI für den Webdienst an, der mit dem Contacts Provider synchronisiert wird, sowie einen Kontotyp für den Webdienst.
  • Optional:Das Element <meta-data> für den Authentifikator verweist auf die XML-Datei res/xml/authenticator.xml. Diese Datei gibt wiederum den Kontotyp an, 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 Synchronisierungsadapter angegebenen Kontotyp übereinstimmen.

Daten aus sozialen Netzwerken

In den Tabellen android.provider.ContactsContract.StreamItems und android.provider.ContactsContract.StreamItemPhotos werden eingehende Daten aus sozialen Netzwerken verwaltet. Sie können einen Synchronisierungsadapter schreiben, der diesen Tabellen Streamdaten aus Ihrem eigenen Netzwerk hinzufügt. Sie können aber auch Streamdaten aus diesen Tabellen lesen und in Ihrer eigenen Anwendung anzeigen oder beides tun. Mit diesen Funktionen können Ihre sozialen Netzwerke und Anwendungen in die sozialen Netzwerke von Android integriert werden.

Text für Social-Media-Stream

Stream-Elemente sind immer mit einem Rohkontakt verknüpft. Der Wert android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID verweist auf den _ID-Wert für den Rohkontakt. Der Kontotyp und der Kontoname des ursprünglichen Kontakts werden ebenfalls in der Stream-Elementzeile 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 Streamelement 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 Streamelement einfügen.
ID-Spalten
Erforderlich. Sie müssen die folgenden Kennungsspalten einfügen, wenn Sie ein Streamelement einfügen:
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: Der Wert android.provider.BaseColumns#_ID des Kontakts, dem dieses Streamelement zugeordnet ist.
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: Der Wert android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY des Kontakts, dem dieses Stream-Element zugeordnet ist.
  • android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: Der Wert android.provider.BaseColumns#_ID des Rohkontakts, dem dieses Streamelement zugeordnet ist.
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
Optional. Hier werden zusammenfassende Informationen gespeichert, die am Anfang eines Stream-Elements angezeigt werden 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, durch die das Stream-Element generiert wurde. Diese Spalte kann beliebige Formatierungen und eingebettete Ressourcenbilder enthalten, die von fromHtml() gerendert werden können. Der Anbieter kann lange Inhalte kürzen oder auslassen, versucht aber, Tags nicht zu unterbrechen.
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
Ein Textstring mit der Zeit, 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 Verwaltung dieser Spalte verantwortlich. Sie wird nicht automatisch vom Contacts Provider verwaltet.

Verwenden Sie android.provider.ContactsContract.StreamItemsColumns#RES_ICON, android.provider.ContactsContract.StreamItemsColumns#RES_LABEL und android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE, um identifizierende Informationen für Ihre Stream-Elemente anzuzeigen und Ressourcen in Ihrer Anwendung zu verknüpfen.

Die Tabelle „android.provider.ContactsContract.StreamItems“ enthält auch die Spalten „android.provider.ContactsContract.StreamItemsColumns#SYNC1“ bis „android.provider.ContactsContract.StreamItemsColumns#SYNC4“ zur ausschließlichen Verwendung durch Synchronisierungsadapter.

Fotos aus sozialen Netzwerken

In der Tabelle „android.provider.ContactsContract.StreamItemPhotos“ werden Fotos gespeichert, die mit einem Streamelement verknüpft sind. Die Spalte android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID der Tabelle verweist auf Werte in der Spalte _ID der Tabelle android.provider.ContactsContract.StreamItems. Fotoreferenzen werden in der Tabelle in diesen Spalten gespeichert:

Spalte „android.provider.ContactsContract.StreamItemPhotos#PHOTO“ (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 zur 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. Verwenden Sie 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ängen Sie diesen Wert an die Konstante DisplayPhoto.CONTENT_URI an, um einen Inhalts-URI zu erhalten, der auf eine einzelne Fotodatei verweist, und rufen Sie 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. Wenn Ihre Anwendung Daten daraus lesen soll, muss sie die Berechtigung android.Manifest.permission#READ_SOCIAL_STREAM haben. Zum Ändern der Streams muss Ihre Anwendung die Berechtigung android.Manifest.permission#WRITE_SOCIAL_STREAM haben.
  • Für die 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 Kontakteanbieter 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, senden Sie eine Anfrage an den Inhalts-URI android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI. Alle Argumente außer dem Inhalts-URI können auf null gesetzt bleiben. Die Abfrage gibt einen Cursor mit einer einzelnen Zeile zurück, die die einzelne Spalte android.provider.ContactsContract.StreamItems#MAX_ITEMS enthält.

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 Daten des sozialen Streams bieten in Verbindung mit der Kontakte-App des Geräts eine leistungsstarke Möglichkeit, Ihr soziales Netzwerk mit vorhandenen Kontakten zu verknüpfen. Die folgenden Funktionen sind verfügbar:

  • Wenn Sie Ihren Dienst für soziale Netzwerke mit einem Synchronisierungsadapter mit dem Kontakteanbieter synchronisieren, können Sie 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 regelmäßigen Synchronisierung können Sie Ihren Synchronisierungsadapter so konfigurieren, dass er zusätzliche Daten abruft, wenn der Nutzer einen Kontakt auswählt, um ihn anzusehen. So kann der Synchronisierungsadapter hochauflösende Fotos und die neuesten Stream-Elemente für den Kontakt abrufen.
  • Wenn Sie eine Benachrichtigung bei der Kontakte-App des Geräts und beim Kontakteanbieter registrieren, können Sie einen Intent empfangen, wenn ein Kontakt aufgerufen wird, und den Status des Kontakts dann über Ihren Dienst aktualisieren. Diese Methode ist möglicherweise schneller und verbraucht weniger Bandbreite als eine vollständige Synchronisierung mit einem Synchronisierungsadapter.
  • Nutzer können einen Kontakt zu Ihrem sozialen Netzwerk hinzufügen, während sie sich den Kontakt in der Kontakte-App des Geräts ansehen. Sie aktivieren diese Funktion mit der Funktion „Kontakt einladen“, die Sie mit einer Kombination aus einer Aktivität, die einen vorhandenen Kontakt zu Ihrem Netzwerk hinzufügt, und einer XML-Datei aktivieren, die der Kontakte-App des Geräts und dem Contacts Provider die Details Ihrer Anwendung zur Verfügung stellt.

Die regelmäßige Synchronisierung von Stream-Elementen mit dem Contacts Provider entspricht anderen Synchronisierungen. Weitere Informationen zur Synchronisierung finden Sie im Abschnitt Synchronisierungsadapter für den Contacts Provider. Das Registrieren von Benachrichtigungen und das Einladen von Kontakten werden in den nächsten beiden Abschnitten behandelt.

Registrieren, um Aufrufe in sozialen Netzwerken zu erfassen

So registrieren Sie Ihren Synchronisierungsadapter, damit Sie Benachrichtigungen erhalten, wenn der Nutzer einen Kontakt aufruft, der von Ihrem Synchronisierungsadapter verwaltet wird:

  1. Erstellen Sie im Verzeichnis res/xml/ Ihres Projekts eine Datei mit dem Namen contacts.xml. 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, wobei serviceclass der vollständig qualifizierte Klassenname des Dienstes ist, 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 können Sie eine Verbindung zu Ihrem Synchronisierungsadapter herstellen und ihn aufrufen, um die Daten für den Rohkontakt zu aktualisieren.

So registrieren Sie eine Aktivität, die aufgerufen werden soll, wenn der Nutzer auf ein Streamelement oder ein Foto oder beides klickt:

  1. Erstellen Sie im Verzeichnis res/xml/ Ihres Projekts eine Datei mit dem Namen contacts.xml. 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 eine Ihrer Aktivitäten registrieren möchten, um zu verarbeiten, wenn der Nutzer in der Kontakte-App des Geräts auf ein Stream-Element klickt, fügen Sie dem Element das Attribut viewStreamItemActivity="activityclass" hinzu, wobei activityclass der vollqualifizierte Klassenname der Aktivität ist, die den Intent aus der Kontakte-App des Geräts empfangen soll.
  4. Wenn Sie eine Ihrer Aktivitäten registrieren möchten, um zu verarbeiten, wenn der Nutzer in der Kontakte-App des Geräts auf ein Streamfoto klickt, fügen Sie dem Element das Attribut viewStreamItemPhotoActivity="activityclass" hinzu, wobei activityclass der vollständig qualifizierte Klassenname der Aktivität ist, die den Intent von der Kontakte-App des Geräts empfangen soll.

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

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

Interaktion mit Ihrem sozialen Netzwerkdienst

Nutzer müssen die Kontakte-App des Geräts nicht verlassen, um einen Kontakt zu Ihrer Social-Networking-Website einzuladen. Stattdessen können Sie die Kontakte-App des Geräts einen Intent zum Einladen des Kontakts zu einer Ihrer Aktivitäten senden lassen. So richtest du ein Konto mit Elternaufsicht ein:

  1. Erstellen Sie im Verzeichnis res/xml/ Ihres Projekts eine Datei mit dem Namen contacts.xml. 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 activityclass-Wert ist der vollständig qualifizierte Klassenname der Aktivität, die den Intent empfangen soll. Der invite_action_label-Wert ist ein Textstring, der im Menü Verbindung hinzufügen in der Kontakte-App des Geräts angezeigt wird.

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

Referenz zu contacts.xml

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

<ContactsAccountType>-Element

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

<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 und benachrichtigt werden können, wenn einer ihrer Social-Networking-Streams aktualisiert wird.

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

Attribute:

inviteContactActivity
Der vollqualifizierte Klassenname der Aktivität in Ihrer Anwendung, 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 „Follow in my network“ (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 App, datenintensive Vorgänge zu verschieben, bis sie benötigt werden. Ihre Anwendung kann beispielsweise auf diese Benachrichtigung reagieren, indem sie das hochauflösende Foto des Kontakts und die letzten Social-Stream-Elemente liest und anzeigt. Diese Funktion wird im Abschnitt Interaktionen im sozialen Stream ausführlicher beschrieben.
viewGroupActivity
Der vollständig qualifizierte Klassenname einer Aktivität in Ihrer Anwendung, in der Gruppeninformationen angezeigt werden können. 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 in der Kontakte-App für ein UI-Steuerelement angezeigt wird, mit dem der Nutzer Gruppen in Ihrer App aufrufen kann.

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

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

<ContactsDataKind>-Element

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

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

Enthalten in:

<ContactsAccountType>

Beschreibung:

Mit diesem Element können Sie die Inhalte einer benutzerdefinierten Datenzeile in der Kontakte App als Teil der Details eines Rohkontakts anzeigen lassen. Jedes untergeordnete Element <ContactsDataKind> von <ContactsAccountType> stellt einen Typ von benutzerdefinierter Datenzeile dar, die Ihr Synchronisierungsadapter 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 Android-Drawable-Ressource, die von der Kontakte App neben Ihren Daten angezeigt wird. Damit wird dem Nutzer angezeigt, dass die Daten aus Ihrem 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 von zwei Werten, 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 Provider

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 verwandter Kontakte optional mit group-Daten kennzeichnen. Wenn der mit einem Nutzerkonto verknüpfte Server Gruppen verwalten möchte, sollte der Synchronisierungsadapter für den Kontotyp des Kontos Gruppendaten zwischen dem Kontakteanbieter und dem Server übertragen. Wenn Nutzer dem Server einen neuen Kontakt hinzufügen und diesen Kontakt dann in eine neue Gruppe einfügen, muss der Synchronisierungsadapter die neue Gruppe der Tabelle ContactsContract.Groups hinzufügen. Die Gruppe(n), zu der ein Rohkontakt gehört, werden in der Tabelle ContactsContract.Data mit dem MIME-Typ ContactsContract.CommonDataKinds.GroupMembership gespeichert.

Wenn Sie einen Synchronisierungsadapter entwickeln, der Rohkontaktdaten vom Server zum Contacts Provider hinzufügt, und Sie keine Gruppen verwenden, müssen Sie dem Provider mitteilen, dass Ihre Daten sichtbar sein sollen. Aktualisieren Sie im Code, der ausgeführt wird, wenn ein Nutzer dem Gerät ein Konto hinzufügt, die ContactsContract.Settings-Zeile, die der Kontakteanbieter 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 Ihre Kontaktdaten immer sichtbar, auch wenn Sie keine Gruppen verwenden.

Kontaktfotos

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 zugehörigen Rohkontakts verknüpft. Die Klasse ContactsContract.Contacts.Photo definiert eine Untertabelle von ContactsContract.Contacts, die Fotoinformationen für das primäre Foto eines Kontakts enthält. Das primäre Foto des Kontakts ist das primäre Foto des primären Rohkontakts des Kontakts. Die Klasse ContactsContract.RawContacts.DisplayPhoto definiert 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 für das Abrufen von Fotoinformationen. Es gibt keine Convenience-Klasse zum Abrufen des primären Thumbnails für einen Rohkontakt. Sie können jedoch eine Anfrage an die Tabelle ContactsContract.Data senden und dabei die Spalten _ID, Photo.CONTENT_ITEM_TYPE und IS_PRIMARY des Rohkontakts auswählen, um die primäre Fotorow des Rohkontakts zu finden.

Social-Stream-Daten für eine Person können auch Fotos enthalten. Sie werden in der Tabelle android.provider.ContactsContract.StreamItemPhotos gespeichert, die im Abschnitt Fotos im sozialen Stream ausführlicher beschrieben wird.