Contacts Provider

Der Contacts Provider ist eine leistungsstarke und flexible Android-Komponente, die das zentrale Repository des Geräts mit Daten über Personen verwaltet. Der Contacts Provider ist die Datenquelle, die Sie in der Kontakte-App des Geräts sehen. Sie können auch in Ihrer eigenen Anwendung auf die Daten des Geräts zugreifen und Daten zwischen dem Gerät und Onlinediensten übertragen. Der Anbieter berücksichtigt ein breites Spektrum an Datenquellen und versucht, für jede Person so viele Daten wie möglich zu verwalten, wodurch seine Organisation komplex ist. Aus diesem Grund umfasst die API des Anbieters eine Vielzahl von Vertragsklassen und Schnittstellen, die das Abrufen und Ändern von Daten erleichtern.

In diesem Leitfaden wird Folgendes beschrieben:

  • Die grundlegende Anbieterstruktur.
  • Daten vom Anbieter abrufen
  • Daten beim Anbieter ändern
  • Hier erfahren Sie, wie Sie einen Synchronisierungsadapter schreiben, um Daten von Ihrem Server mit dem Contacts Provider zu synchronisieren.

In diesem Leitfaden wird davon ausgegangen, dass Sie mit den Grundlagen von Android-Contentanbietern vertraut sind. Weitere Informationen zu Android-Inhaltsanbietern finden Sie im Leitfaden Grundlagen von Contentanbietern.

Organisation des Kontaktanbieters

Der Contacts Provider ist eine Komponente des Android-Inhaltsanbieters. Dabei werden drei Arten von Daten zu einer Person verwaltet, von denen jedes einer vom Anbieter angebotenen Tabelle entspricht (siehe Abbildung 1):

Abbildung 1: Tabellenstruktur des Contacts Providers.

Auf die drei Tabellen werden im Allgemeinen die Namen der jeweiligen Vertragsklassen bezeichnet. Die Klassen definieren Konstanten für Inhalts-URIs, Spaltennamen und Spaltenwerte, die von den Tabellen verwendet werden:

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

Die anderen durch Vertragsklassen in ContactsContract dargestellten Tabellen sind Hilfstabellen, mit denen der Kontakteanbieter seine Vorgänge verwaltet oder bestimmte Funktionen in den Kontakten oder Telefonieanwendungen des Geräts unterstützt.

Rohkontakte

Ein Rohkontakt stellt die Daten einer Person dar, die aus einem einzigen Kontotyp und Kontonamen stammen. Da Contact Provider mehr als einen Onlinedienst als Datenquelle für eine Person zulässt, lässt er mehrere Rohkontakte für dieselbe Person zu. Mit mehreren Rohkontakten kann ein Nutzer außerdem 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 die Spalte Data.RAW_CONTACT_ID, die den RawContacts._ID-Wert der übergeordneten ContactsContract.RawContacts-Zeile enthält.

Wichtige Rohkontaktspalten

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

Tabelle 1 Wichtige Rohkontaktspalten.

Spaltenname Verwenden Hinweise
ACCOUNT_NAME Der Kontoname des Kontotyps, der die Quelle dieses unbearbeiteten Kontakts ist. Beispielsweise ist der Kontoname eines Google-Kontos eine der Gmail-Adressen des Geräteeigentümers. Weitere Informationen finden Sie im nächsten Eintrag für ACCOUNT_TYPE. Das Format dieses Namens hängt vom Kontotyp ab. Dies ist nicht unbedingt eine E-Mail-Adresse.
ACCOUNT_TYPE Der Kontotyp, aus dem dieser unbearbeitete Kontakt stammt. Der Kontotyp eines Google-Kontos ist beispielsweise com.google. Kennzeichnen Sie Ihren Kontotyp immer mit einer Domain-ID für eine Domain, die Ihnen gehört oder die Sie verwalten. Dadurch wird sichergestellt, dass Ihr Kontotyp eindeutig ist. Ein Kontotyp, der Kontaktdaten bereitstellt, ist normalerweise mit einem Synchronisierungsadapter verknüpft, der mit dem Kontakteanbieter synchronisiert wird.
DELETED Die Markierung „gelöscht“ für einen Rohkontakt. Mit diesem Flag kann der Contacts Provider die Zeile intern verwalten, bis die Synchronisierungsadapter die Zeile von ihren Servern löschen und sie schließlich aus dem Repository löschen können.

Hinweise

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

  • Der Name eines Rohkontakts wird nicht in seiner Zeile in ContactsContract.RawContacts gespeichert. Stattdessen werden sie in der Tabelle ContactsContract.Data in einer ContactsContract.CommonDataKinds.StructuredName-Zeile gespeichert. Ein Rohkontakt hat nur eine Zeile dieses Typs in der Tabelle ContactsContract.Data.
  • Achtung: Damit Sie Ihre eigenen Kontodaten in einer Zeile für unformatierte Kontakte verwenden können, müssen diese zuerst mit der AccountManager registriert werden. Fordern Sie dazu die Nutzer auf, der Liste der Konten den Kontotyp und ihren Kontonamen hinzuzufügen. Wenn Sie dies nicht tun, löscht der Contacts Provider automatisch die Zeile mit Ihren unformatierten Kontakten.

    Wenn Ihre Anwendung 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 ist, muss der Nutzer zuerst das Konto „Typ“ (com.example.dataservice) und den Kontonamen (becky.smart@dataservice.example.com) hinzufügen, bevor Ihre Anwendung Rohkontaktzeilen hinzufügen kann. Sie können dem Nutzer diese Anforderung in der Dokumentation erläutern oder ihn auffordern, den Typ und den Namen hinzuzufügen, oder beides. Die Kontotypen und Kontonamen werden im nächsten Abschnitt ausführlicher beschrieben.

Quellen von unformatierten Kontaktdaten

Um zu verstehen, wie Rohkontakte funktionieren, betrachten Sie die Nutzerin „Emily Dickinson“, für die auf ihrem Gerät die folgenden drei Nutzerkonten definiert sind:

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

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

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

Der Contacts Provider erstellt als Ergebnis dieser Arbeit drei Rohkontakte:

  1. Ein Rohkontakt für „Thomas Higginson“, der mit emily.dickinson@gmail.com in Verbindung steht. Der Typ des Nutzerkontos ist „Google“.
  2. Ein zweiter Rohkontakt für „Thomas Higginson“ in Verbindung mit emilyd@gmail.com. Der Typ des Nutzerkontos ist auch „Google“. Es gibt einen zweiten Rohkontakt, obwohl der Name mit einem früheren 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 Typ des Nutzerkontos 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. Dadurch 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 Zeile mit den unformatierten Kontakten 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 Contact Provider die beiden Zeilen mit den E-Mail-Adressen und verknüpft sie mit dem Rohkontakt.

Beachten Sie, dass in dieser Tabelle verschiedene Datentypen gespeichert sind. Die Zeilen „Anzeigename“, „Telefonnummer“, „E-Mail-Adresse“, „Postanschrift“, „Foto“ und „Websitedetails“ finden Sie alle in der Tabelle ContactsContract.Data. Um dies zu vereinfachen, hat die Tabelle ContactsContract.Data einige Spalten mit beschreibenden Namen und andere mit allgemeinen Namen. Der Inhalt einer Spalte mit beschreibenden Namen hat unabhängig vom Datentyp in der Zeile die gleiche Bedeutung, während der Inhalt einer Spalte mit generischen Namen je nach Datentyp unterschiedliche Bedeutungen hat.

Beschreibende Spaltennamen

Hier einige 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 abgeleiteten Klassen von ContactsContract.CommonDataKinds definiert sind. Diese MIME-Typen sind Open Source und können von jeder Anwendung oder jedem Synchronisierungsadapter verwendet werden, die mit dem Contacts Provider kompatibel sind.
IS_PRIMARY
Wenn dieser Datenzeilentyp bei einem Rohkontakt mehr als einmal vorkommen kann, markiert die Spalte IS_PRIMARY die Datenzeile, die die primären Daten für den Typ enthält. Wenn der Nutzer beispielsweise lange auf eine Telefonnummer für einen Kontakt drückt und Standard festlegen auswählt, wird für die Spalte IS_PRIMARY in der Zeile ContactsContract.Data mit dieser Nummer ein Wert ungleich null festgelegt.

Allgemeine Spaltennamen

Es gibt 15 allgemeine Spalten mit den Namen DATA1 bis DATA15, die allgemein verfügbar sind, und vier zusätzliche generische Spalten SYNC1 bis SYNC4, die nur von Synchronisierungsadaptern verwendet werden sollten. Die generischen Konstanten für Spaltennamen funktionieren immer, unabhängig vom Typ der Daten in der Zeile.

Die Spalte DATA1 ist indexiert. Der Contacts Provider verwendet diese Spalte immer für die Daten, von denen der Anbieter erwartet, dass sie das häufigste Ziel einer Abfrage sind. In einer E-Mail-Zeile enthält diese Spalte beispielsweise die tatsächliche E-Mail-Adresse.

Konventionsgemäß ist die Spalte DATA15 für das Speichern von BLOB-Daten (Binary Large Object) wie Miniaturansichten von Fotos reserviert.

Typspezifische Spaltennamen

Um die Arbeit mit den Spalten für einen bestimmten Zeilentyp zu erleichtern, bietet Contact Provider auch typspezifische Spaltennamenkonstanten, die in abgeleiteten Klassen von ContactsContract.CommonDataKinds definiert sind. Die Konstanten geben demselben Spaltennamen einfach einen anderen konstanten Namen, sodass Sie auf Daten in einer Zeile eines bestimmten Typs zugreifen können.

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

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

Abbildung 2 zeigt, wie beschreibende Spalten und Datenspalten in einer ContactsContract.Data-Zeile erscheinen und wie typspezifische Spaltennamen die allgemeinen Spaltennamen überlagern.

Zuordnung von typspezifischen Spaltennamen zu generischen Spaltennamen

Abbildung 2: Typspezifische Spaltennamen und allgemeine 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 mit dieser Datenzeile verknüpften Rohkontakt. Ein Rohkontakt hat nur eine dieser Zeilen.
ContactsContract.CommonDataKinds.Photo Das Hauptfoto für den unbearbeiteten Kontakt, der mit dieser Datenzeile verknüpft ist. Ein Rohkontakt hat nur eine dieser Zeilen.
ContactsContract.CommonDataKinds.Email Eine E-Mail-Adresse für den Rohkontakt, der mit dieser Datenzeile verknüpft ist. Ein Rohkontakt kann mehrere E-Mail-Adressen haben.
ContactsContract.CommonDataKinds.StructuredPostal Eine Postanschrift für den Rohkontakt, der mit dieser Datenzeile verknüpft ist. Ein Rohkontakt kann mehrere Postadressen haben.
ContactsContract.CommonDataKinds.GroupMembership Eine Kennung, die den Rohkontakt mit einer der Gruppen im Contacts Provider verknüpft. Gruppen sind eine optionale Funktion für einen Kontotyp und Kontonamen. Sie werden im Abschnitt Kontaktgruppen ausführlicher beschrieben.

Kontakte

Der Contacts Provider kombiniert die Rohkontaktzeilen aller 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 die Aggregation 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 einer insert() hinzuzufügen, wird die Ausnahme UnsupportedOperationException ausgelöst. Wenn Sie versuchen, eine Spalte zu aktualisieren, die als „schreibgeschützt“ aufgeführt ist, wird das Update ignoriert.

Der Contacts Provider erstellt einen neuen Kontakt, wenn ein neuer Rohkontakt hinzugefügt wird, der mit keinem vorhandenen Kontakt übereinstimmt. Der Anbieter tut dies auch, wenn sich die Daten eines vorhandenen Kontakts so ändern, dass sie nicht mehr mit dem Kontakt übereinstimmen, mit dem sie zuvor verknüpft waren. Wenn eine Anwendung oder ein Synchronisierungsadapter einen neuen Kontaktkontakt erstellt, der mit einem vorhandenen Kontakt übereinstimmt, wird dieser Kontakt unter dem vorhandenen Kontakt zusammengefasst.

Der Contact Provider verknüpft eine Kontaktzeile mit seinen Zeilen mit Rohkontaktdaten über die Spalte _ID der Kontaktzeile in der Tabelle Contacts. Die Spalte CONTACT_ID der Tabelle ContactsContract.RawContacts mit Rohkontakten 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 eine „dauerhafte“ Verknüpfung zur Kontaktzeile ist. Da der Contact Provider Kontakte automatisch verwaltet, kann er den _ID-Wert einer Kontaktzeile als Reaktion auf eine Aggregation oder Synchronisierung ändern. Selbst in diesem Fall verweist der Inhalts-URI CONTENT_LOOKUP_URI in Kombination mit dem LOOKUP_KEY des Kontakts weiterhin auf die Kontaktzeile, sodass Sie mit LOOKUP_KEY Links zu „Favoriten“-Kontakten usw. verwalten können. Diese Spalte hat ein eigenes Format, das in keinem Zusammenhang mit dem Format der Spalte _ID steht.

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

Haupttabellen des Kontaktanbieters

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

Achtung : Wenn du deine App im Google Play Store veröffentlichst oder auf einem Gerät mit Android 10 (API-Level 29) oder höher ist, musst du bedenken, dass eine begrenzte Anzahl von Feldern und Methoden für Kontaktdaten veraltet ist.

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

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

Darüber hinaus geben die folgenden Felder keine häufigen Kontakte mehr zurück. Einige dieser Felder beeinflussen das Ranking von Kontakten nur dann, wenn die Kontakte zu einer bestimmten Datenart gehören.

Wenn Ihre Anwendungen auf diese Felder oder APIs zugreifen oder sie aktualisieren, verwenden Sie alternative Methoden. Sie können beispielsweise bestimmte Anwendungsfälle erfüllen, indem Sie Anbieter privater Inhalte oder andere Daten nutzen, die in Ihrer App oder in Ihren Back-End-Systemen gespeichert sind.

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

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

Daten von Synchronisierungsadaptern

Nutzer geben Kontaktdaten direkt auf dem Gerät ein. Über Synchronisierungsadapter werden jedoch auch Daten aus Webdiensten an den Kontakteanbieter gesendet, die die Datenübertragung zwischen dem Gerät und den Diensten automatisieren. 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 funktioniert, durch einen Kontotyp identifiziert. Jeder Synchronisierungsadapter funktioniert mit einem Kontotyp, unterstützt jedoch mehrere Kontonamen für diesen Typ. Kontotypen und Kontonamen werden im Abschnitt Quellen von unformatierten Kontaktdaten kurz beschrieben. Die folgenden Definitionen bieten detailliertere Informationen und beschreiben, wie der Kontotyp und -name mit Synchronisierungsadaptern und -diensten zusammenhängen.

Kontotyp
Kennzeichnet einen Dienst, in dem der Nutzer Daten gespeichert hat. In den meisten Fällen muss sich der Nutzer beim Dienst authentifizieren. Google Kontakte ist beispielsweise ein Kontotyp, der durch den Code google.com identifiziert wird. Dieser Wert entspricht dem von AccountManager verwendeten Kontotyp.
Ihren Kontonamen
Identifiziert ein bestimmtes Konto oder eine bestimmte Anmeldung für einen Kontotyp. Google Kontakte-Konten sind dieselben wie Google-Konten, bei denen eine E-Mail-Adresse als Kontoname verwendet wird. Andere Dienste können einen aus einem Wort bestehenden Nutzernamen oder eine numerische ID verwenden.

Kontotypen müssen nicht eindeutig sein. Ein Nutzer kann mehrere Google Kontakte-Konten konfigurieren und seine Daten beim Kontakteanbieter herunterladen. Dies kann passieren, wenn der Nutzer eine Reihe von persönlichen Kontakten für einen privaten Kontonamen und eine andere für die Arbeit hat. Kontonamen sind in der Regel eindeutig. Gemeinsam identifizieren sie einen bestimmten Datenfluss zwischen dem Contacts Provider und einem externen Dienst.

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

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

Datenfluss über Personen

Abbildung 4: Der Datenfluss des Contacts Providers.

Erforderliche Berechtigungen

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

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

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

Denken Sie daran, dass die Kontaktdaten des Nutzers persönlich und vertraulich sind. Nutzer nehmen den Schutz ihrer Daten sehr ernst und möchten daher nicht, dass Apps Daten über sie oder ihre Kontakte erheben. Wenn nicht offensichtlich ist, warum Sie eine Berechtigung für den Zugriff auf die Kontaktdaten benötigen, erhalten Ihre App möglicherweise schlechte Bewertungen oder die Installation wird einfach abgelehnt.

Das Nutzerprofil

Die Tabelle ContactsContract.Contacts enthält eine einzelne Zeile mit Profildaten für den Gerätenutzer. Diese Daten beziehen sich auf user des Geräts und nicht auf einen der Kontakte des Nutzers. Die Zeile mit den Profilkontakten ist für jedes System, das ein Profil verwendet, mit einer Zeile für unformatierte Kontakte verknüpft. Jede Profilzeile mit Rohkontakten 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. Neben den Lese- und Schreibberechtigungen READ_CONTACTS und WRITE_CONTACTS sind für den Zugriff auf das Nutzerprofil die Berechtigungen „android.Manifest.permission#READ_PROFILE“ bzw. „android.Manifest.permission#WRITE_PROFILE“ für den Lese- bzw. Schreibzugriff erforderlich.

Denken Sie daran, dass Sie das Profil eines Nutzers als sensibel erachten sollten. Mit der Berechtigung „android.Manifest.permission#READ_PROFILE“ können Sie auf die personenidentifizierbaren Daten des Gerätenutzers zugreifen. Teilen Sie dem Nutzer in der Beschreibung Ihrer Anwendung mit, warum Sie Zugriffsberechtigungen für das Nutzerprofil benötigen.

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

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 ermitteln möchten, ob eine davon das Nutzerprofil ist, testen Sie die Spalte IS_USER_PROFILE der Zeile. Diese Spalte wird auf „1“ gesetzt, wenn der Kontakt das Nutzerprofil ist.

Metadaten des Kontaktanbieters

Der Contacts Provider verwaltet Daten, die den Status der Kontaktdaten im Repository erfassen. Diese Metadaten zum Repository werden an verschiedenen Orten gespeichert, z. B. in den Tabellenzeilen „Rohkontakte“, „Daten“ und „Kontakte“, der Tabelle ContactsContract.Settings und der Tabelle ContactsContract.SyncState. In der folgenden Tabelle sehen Sie die Auswirkungen der einzelnen Metadaten:

Tabelle 3 Metadaten im Contact Provider

Tischspiel Spalte Werte Bedeutung
ContactsContract.RawContacts DIRTY "0": seit der letzten Synchronisierung nicht geändert Unverarbeitete Kontakte werden markiert, die auf dem Gerät geändert wurden und wieder mit dem Server synchronisiert werden müssen. Der Wert wird vom Contacts Provider automatisch festgelegt, wenn Android-Anwendungen eine Zeile aktualisieren.

Synchronisierungsadapter, die die Rohkontakt- oder Datentabellen ändern, sollten immer den String CALLER_IS_SYNCADAPTER an den von ihnen verwendeten Inhalts-URI anhängen. Dadurch wird verhindert, dass der Anbieter Zeilen als „schmutzig“ markiert. Andernfalls scheinen Änderungen des Synchronisierungsadapters lokale Änderungen zu sein und an den Server gesendet zu werden, obwohl der Server die Quelle der Änderung war.

"1": Seit der letzten Synchronisierung geändert, muss wieder mit dem Server synchronisiert werden.
ContactsContract.RawContacts VERSION Die Versionsnummer dieser Zeile. Der Contacts Provider erhöht diesen Wert automatisch, wenn sich die Zeile oder die zugehörigen Daten ändern.
ContactsContract.Data DATA_VERSION Die Versionsnummer dieser Zeile. Der Contacts Provider erhöht diesen Wert automatisch, wenn die Datenzeile geändert wird.
ContactsContract.RawContacts SOURCE_ID Ein Stringwert, der diesen Rohkontakt für das Konto, in dem er erstellt wurde, eindeutig identifiziert. 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-App einen neuen Rohkontakt erstellt, sollte diese Spalte leer bleiben. 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 und bei allen Synchronisierungen stabil sein:

  • Eindeutig: Jeder Rohkontakt für ein Konto muss eine eigene Quellen-ID haben. Wenn Sie dies nicht erzwingen, treten Probleme in der Kontakte-Anwendung auf. Zwei Rohkontakte für denselben Kontotyp können dieselbe Quell-ID haben. So darf beispielsweise der unbearbeitete Kontakt „Thomas Higginson“ für das Konto emily.dickinson@gmail.com dieselbe Quell-ID haben wie der unbearbeitete Kontakt „Thomas Higginson“ für das Konto emilyd@gmail.com.
  • Stabil: Quell-IDs sind ein dauerhafter Bestandteil der Daten des Onlinedienstes für den Rohkontakt. Wenn der Nutzer beispielsweise den Kontaktspeicher aus den Anwendungseinstellungen löscht und eine neue Synchronisierung durchführt, sollten die wiederhergestellten Rohkontakte dieselben Quell-IDs wie zuvor haben. Wenn Sie dies nicht erzwingen, funktionieren Tastenkombinationen nicht mehr.
ContactsContract.Groups GROUP_VISIBLE "0" – Kontakte in dieser Gruppe sollten nicht in Benutzeroberflächen von Android-Anwendungen sichtbar sein. Diese Spalte dient der Kompatibilität mit Servern, bei 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“: Für dieses Konto und diesen Kontotyp sind Kontakte, die nicht zu einer Gruppe gehören, für die Benutzeroberflächen von Android-Anwendungen nicht sichtbar. Kontakte sind standardmäßig unsichtbar, wenn keiner ihrer Rohkontakte zu einer Gruppe gehört (Gruppenzugehörigkeit für einen Rohkontakt wird durch eine oder mehrere ContactsContract.CommonDataKinds.GroupMembership-Zeilen in der Tabelle ContactsContract.Data angegeben). Wenn Sie dieses Flag in der Tabellenzeile ContactsContract.Settings für einen Kontotyp und ein Konto festlegen, können Sie erzwingen, dass Kontakte ohne Gruppen sichtbar sind. Dieses Flag wird zum Beispiel verwendet, um Kontakte von Servern anzuzeigen, die keine Gruppen verwenden.
"1": Für dieses Konto und diesen Kontotyp sind Kontakte, die nicht zu einer Gruppe gehören, in Anwendungs-UIs sichtbar.
ContactsContract.SyncState (alle) Verwenden Sie diese Tabelle zum Speichern von Metadaten für Ihren Synchronisierungsadapter. Mit dieser Tabelle können Sie den Synchronisierungsstatus und andere synchronisierungsbezogene Daten dauerhaft auf dem Gerät speichern.

Kontaktanbieterzugriff

In diesem Abschnitt werden Richtlinien für den Zugriff auf Daten des Contacts Providers beschrieben. Der Schwerpunkt liegt dabei auf folgenden Punkten:

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

Das Vornehmen von Änderungen über einen Synchronisierungsadapter wird auch im Abschnitt Synchronisierungsadapter für Contacts Provider ausführlicher beschrieben.

Entitäten abfragen

Da die Tabellen „Contact Provider“ in einer Hierarchie organisiert sind, ist es oft sinnvoll, eine Zeile und alle damit verknüpften „untergeordneten“ Zeilen abzurufen. Wenn beispielsweise alle Informationen für eine Person angezeigt werden sollen, können Sie alle ContactsContract.RawContacts-Zeilen für eine einzelne ContactsContract.Contacts-Zeile oder alle ContactsContract.CommonDataKinds.Email-Zeilen für eine einzelne ContactsContract.RawContacts-Zeile abrufen. Zu diesem Zweck bietet Contact Provider Entitätskonstrukte an, die wie Datenbankverknüpfungen zwischen Tabellen fungieren.

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

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 abfragen zu müssen, um eine ID zu erhalten, und dann die untergeordnete Tabelle mit dieser ID abfragen zu müssen. Außerdem verarbeitet der Contacts Provider eine Abfrage für eine Entität in einer einzelnen Transaktion, wodurch sichergestellt wird, dass die abgerufenen Daten intern konsistent sind.

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

Das folgende Snippet zeigt, wie Sie alle Rohkontaktzeilen für einen Kontakt abrufen. Das Snippet ist Teil einer größeren Anwendung mit den zwei Aktivitäten „main“ und „detail“. Die Hauptaktivität zeigt eine Liste von Kontaktzeilen an. Wenn der Nutzer eine Kontaktzeile auswählt, sendet die Aktivität ihre ID an die Detailaktivität. Bei der Detailaktivität werden mit dem 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 von onLoadFinished() auf. Eines der eingehenden Argumente für diese Methode ist ein Cursor mit den Ergebnissen der Abfrage. In Ihrer eigenen Anwendung können Sie die Daten von diesem Cursor abrufen, um sie anzuzeigen oder weiter damit zu arbeiten.

Batchänderung

Nach Möglichkeit sollten Sie Daten im „Batch-Modus“ im Contact Provider einfügen, aktualisieren und löschen. Erstellen Sie dazu eine ArrayList mit ContentProviderOperation-Objekten und rufen Sie applyBatch() auf. Da der Contacts Provider alle Vorgänge in einer applyBatch() in einer einzigen Transaktion ausführt, verbleiben Ihre Änderungen im Kontakt-Repository nie in einem inkonsistenten Zustand. Eine Batchänderung erleichtert außerdem gleichzeitig das Einfügen eines Rohkontakts und seiner Detaildaten.

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

Ertragsgruppen

Eine Batchänderung mit einer großen Anzahl von Vorgängen kann andere Prozesse blockieren und so zu einer negativen Nutzererfahrung führen. Um alle gewünschten Änderungen in möglichst wenigen separaten Listen zu organisieren und gleichzeitig zu verhindern, dass sie das System blockieren, sollten Sie Ertragsgruppen für einen oder mehrere Vorgänge festlegen. Ein Ertragspartner ist ein ContentProviderOperation-Objekt, dessen Wert isYieldAllowed() auf true festgelegt ist. Wenn der Contact Provider auf einen Ertragspartner trifft, unterbricht er seine Arbeit, damit andere Prozesse ausgeführt werden können, und schließt die aktuelle Transaktion. Wird der Anbieter wieder gestartet, fährt er mit dem nächsten Vorgang im ArrayList fort und startet eine neue Transaktion.

Ertragsgruppen führen zu mehr als einer Transaktion pro Aufruf von applyBatch(). Aus diesem Grund sollten Sie für den letzten Vorgang einen Ertragspartner für eine Reihe zusammengehöriger Zeilen festlegen. Sie sollten beispielsweise einen Ertragspartner für den letzten Vorgang in einem Dataset festlegen, mit dem Rohkontaktzeilen und die zugehörigen Datenzeilen hinzugefügt werden, oder den letzten Vorgang für eine Gruppe von Zeilen, die einen einzelnen Kontakt betreffen.

Ertragsgruppen sind auch eine Einheit des atomaren Vorgangs. Alle Zugriffe zwischen zwei Ertragsgruppen sind als eine Einheit erfolgreich oder schlagen fehl. Wenn Sie keine Ertragsgruppen festlegen, besteht der kleinste atomare Vorgang aus dem gesamten Batch von Vorgängen. Wenn Sie Ertragsgruppen verwenden, verhindern Sie, dass Vorgänge die Systemleistung beeinträchtigen. Gleichzeitig sorgen Sie dafür, dass ein Teil der Vorgänge atomar ist.

Rückverweise für Änderungen

Wenn Sie eine neue Rohkontaktzeile und die zugehörigen Datenzeilen als eine Reihe von ContentProviderOperation-Objekten einfügen, müssen Sie die Datenzeilen mit der Zeile mit Rohkontaktdaten verknüpfen. Fügen Sie dazu den _ID-Wert des Rohkontakts als RAW_CONTACT_ID-Wert ein. Dieser Wert ist jedoch nicht verfügbar, wenn Sie den ContentProviderOperation für die Datenzeile erstellen, da Sie den ContentProviderOperation für die Rohkontaktzeile noch nicht angewendet haben. Zur Umgehung dieses Problems 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 von ContentProviderResult-Objekten aus applyBatch(). Beim Anwenden der Batchvorgänge wird das Ergebnis jedes Vorgangs in einem Zwischenarray von Ergebnissen gespeichert. Der Wert previousResult ist der Index eines dieser Ergebnisse, das mit dem Wert key abgerufen und gespeichert wird. Auf diese Weise können Sie einen neuen Rohkontaktdatensatz einfügen und seinen _ID-Wert zurückerhalten. Sie können dann einen „Zurückverweis“ auf den Wert herstellen, wenn Sie eine ContactsContract.Data-Zeile hinzufügen.

Das gesamte Ergebnisarray wird beim ersten Aufruf von applyBatch() erstellt. Es hat eine Größe, die der Größe des ArrayList der von Ihnen bereitgestellten ContentProviderOperation-Objekte entspricht. Alle Elemente im Ergebnisarray sind jedoch auf null gesetzt. Wenn Sie versuchen, für einen Vorgang, der noch nicht angewendet wurde, einen Rückverweis auf ein Ergebnis auszuführen, gibt withValueBackReference() den Fehler Exception aus.

Die folgenden Snippets zeigen, wie ein neuer Rohkontakt und Daten im Batch eingefügt werden. Sie enthalten Code, der einen Ertragspartner definiert und einen Back-Verweis verwendet.

Mit dem ersten Snippet werden Kontaktdaten von der UI abgerufen. An dieser Stelle hat der Nutzer bereits das Konto ausgewählt, für das 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());

Das nächste Snippet erstellt einen Vorgang zum Einfügen der Zeile mit den Rohkontaktdaten in die Tabelle ContactsContract.RawContacts:

Kotlin

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

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

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

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

Java

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

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

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

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

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

Jedes Vorgangs-Builder-Objekt verwendet withValueBackReference(), um das RAW_CONTACT_ID abzurufen. Die Referenzpunkte zurück zum ContentProviderResult-Objekt aus dem ersten Vorgang, der die Rohkontaktzeile hinzufügt und den neuen _ID-Wert zurückgibt. Dadurch wird jede Datenzeile automatisch über ihre RAW_CONTACT_ID mit der neuen ContactsContract.RawContacts-Zeile verknüpft, zu der sie gehört.

Das Objekt ContentProviderOperation.Builder, dem die E-Mail-Zeile hinzugefügt wird, wird mit withYieldAllowed() gekennzeichnet, wodurch ein Ertragspartner 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 die neuen Rohkontakt- und 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);
    }
}

Batchvorgänge ermöglichen es Ihnen auch, die optimistische Gleichzeitigkeitserkennung zu implementieren. Mit dieser Methode werden Änderungstransaktionen angewendet, ohne das zugrunde liegende Repository sperren zu müssen. Wenn Sie diese Methode verwenden möchten, wenden Sie die Transaktion an und suchen Sie dann nach anderen Änderungen, die möglicherweise gleichzeitig vorgenommen wurden. Wenn Sie feststellen, dass eine inkonsistente Änderung vorgenommen wurde, führen Sie ein Rollback der Transaktion durch und wiederholen Sie sie.

Die optimistische Gleichzeitigkeitserkennung ist nützlich für ein Mobilgerät, bei dem es nur einen Nutzer auf einmal gibt und gleichzeitige Zugriffe auf ein Daten-Repository selten sind. Da Sperren nicht verwendet werden, verschwenden Sie keine Zeit damit, Sperren einzurichten oder darauf zu warten, dass andere Transaktionen ihre Sperren aufheben.

So verwenden Sie die optimistische Gleichzeitigkeitserkennung 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 zum Erzwingen einer Einschränkung geeignet ist. Verwenden Sie als Inhalts-URI RawContacts.CONTENT_URI mit angehängtem _ID des Rohkontakts.
  3. Rufen Sie für das ContentProviderOperation.Builder-Objekt withValue() auf, um die Spalte VERSION mit der soeben abgerufenen Versionsnummer zu vergleichen.
  4. Rufen Sie für dieselbe ContentProviderOperation.Builder withExpectedCount() auf, damit nur eine Zeile von dieser Assertion getestet wird.
  5. Rufen Sie build() auf, um das ContentProviderOperation-Objekt zu erstellen. Fügen Sie dieses Objekt dann als erstes Objekt in der ArrayList hinzu, das Sie an applyBatch() übergeben.
  6. Wenden Sie die Batch-Transaktion an.

Wenn die Zeile mit unverarbeiteten Kontakten zwischen dem Zeitpunkt des Lesens der Zeile und dem Versuch, sie zu ändern, durch einen anderen Vorgang aktualisiert wird, schlägt das Assertion-ContentProviderOperation-Objekt fehl und der gesamte Batch von Vorgängen wird gelöscht. Sie können dann den Batch wiederholen oder eine andere Aktion ausführen.

Das folgende Snippet zeigt, wie ein „assert“-ContentProviderOperation nach der Abfrage eines einzelnen Rohkontakts mit einem CursorLoader erstellt wird:

Kotlin

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

...

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

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

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

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

ops.add(assertOp.build())

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

...

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

Java

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

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

...

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

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

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

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

ops.add(assertOp.build());

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

...

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

    } catch (OperationApplicationException e) {

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

Abruf und Änderung mit Intents

Wenn Sie einen Intent an die Kontakte-App des Geräts senden, können Sie indirekt auf den Contact Provider zugreifen. Der Intent startet die UI der Anwendung „Kontakte“ auf dem Gerät, in der Nutzer kontaktbezogene 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ücksenden.
  • Bearbeiten Sie die Daten eines vorhandenen Kontakts.
  • Fügen Sie einen neuen Rohkontakt für eines ihrer Konten ein.
  • Kontakte oder Kontaktdaten löschen

Wenn der Nutzer Daten einfügt oder aktualisiert, kannst du die Daten zuerst erfassen und als Teil des Intents senden.

Wenn Sie Intents verwenden, um über die Kontakteanwendung des Geräts auf den Contacts Provider zuzugreifen, müssen Sie keine eigene UI oder keinen eigenen Code für den Zugriff auf den Anbieter schreiben. Sie müssen auch keine Lese- oder Schreibberechtigung für den Anbieter anfordern. Die Kontakte-App des Geräts kann Ihnen die Leseberechtigung für einen Kontakt delegieren. Da Sie Änderungen am Anbieter über eine andere App vornehmen, benötigen Sie keine Schreibberechtigungen.

Das allgemeine Verfahren zum Senden eines Intents für den Zugriff auf einen Anbieter wird im Abschnitt „Datenzugriff über Intents“ im Leitfaden Grundlagen von Contentanbietern 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 des Kontaktanbieters.

Aufgabe Aktion Daten MIME-Typ Hinweise
Kontakt aus einer Liste auswählen ACTION_PICK Eine der folgenden Optionen: Nicht verwendet Zeigt je nach angegebenem Inhalts-URI-Typ eine Liste von Rohkontakten oder eine Liste mit Daten eines Rohkontakts an.

Rufen Sie startActivityForResult() auf. Dadurch wird der Inhalts-URI der ausgewählten Zeile zurückgegeben. Das Format des URI ist der Inhalts-URI der Tabelle mit dem angehängten LOOKUP_ID der Zeile. Die Kontakte-App des Geräts delegiert diesem Inhalts-URI Lese- und Schreibberechtigungen für die gesamte Lebensdauer Ihrer Aktivität. Weitere Informationen finden Sie im Leitfaden Grundlagen von Contentanbietern.

Neuen Rohkontakt einfügen Insert.ACTION RawContacts.CONTENT_TYPE, MIME-Typ für eine Gruppe unformatierter Kontakte. Der Bildschirm Kontakt hinzufügen der Kontakte-Anwendung des Geräts wird angezeigt. Die Extraswerte, die Sie dem Intent hinzufügen, werden angezeigt. Wird er mit startActivityForResult() gesendet, wird der Inhalts-URI des neu hinzugefügten Rohkontakts im Feld „Daten“ im Argument Intent an die Callback-Methode onActivityResult() Ihrer Aktivität zurückgegeben. Rufen Sie getData() auf, um den Wert zu erhalten.
Kontakte bearbeiten ACTION_EDIT CONTENT_LOOKUP_URI für den Kontakt. Die Editoraktivität ermöglicht es dem Nutzer, alle mit diesem Kontakt verknüpften Daten zu bearbeiten. Contacts.CONTENT_ITEM_TYPE, ein einzelner Kontakt. Zeigt den Bildschirm Kontakt bearbeiten in der Kontakte-Anwendung an. Die Extraswerte, die Sie dem Intent hinzufügen, werden angezeigt. Wenn der Nutzer auf Fertig klickt, um die Änderungen zu speichern, wird die Aktivität wieder im Vordergrund ausgeführt.
Auswahl anzeigen, über die Daten hinzugefügt werden können ACTION_INSERT_OR_EDIT CONTENT_ITEM_TYPE Dieser Intent zeigt immer den Auswahlbildschirm der Kontakte-App an. Der Nutzer kann entweder einen Kontakt zum Bearbeiten auswählen oder einen neuen Kontakt hinzufügen. Je nach Auswahl des Nutzers wird entweder der Bildschirm zum Bearbeiten oder zum Hinzufügen angezeigt und die zusätzlichen Daten, die Sie im Intent übergeben, werden angezeigt. Wenn deine App Kontaktdaten wie eine E-Mail-Adresse oder Telefonnummer anzeigt, verwende diesen Intent, damit der Nutzer die Daten einem vorhandenen Kontakt hinzufügen kann. Kontakt

Hinweis:In den Extras dieses Intents muss kein Namenswert gesendet werden, da der Nutzer immer einen vorhandenen Namen auswählt oder einen neuen hinzufügt. Wenn Sie einen Namen senden und der Nutzer sich dafür entscheidet, eine Änderung vorzunehmen, zeigt die Kontakte-App den von Ihnen gesendeten Namen an und überschreibt den vorherigen Wert. Wenn der Nutzer dies nicht bemerkt und die Änderung speichert, geht der alte Wert verloren.

In der Kontakte App des Geräts ist es nicht möglich, einen Rohkontakt oder zugehörige Daten mit einer Absicht zu löschen. Verwenden Sie zum Löschen eines Rohkontakts stattdessen ContentResolver.delete() oder ContentProviderOperation.newDelete().

Das folgende Snippet zeigt, wie Sie einen Intent erstellen und senden, der einen neuen Rohkontakt und neue Daten einfügt:

Kotlin

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

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

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

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

/*
 * Defines the raw contact row
 */

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

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

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

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

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

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

/*
 * Sets up the email data row
 */

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

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

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

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

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

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

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

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

Java

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

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

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

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

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

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

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

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


/*
 * Defines the raw contact row
 */

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

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

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

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

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

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

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

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

/*
 * Sets up the email data row
 */

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

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

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

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

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

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

Datenintegrität

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

Fügen Sie für jede hinzugefügte ContactsContract.RawContacts-Zeile immer eine ContactsContract.CommonDataKinds.StructuredName-Zeile hinzu.
Eine ContactsContract.RawContacts-Zeile ohne eine ContactsContract.CommonDataKinds.StructuredName-Zeile in der Tabelle ContactsContract.Data kann Probleme bei der Aggregation verursachen.
Verknüpfen Sie neue ContactsContract.Data-Zeilen immer mit der entsprechenden übergeordneten ContactsContract.RawContacts-Zeile.
Eine ContactsContract.Data-Zeile, die nicht mit einem ContactsContract.RawContacts verknüpft ist, ist in der Kontakte-Anwendung des Geräts nicht sichtbar und kann Probleme mit Synchronisierungsadaptern verursachen.
Ändern Sie die Daten nur für die unbearbeiteten Kontakte, deren Inhaber Sie sind.
Denken Sie daran, dass der Contacts Provider Daten aus mehreren verschiedenen Kontotypen/Onlinediensten verwaltet. Ihre Anwendung muss nur Daten in Zeilen ändern oder löschen, die Ihnen gehören. Außerdem dürfen nur Daten mit einem Kontotyp und Namen eingefügt werden, den Sie kontrollieren.
Verwenden Sie immer die in ContactsContract und den zugehörigen abgeleiteten Klassen definierten Konstanten für Zertifizierungsstellen, Inhalts-URIs, URI-Pfade, Spaltennamen, MIME-Typen und TYPE-Werte.
Mithilfe dieser Konstanten können Sie Fehler vermeiden. Außerdem erhalten Sie Compiler-Warnungen, wenn eine der Konstanten verworfen wird.

Benutzerdefinierte Datenzeilen

Wenn Sie eigene benutzerdefinierte MIME-Typen erstellen und verwenden, können Sie eigene Datenzeilen in der Tabelle ContactsContract.Data einfügen, bearbeiten, löschen und abrufen. Die Zeilen sind auf die in ContactsContract.DataColumns definierte Spalte beschränkt. Sie können den Standardspaltennamen jedoch auch eigene typspezifische Spaltennamen zuordnen. Die Daten für die Zeilen werden in der Kontakte-Anwendung des Geräts angezeigt, können aber weder bearbeitet noch gelöscht werden. Nutzer können keine zusätzlichen Daten hinzufügen. Damit Nutzer Ihre benutzerdefinierten Datenzeilen ändern können, müssen Sie eine Editoraktivität in Ihrer eigenen Anwendung bereitstellen.

Stellen Sie zum Anzeigen Ihrer benutzerdefinierten Daten eine contacts.xml-Datei bereit, die ein <ContactsAccountType>-Element und mindestens eines seiner untergeordneten <ContactsDataKind>-Elemente enthält. Dies wird im Abschnitt <ContactsDataKind> element ausführlicher beschrieben.

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

Synchronisierungsadapter für Contact Provider

Der Contacts Provider wurde speziell für die Synchronisierung von Kontaktdaten zwischen einem Gerät und einem Onlinedienst entwickelt. Dadurch können Nutzer vorhandene Daten auf ein neues Gerät herunterladen und in ein neues Konto hochladen. Die Synchronisierung stellt außerdem sicher, dass den Nutzern die neuesten Daten zur Verfügung stehen, unabhängig von der Quelle von Ergänzungen und Änderungen. Ein weiterer Vorteil der Synchronisierung besteht darin, dass Kontaktdaten auch dann verfügbar sind, wenn das Gerät nicht mit dem Netzwerk verbunden ist.

Obwohl Sie die Synchronisierung auf verschiedene Arten implementieren können, bietet das Android-System ein Plug-in-Synchronisierungs-Framework, das die folgenden Aufgaben automatisiert:

  • Netzwerkverfügbarkeit wird geprüft.
  • Planen und Ausführen der Synchronisierung gemäß den Nutzereinstellungen
  • Beendete Synchronisierungen neu starten

Zur Verwendung dieses Frameworks ist ein Synchronisierungsadapter-Plug-in erforderlich. Jeder Synchronisierungsadapter ist für einen Dienst und Contentanbieter eindeutig, kann aber mehrere Kontonamen für denselben Dienst verarbeiten. Das Framework ermöglicht außerdem mehrere Synchronisierungsadapter für denselben Dienst und Anbieter.

Adapterklassen und -dateien synchronisieren

Sie implementieren einen Synchronisierungsadapter als abgeleitete Klasse von AbstractThreadedSyncAdapter und installieren ihn als Teil einer Android-App. Das System lernt den Synchronisierungsadapter von Elementen in Ihrem Anwendungsmanifest und aus einer speziellen XML-Datei, auf die das Manifest verweist. Die XML-Datei definiert den Kontotyp für den Onlinedienst und die Autorisierung für den Contentanbieter. Zusammen wird der Adapter eindeutig identifiziert. 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. An diesem Punkt beginnt das System mit der Verwaltung des Adapters und ruft ihn für die Synchronisierung zwischen dem Inhaltsanbieter und dem Server auf.

Hinweis: Durch die Verwendung eines Kontotyps zur Identifizierung des Synchronisierungsadapters kann das System Synchronisierungsadapter erkennen und gruppieren, die auf verschiedene Dienste aus derselben Organisation zugreifen. Synchronisierungsadapter für Google-Onlinedienste haben beispielsweise 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 Contentanbieter auf dem Gerät synchronisiert.

Da bei den meisten Diensten Nutzer ihre Identität bestätigen müssen, bevor sie auf Daten zugreifen können, bietet das Android-System ein Authentifizierungs-Framework, das dem Synchronisierungsadapter-Framework ähnelt und häufig in Verbindung mit diesem verwendet wird. Das Authentifizierungs-Framework verwendet Plug-in-Authenticatoren, die Unterklassen von AbstractAccountAuthenticator sind. Ein Authenticator überprüft die Identität des Nutzers in den folgenden Schritten:

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

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

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

Synchronisierungsadapter implementieren

Um einen Synchronisierungsadapter für den Contacts Provider zu implementieren, müssen Sie zuerst eine Android-App erstellen, die Folgendes enthält:

Eine Service-Komponente, die auf Anfragen vom System antwortet, um eine Bindung an den Synchronisierungsadapter zu erhalten.
Wenn das System eine Synchronisierung ausführen möchte, ruft es die Methode onBind() des Dienstes auf, um ein IBinder für den Synchronisierungsadapter zu erhalten. Dadurch kann das System prozessübergreifende Aufrufe an die Methoden des Adapters ausführen.
Der eigentliche Synchronisierungsadapter, der als konkrete abgeleitete Klasse von AbstractThreadedSyncAdapter implementiert ist.
Diese Klasse übernimmt das Herunterladen von Daten vom Server, das Hochladen von Daten vom Gerät und das Beheben von Konflikten. Die Hauptfunktion des Adapters wird in der Methode onPerformSync() ausgeführt. Diese Klasse muss als Singleton instanziiert werden.
Eine abgeleitete Klasse von Application.
Diese Klasse dient als Factory für den Singleton-Synchronisierungsadapter. Verwenden Sie die Methode onCreate(), um den Synchronisierungsadapter zu instanziieren, und stellen Sie eine statische Gettermethode bereit, um das Singleton an die Methode onBind() des Synchronisierungsadapters zurückzugeben.
Optional: Eine Service-Komponente, die auf Anfragen vom System zur Nutzerauthentifizierung antwortet.
AccountManager startet diesen Dienst, um den Authentifizierungsprozess zu starten. Die Methode onCreate() des Dienstes instanziiert ein Authenticator-Objekt. Wenn das System ein Nutzerkonto für den Synchronisierungsadapter der Anwendung authentifizieren möchte, ruft es die Methode onBind() des Dienstes auf, um ein IBinder für den Authenticator abzurufen. Dadurch kann das System prozessübergreifende Aufrufe an die Methoden des Authenticator ausführen.
Optional: Eine konkrete abgeleitete Klasse von AbstractAccountAuthenticator, die Anfragen zur Authentifizierung verarbeitet.
Diese Klasse bietet Methoden, die AccountManager aufruft, um die Anmeldedaten des Nutzers beim Server zu authentifizieren. Die Details des Authentifizierungsprozesses können je nach verwendeter Servertechnologie stark variieren. Weitere Informationen zur Authentifizierung finden Sie in der Dokumentation zu Ihrer Serversoftware.
XML-Dateien, die den Synchronisierungsadapter und den Authenticator für das System definieren.
Die zuvor beschriebenen Komponenten des Synchronisierungsadapters und des Authenticator-Diensts werden im Anwendungsmanifest in <service>-Elementen 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, der mit dem Contacts Provider synchronisiert wird, sowie einen Kontotyp für den Webdienst an.
  • Optional: Das Element <meta-data> für den Authenticator verweist auf die XML-Datei res/xml/authenticator.xml. Diese Datei gibt wiederum den von diesem Authenticator unterstützten Kontotyp sowie UI-Ressourcen an, die während der Authentifizierung angezeigt werden. Der in diesem Element angegebene Kontotyp muss mit dem Kontotyp übereinstimmen, der für den Synchronisierungsadapter angegeben wurde.

Daten aus sozialen Streams

Die Tabellen „android.provider.ContactsContract.StreamItems“ und „android.provider.ContactsContract.StreamItemPhotos“ verwalten eingehende Daten aus sozialen Netzwerken. Sie können einen Synchronisierungsadapter schreiben, der diesen Tabellen Streamdaten aus Ihrem eigenen Netzwerk hinzufügt, oder Sie können Streamdaten aus diesen Tabellen lesen und in Ihrer eigenen Anwendung darstellen oder beides. Mit diesen Funktionen können Ihre Dienste und Anwendungen für soziale Netzwerke in das soziale Netzwerk von Android integriert werden.

Text für Stream in sozialen Netzwerken

Stream-Elemente sind immer mit einem Rohkontakt verknüpft. „android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID“ ist mit dem Wert _ID für den Rohkontakt verknüpft. Der Kontotyp und der Kontoname des unformatierten Kontakts werden ebenfalls in der Zeile des Streamelements 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 Streamelement 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 mit diesem Streamelement verknüpften Kontaktkontakt. Denken Sie daran, diesen Wert festzulegen, wenn Sie ein Streamelement einfügen.
Kennungsspalten
Erforderlich. Beim Einfügen eines Streamelements müssen Sie die folgenden Kennungsspalten einfügen:
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: Der Wert android.provider.BaseColumns#_ID des Kontakts, mit dem dieses Streamelement verknüpft ist.
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: Der Wert für android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY des Kontakts, mit dem dieses Streamelement verknüpft ist.
  • android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: Der Wert android.provider.BaseColumns#_ID des Rohkontakts, mit dem dieses Streamelement verknüpft ist.
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
Optional. Enthält zusammenfassende Informationen, die am Anfang eines Stream-Artikels angezeigt werden können.
android.provider.ContactsContract.StreamItemsColumns#TEXT
Der Text des Streamelements, entweder der Inhalt, der von der Quelle des Elements gepostet wurde, oder eine Beschreibung einer Aktion, die das Streamelement generiert hat. Diese Spalte kann beliebige Formatierungen und eingebettete Ressourcen-Images enthalten, die von fromHtml() gerendert werden können. Der Anbieter kann lange Inhalte kürzen oder mit Ellipsen schneiden, versucht jedoch, zu vermeiden, dass Tags beschädigt werden.
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
Ein Textstring mit der Zeit, zu der das Streamelement eingefügt oder aktualisiert wurde, in Form von Millisekunden seit Epoche. Anwendungen, die Streamelemente einfügen oder aktualisieren, sind für die Verwaltung dieser Spalte verantwortlich. Sie wird nicht automatisch vom Contacts Provider gepflegt.

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 deine Stream-Elemente anzuzeigen, um eine Verknüpfung zu Ressourcen in deiner App herzustellen.

Die Tabelle „android.provider.ContactsContract.StreamItems“ enthält auch die Spalten „android.provider.ContactsContract.StreamItemsColumns#SYNC1 through android.provider.ContactsContract.StreamItemsColumns#SYNC4“ für die ausschließliche Verwendung von Synchronisierungsadaptern.

Fotos aus sozialen Streams

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 enthält einen Link zu Werten in der Spalte _ID der Tabelle „android.provider.ContactsContract.StreamItems“. Fotoreferenzen werden in der Tabelle in den folgenden Spalten gespeichert:

Spalte android.provider.ContactsContract.StreamItemPhotos#PHOTO (ein BLOB).
Eine binäre Darstellung des Fotos, deren Größe zum Speichern und Anzeigen vom Anbieter angepasst wird. Diese Spalte ist aus Gründen der Abwärtskompatibilität mit früheren Versionen von 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 im Folgenden beschrieben), um Fotos in einer Datei zu speichern. Diese Spalte enthält jetzt eine Miniaturansicht des Fotos, die zum Lesen angezeigt wird.
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 einen Handle für die Fotodatei zu erhalten.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
Ein Inhalts-URI, der direkt auf die Fotodatei für das durch diese Zeile dargestellte Foto verweist. Rufen Sie openAssetFileDescriptor() mit diesem URI auf, um einen 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. Um Daten aus ihnen zu lesen, benötigt deine App die Berechtigung „android.Manifest.permission#READ_SOCIAL_STREAM“. Wenn du sie ändern möchtest, muss deine App 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, macht der Contacts Provider Platz für neue Streamelement-Zeilen, indem er automatisch die Zeilen mit dem ältesten android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP löscht. Um das Limit zu ermitteln, führen Sie eine Abfrage an den Inhalts-URI android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI aus. Sie können alle Argumente mit Ausnahme des Inhalts-URI auf null gesetzt lassen. Die Abfrage gibt einen Cursor zurück, der eine einzelne Zeile mit der einzelnen Spalte android.provider.ContactsContract.StreamItems#MAX_ITEMS enthält.

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

Interaktionen in sozialen Streams

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

  • Wenn Sie Ihr soziales Netzwerk über einen Synchronisierungsadapter mit dem Kontakteanbieter synchronisieren, können Sie die letzten Aktivitäten der Kontakte eines Nutzers abrufen und in den Tabellen „android.provider.ContactsContract.StreamItems“ und „android.provider.ContactsContract.StreamItemPhotos“ zur späteren Verwendung speichern.
  • Neben der regulären Synchronisierung können Sie den Synchronisierungsadapter so einstellen, dass zusätzliche Daten abgerufen werden, wenn der Nutzer einen Kontakt zur Ansicht auswählt. Dadurch kann der Synchronisierungsadapter Fotos mit hoher Auflösung und die neuesten Stream-Elemente für den Kontakt abrufen.
  • Wenn Sie eine Benachrichtigung in der Kontakte-Anwendung des Geräts und beim Kontakteanbieter registrieren, können Sie einen Intent erhalten, wenn ein Kontakt angesehen wird, und zu diesem Zeitpunkt den Status des Kontakts über Ihren Dienst aktualisieren. Dieser Ansatz ist möglicherweise schneller und benötigt weniger Bandbreite als eine vollständige Synchronisierung mit einem Synchronisierungsadapter.
  • Nutzer können Ihrem sozialen Netzwerk einen Kontakt hinzufügen, während sie sich den Kontakt in der Kontakte-App auf dem Gerät ansehen. Dazu verwenden Sie die Funktion „Kontakt einladen“, die Sie aus einer Kombination aus einer Aktivität, bei der ein vorhandener Kontakt zu Ihrem Netzwerk hinzugefügt wird, und einer XML-Datei aktivieren, die die Kontaktanwendung des Geräts und den Kontaktanbieter mit den Details Ihrer Anwendung bereitstellt.

Die reguläre Synchronisierung von Streamelementen mit dem Contacts Provider ist identisch mit anderen Synchronisierungen. Weitere Informationen zur Synchronisierung finden Sie im Abschnitt Synchronisierungsadapter für Contact Provider. Das Registrieren von Benachrichtigungen und das Einladen von Kontakten werden in den nächsten beiden Abschnitten behandelt.

Für die Bearbeitung von Aufrufen in sozialen Netzwerken registrieren

So registrieren Sie Ihren Synchronisierungsadapter, um Benachrichtigungen zu erhalten, wenn der Nutzer einen Kontakt aufruft, der über Ihren Synchronisierungsadapter verwaltet wird:

  1. Erstellen Sie eine Datei mit dem Namen contacts.xml im Verzeichnis res/xml/ Ihres Projekts. Wenn Sie diese Datei bereits haben, können Sie diesen Schritt überspringen.
  2. Fügen Sie in dieser Datei das Element <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> hinzu. Wenn dieses Element bereits vorhanden ist, können Sie diesen Schritt überspringen.
  3. Zum Registrieren eines Dienstes, der benachrichtigt wird, wenn der Nutzer die Detailseite eines Kontakts in der Kontakte-Anwendung des Geräts öffnet, fügen Sie dem Element das Attribut viewContactNotifyService="serviceclass" hinzu. Dabei ist serviceclass der voll qualifizierte Klassenname des Dienstes, der den Intent von der Kontakte-Anwendung des Geräts erhalten soll. Verwenden Sie für den Notifier-Dienst 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. Vom Notifier-Dienst aus können Sie eine Bindung an Ihren Synchronisierungsadapter herstellen und diesen dann 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 Stream-Element, ein Foto oder beides klickt:

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

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

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

Interaktion mit Ihrem sozialen Netzwerk

Nutzer müssen die Kontaktanwendung auf dem Gerät nicht verlassen, um einen Kontakt zu deinem sozialen Netzwerk einzuladen. Stattdessen kannst du festlegen, dass die Kontakte-App des Geräts einen Intent sendet, um den Kontakt zu einer deiner Aktivitäten einzuladen. So richtest du ein Konto mit Elternaufsicht ein:

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

Hinweis:ContactsSource ist ein verworfener 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 Kontakteanwendung und dem Contacts Provider steuern. Diese Elemente werden in den folgenden Abschnitten beschrieben.

Element <ContactsAccountType>

Über das Element <ContactsAccountType> wird die Interaktion der App mit der Kontakte App gesteuert. Sie hat die folgende Syntax:

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

Description:

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

Das Attributpräfix android: ist für die Attribute von <ContactsAccountType> nicht erforderlich.

Attribute:

inviteContactActivity
Der voll qualifizierte Klassenname der Aktivität in Ihrer App, die Sie aktivieren möchten, wenn der Nutzer in der Kontakte-App des Geräts Verbindung hinzufügen auswählt.
inviteContactActionLabel
Ein Textstring, der für die in inviteContactActivity im Menü Verbindung hinzufügen angegebene Aktivität angezeigt wird. Sie können beispielsweise den String „In meinem Netzwerk folgen“ verwenden. Sie können für dieses Label eine String-Ressourcen-ID verwenden.
viewContactNotifyService
Der voll qualifizierte Klassenname eines Dienstes in Ihrer Anwendung, der Benachrichtigungen erhalten soll, wenn der Nutzer einen Kontakt aufruft. Diese Benachrichtigung wird von der Kontakte-Anwendung des Geräts gesendet. Ihre Anwendung kann dadurch datenintensive Vorgänge aufschieben, bis sie benötigt werden. Ihre Anwendung kann beispielsweise auf diese Benachrichtigung reagieren, indem sie das hochauflösende Foto des Kontakts und die neuesten Elemente aus sozialen Streams einliest und anzeigt. Weitere Informationen zu dieser Funktion finden Sie im Abschnitt Interaktionen in sozialen Netzwerken.
viewGroupActivity
Der voll qualifizierte Klassenname einer Aktivität in Ihrer Anwendung, die Gruppeninformationen anzeigen kann. Wenn der Nutzer in der Kontakte-App des Geräts auf das Gruppenlabel klickt, wird die UI für diese Aktivität angezeigt.
viewGroupActionLabel
Das Label, das in der Kontakteanwendung für ein UI-Steuerelement angezeigt wird, mit dem sich Nutzer Gruppen in Ihrer Anwendung ansehen können.

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

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

Element <ContactsDataKind>

Mit dem Element <ContactsDataKind> wird die Anzeige der benutzerdefinierten Datenzeilen der Anwendung auf der Benutzeroberfläche der Kontaktanwendung gesteuert. Sie hat die folgende Syntax:

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

enthalten in:

<ContactsAccountType>

Description:

Verwenden Sie dieses Element, damit die Kontakteanwendung den Inhalt einer benutzerdefinierten Datenzeile als Teil der Details eines Rohkontakts anzeigt. Jedes untergeordnete <ContactsDataKind>-Element von <ContactsAccountType> stellt einen Typ von benutzerdefinierten Datenzeilen dar, die vom Synchronisierungsadapter der Tabelle ContactsContract.Data hinzugefügt werden. Fügen Sie für jeden von Ihnen verwendeten benutzerdefinierten MIME-Typ ein <ContactsDataKind>-Element hinzu. Wenn Sie eine benutzerdefinierte Datenzeile haben, für die Sie keine Daten anzeigen möchten, müssen Sie das Element nicht hinzufügen.

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 Drawable-Ressource, die in der Kontakte-App neben deinen Daten angezeigt wird. Hiermit können Sie den Nutzer darüber informieren, dass die Daten von 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 dient als Zusammenfassung der Daten, ist aber optional. Weitere Informationen finden Sie unter 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. Weitere Informationen finden Sie unter android:summaryColumn.

Zusätzliche Funktionen von Anbietern von Kontakten

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

  • Kontaktgruppen
  • Fotofunktionen

Kontaktgruppen

Der Contacts Provider kann Sammlungen zusammengehöriger 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 Kontaktanbieter und dem Server übertragen. Wenn Nutzer dem Server einen neuen Kontakt hinzufügen und diesen Kontakt dann in einer neuen Gruppe ablegen, muss die neue Gruppe vom Synchronisierungsadapter der Tabelle ContactsContract.Groups hinzugefügt werden. Die Gruppe oder Gruppen, zu denen ein Rohkontakt gehört, werden mit dem MIME-Typ ContactsContract.CommonDataKinds.GroupMembership in der Tabelle ContactsContract.Data gespeichert.

Wenn Sie einen Synchronisierungsadapter entwerfen, mit dem dem Contact Provider rohe Kontaktdaten vom Server hinzugefügt werden, und Sie keine Gruppen verwenden, müssen Sie den Anbieter bitten, Ihre Daten sichtbar zu machen. Aktualisiere in dem Code, der ausgeführt wird, wenn ein Nutzer dem Gerät ein Konto hinzufügt, die Zeile ContactsContract.Settings, die der Kontaktanbieter 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.

Fotos von Kontakten

In der Tabelle ContactsContract.Data werden Fotos als Zeilen mit dem MIME-Typ Photo.CONTENT_ITEM_TYPE gespeichert. Die Spalte CONTACT_ID der Zeile ist mit der Spalte _ID des Rohkontakts verknüpft, zu dem sie gehört. Die Klasse ContactsContract.Contacts.Photo definiert eine Untertabelle von ContactsContract.Contacts mit Fotoinformationen für das primäre Foto eines Kontakts, also das Hauptfoto des primären Rohkontakts des Kontakts. In ähnlicher Weise definiert die Klasse ContactsContract.RawContacts.DisplayPhoto eine Untertabelle von ContactsContract.RawContacts, die Fotoinformationen für das Hauptfoto 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 der primären Miniaturansicht eines Rohkontakts. Sie können jedoch eine Abfrage an die Tabelle ContactsContract.Data senden und dabei die Spalte _ID des Rohkontakts, Photo.CONTENT_ITEM_TYPE und IS_PRIMARY auswählen, um die Zeile mit dem primären Foto des Rohkontakts zu finden.

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