連絡先プロバイダ

連絡先プロバイダは、デバイスのユーザーに関するデータの中央リポジトリを管理する、強力で柔軟な Android コンポーネントです。連絡先プロバイダは、デバイスの連絡先アプリケーションに表示されるデータのソースです。また、独自のアプリケーションでそのデータにアクセスし、デバイスとオンライン サービスの間でデータを転送することもできます。このプロバイダは、幅広いデータソースに対応し、各ユーザーについてできるだけ多くのデータを管理しようとするため、その組織は複雑になっています。このため、プロバイダの API には、データの取得と変更の両方を容易にする、広範なコントラクト クラスとインターフェースが含まれています。

このガイドでは、以下の方法について説明します。

  • 基本的なプロバイダ構造。
  • プロバイダからデータを取得する方法。
  • プロバイダのデータを変更する方法。
  • サーバーから連絡先プロバイダにデータを同期するための同期アダプターを作成する方法。

このガイドでは、Android コンテンツ プロバイダの基本を理解していることを前提としています。Android コンテンツ プロバイダの詳細については、 コンテンツ プロバイダの基本ガイドをご覧ください。

連絡先プロバイダの組織

連絡先プロバイダは、Android のコンテンツ プロバイダ コンポーネントです。このプロバイダは、図 1 に示すように、ユーザーに関する 3 種類のデータを保持しており、それぞれがプロバイダによって提供されるテーブルに対応しています。

図 1. 連絡先プロバイダのテーブル構造。

この 3 つのテーブルは、通常、コントラクト クラスの名前で参照されます。これらのクラスは、テーブルで使用されるコンテンツ URI、列名、列値の定数を定義します。

ContactsContract.Contacts テーブル
未加工連絡先の行の集計に基づいて、異なる人物を表す行。
ContactsContract.RawContacts テーブル
ユーザー アカウントとタイプに固有の、ユーザーのデータの概要を含む行。
ContactsContract.Data テーブル
未加工連絡先の詳細(メールアドレスや電話番号など)を含む行。

ContactsContract のコントラクト クラスで表される他のテーブルは、連絡先プロバイダがオペレーションを管理したり、デバイスの連絡先アプリや電話アプリの特定の機能をサポートしたりするために使用する補助テーブルです。

未加工連絡先

未加工連絡先は、1 つのアカウントの種類とアカウント名から取得された個人のデータを表します。連絡先プロバイダでは、1 人のユーザーのデータソースとして複数のオンライン サービスを使用できるため、同じユーザーに対して複数の未加工連絡先を使用できます。また、複数の未加工連絡先を使用すると、ユーザーは同じアカウント タイプの複数のアカウントから 1 人のユーザーのデータを結合することもできます。

未加工連絡先のほとんどのデータは、ContactsContract.RawContacts テーブルに保存されません。代わりに、ContactsContract.Data テーブルの 1 つ以上の行に保存されます。各データ行には、親 ContactsContract.RawContacts 行の RawContacts._ID 値を含む Data.RAW_CONTACT_ID 列があります。

重要な未加工連絡先列

ContactsContract.RawContacts テーブルの重要な列を表 1 に示します。表の後に続く注記をお読みください。

表 1. 重要な未加工連絡先の列。

列名 使用 メモ
ACCOUNT_NAME この未加工連絡先のソースであるアカウントの種類のアカウント名。 たとえば、Google アカウントのアカウント名は、デバイス所有者の Gmail アドレスの 1 つです。詳細については、次の ACCOUNT_TYPE のエントリをご覧ください。 この名前の形式は、アカウントの種類によって異なります。メールアドレスである必要はありません。
ACCOUNT_TYPE この未加工連絡先のソースであるアカウントの種類。たとえば、Google アカウントのアカウント タイプは com.google です。アカウントの種類は、所有または管理しているドメインのドメイン識別子で常に修飾してください。これにより、アカウントの種類が一意になります。 連絡先データを提供するアカウント タイプには、通常、連絡先プロバイダと同期する同期アダプターが関連付けられています。
DELETED 未加工連絡先の「削除済み」フラグ。 このフラグにより、連絡先プロバイダは、同期アダプターがサーバーから行を削除し、最終的にリポジトリから行を削除できるようになるまで、内部で行を維持できます。

メモ

ContactsContract.RawContacts テーブルに関する重要な注意事項は次のとおりです。

  • 未加工連絡先の名前は、ContactsContract.RawContacts の行には保存されません。代わりに、ContactsContract.Data テーブルの ContactsContract.CommonDataKinds.StructuredName 行に保存されます。未加工連絡先には、ContactsContract.Data テーブルにこのタイプの行が 1 つだけあります。
  • 注意: 未加工連絡先の行で独自のアカウント データを使用するには、まず AccountManager に登録する必要があります。これを行うには、アカウントの種類とアカウント名をアカウントのリストに追加するようユーザーに促します。この処理を行わないと、連絡先プロバイダによって未加工連絡先の行が自動的に削除されます。

    たとえば、ドメイン com.example.dataservice を持つウェブベースのサービス用にアプリで連絡先データを保持し、そのサービス用のユーザーのアカウントが becky.sharp@dataservice.example.com である場合、アプリで未加工連絡先の行を追加する前に、ユーザーがアカウントの「タイプ」(com.example.dataservice)とアカウントの「名前」(becky.smart@dataservice.example.com)を追加する必要があります。この要件をドキュメントでユーザーに説明することも、タイプと名前、またはその両方を追加するようユーザーに求めることもできます。アカウントの種類とアカウント名については、次のセクションで詳しく説明します。

未加工連絡先データのソース

未加工連絡先の仕組みを理解するために、デバイスに次の 3 つのユーザー アカウントが定義されている「Emily Dickinson」というユーザーを考えてみましょう。

  • emily.dickinson@gmail.com
  • emilyd@gmail.com
  • Twitter アカウント「belle_of_amherst」

このユーザーは、[アカウント] 設定で、これら 3 つのアカウントすべてに対して [連絡先を同期] を有効にしています。

Emily Dickinson がブラウザ ウィンドウを開き、emily.dickinson@gmail.com として Gmail にログインし、連絡先を開いて「Thomas Higginson」を追加したとします。その後、emilyd@gmail.com として Gmail にログインし、「Thomas Higginson」にメールを送信すると、このユーザーが連絡先として自動的に追加されます。また、Twitter で「colonel_tom」(Thomas Higginson の Twitter ID)をフォローしています。

この処理の結果、連絡先プロバイダは 3 つの未加工連絡先を作成します。

  1. emily.dickinson@gmail.com に関連付けられた「Thomas Higginson」の未加工連絡先。 ユーザー アカウントの種類は Google です。
  2. emilyd@gmail.com に関連付けられた「Thomas Higginson」の 2 つ目の未加工連絡先。 ユーザー アカウントの種類も Google です。名前が以前の名前と同じであっても、別のユーザー アカウントに追加されたため、2 つ目の未加工連絡先が存在します。
  3. 「belle_of_amherst」に関連付けられた「Thomas Higginson」の 3 つ目の未加工連絡先。ユーザー アカウントの種類は Twitter です。

データ

前述のように、未加工連絡先のデータは、未加工連絡先の _ID 値にリンクされた ContactsContract.Data 行に保存されます。これにより、1 つの未加工連絡先に、メールアドレスや電話番号など、同じ種類のデータの複数のインスタンスを格納できます。たとえば、emilyd@gmail.com の「Thomas Higginson」(Google アカウント emilyd@gmail.com に関連付けられている Thomas Higginson の未加工連絡先)に、自宅のメールアドレス thigg@gmail.com と仕事用メールアドレス thomas.higginson@gmail.com がある場合、連絡先プロバイダは 2 つのメールアドレス行を保存し、両方を未加工連絡先にリンクします。

この 1 つのテーブルにさまざまな種類のデータが保存されていることに注目してください。表示名、電話番号、メール、住所、写真、ウェブサイトの詳細行はすべて ContactsContract.Data テーブルにあります。この管理を容易にするため、ContactsContract.Data テーブルには、わかりやすい名前の列と、一般的な名前の列があります。説明的な名前の列の内容は、行のデータの種類に関係なく同じ意味を持ちますが、一般的な名前の列の内容は、データの種類によって意味が異なります。

わかりやすい列名

わかりやすい列名の例を次に示します。

RAW_CONTACT_ID
このデータの未加工連絡先の _ID 列の値。
MIMETYPE
この行に格納されているデータのタイプ。カスタム MIME タイプとして表されます。連絡先プロバイダは、ContactsContract.CommonDataKinds のサブクラスで定義された MIME タイプを使用します。これらの MIME タイプはオープンソースであり、連絡先プロバイダと連携する任意のアプリまたは同期アダプターで使用できます。
IS_PRIMARY
このタイプのデータ行が未加工連絡先に対して複数回発生する可能性がある場合、IS_PRIMARY 列は、そのタイプのプライマリ データを含むデータ行にフラグを設定します。たとえば、ユーザーが連絡先の電話番号を長押しして [デフォルトを設定] を選択すると、その番号を含む ContactsContract.Data 行の IS_PRIMARY 列がゼロ以外の値に設定されます。

一般的な列名

一般的に使用可能な DATA1 から DATA15 までの 15 個の汎用列と、同期アダプターでのみ使用する必要がある SYNC1 から SYNC4 までの 4 個の汎用列があります。汎用列名の定数は、行に含まれるデータの種類に関係なく常に機能します。

DATA1 列はインデックスに登録されています。連絡先プロバイダは、クエリの対象となる頻度が最も高いと予想されるデータに、常にこの列を使用します。たとえば、メール行では、この列に実際のメールアドレスが含まれます。

慣例として、DATA15 列は、写真のサムネイルなどのバイナリ ラージ オブジェクト(BLOB)データを保存するために予約されています。

タイプ固有の列名

特定のタイプの行の列を簡単に操作できるように、連絡先プロバイダには、ContactsContract.CommonDataKinds のサブクラスで定義された、タイプ固有の列名定数も用意されています。定数は、同じ列名に異なる定数名を付けるだけで、特定の型の行のデータにアクセスするのに役立ちます。

たとえば、ContactsContract.CommonDataKinds.Email クラスは、MIME タイプが Email.CONTENT_ITEM_TYPE の ContactsContract.Data 行のタイプ固有の列名定数を定義します。このクラスには、メールアドレス列の定数 ADDRESS が含まれています。ADDRESS の実際の値は「data1」です。これは列の汎用名と同じです。

注意: プロバイダの事前定義された MIME タイプのいずれかを持つ行を使用して、独自のカスタムデータを ContactsContract.Data テーブルに追加しないでください。変更すると、データが失われたり、プロバイダが正常に機能しなくなったりする可能性があります。たとえば、MIME タイプが Email.CONTENT_ITEM_TYPE の行を追加する場合、DATA1 列にメールアドレスではなくユーザー名を入力することはできません。行に独自のカスタム MIME タイプを使用する場合は、タイプ固有の列名を自由に定義し、列を自由に使用できます。

図 2 は、説明列とデータ列が ContactsContract.Data 行にどのように表示されるか、およびタイプ固有の列名が汎用列名にどのように「オーバーレイ」されるかを示しています。

タイプ固有の列名が一般的な列名にマッピングされる仕組み

図 2. タイプ固有の列名と一般的な列名。

タイプ固有の列名クラス

表 2 に、よく使用される型固有の列名クラスを示します。

表 2. タイプ固有の列名クラス

マッピング クラス データの種類 メモ
ContactsContract.CommonDataKinds.StructuredName このデータ行に関連付けられている未加工連絡先の名前データ。 未加工連絡先には、これらの行のうち 1 つだけが含まれます。
ContactsContract.CommonDataKinds.Photo このデータ行に関連付けられている未加工連絡先のメインの写真。 未加工連絡先には、これらの行のうち 1 つだけが含まれます。
ContactsContract.CommonDataKinds.Email このデータ行に関連付けられている未加工連絡先のメールアドレス。 未加工連絡先には複数のメールアドレスを設定できます。
ContactsContract.CommonDataKinds.StructuredPostal このデータ行に関連付けられている未加工連絡先の住所。 1 つの未加工連絡先に複数の住所を設定できます。
ContactsContract.CommonDataKinds.GroupMembership 未加工連絡先を連絡先プロバイダのグループの 1 つにリンクする識別子。 グループは、アカウントの種類とアカウント名のオプション機能です。詳しくは、連絡先グループのセクションをご覧ください。

連絡先

連絡先プロバイダは、すべてのアカウントの種類とアカウント名にわたる未加工連絡先の行を組み合わせて、連絡先を形成します。これにより、ユーザーが収集した人物に関するすべてのデータを簡単に表示、変更できます。連絡先プロバイダは、新しい連絡先行の作成と、未加工連絡先と既存の連絡先行の集約を管理します。アプリも同期アダプタも連絡先を追加することはできず、連絡先行の一部の列は読み取り専用です。

注: insert() を使用して連絡先プロバイダに連絡先を追加しようとすると、UnsupportedOperationException 例外が発生します。「読み取り専用」としてリストされている列を更新しようとすると、更新は無視されます。

連絡先プロバイダは、既存の連絡先と一致しない新しい未加工連絡先の追加に応じて、新しい連絡先を作成します。また、既存の未加工連絡先のデータが変更され、以前に関連付けられていた連絡先と一致しなくなった場合も、プロバイダは同じ処理を行います。アプリまたは同期アダプタが、既存の連絡先と一致する新しい未加工連絡先を作成した場合、新しい未加工連絡先は既存の連絡先に集約されます。

連絡先プロバイダは、Contacts テーブルの連絡先行の _ID 列を使用して、連絡先行を未加工連絡先行にリンクします。未加工連絡先テーブル ContactsContract.RawContacts の CONTACT_ID 列には、各未加工連絡先行に関連付けられた連絡先行の _ID 値が含まれます。

ContactsContract.Contacts テーブルには、連絡先行への「永続的な」リンクである LOOKUP_KEY 列もあります。連絡先プロバイダは連絡先を自動的に管理するため、集計や同期に応じて連絡先の行の _ID 値を変更することがあります。この場合でも、コンテンツ URI CONTENT_LOOKUP_URI と連絡先の LOOKUP_KEY を組み合わせると、連絡先の行を指すため、LOOKUP_KEY を使用して「お気に入り」の連絡先へのリンクを維持できます。この列には、_ID 列の形式とは無関係の独自の形式があります。

図 3 は、3 つのメインテーブルが相互にどのように関連しているかを示しています。

連絡先プロバイダのメインテーブル

図 3. Contacts、未加工連絡先、Details の各テーブルの関係。

注意: アプリを Google Play ストアに公開する場合、またはアプリが Android 10(API レベル 29)以降を搭載したデバイスにインストールされている場合は、連絡先データ フィールドとメソッドの一部が廃止されていることに注意してください。

前述の条件に該当する場合、システムは、これらのデータ フィールドに書き込まれた値を定期的に消去します。

上記のデータ フィールドを設定するために使用される API も廃止されました。

また、次のフィールドでは、よく使う連絡先が返されなくなりました。なお、これらのフィールドの一部は、連絡先が特定のデータの種類の一部である場合にのみ、連絡先のランキングに影響します。

アプリがこれらのフィールドまたは API にアクセスまたは更新している場合は、別の方法を使用してください。たとえば、非公開のコンテンツ プロバイダや、アプリまたはバックエンド システム内に保存されている他のデータを使用することで、特定のユースケースに対応できます。

この変更によってアプリの機能が影響を受けないことを確認するには、これらのデータ フィールドを手動で消去します。これを行うには、Android 4.1(API レベル 16)以降を搭載したデバイスで次の ADB コマンドを実行します。

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

同期アダプターからのデータ

ユーザーは連絡先データをデバイスに直接入力しますが、データは同期アダプターを介してウェブサービスから連絡先プロバイダにも流れます。同期アダプターは、デバイスとサービス間のデータ転送を自動化します。同期アダプタは、システムの制御下でバックグラウンドで実行され、ContentResolver メソッドを呼び出してデータを管理します。

Android では、同期アダプターが連携するウェブサービスはアカウント タイプによって識別されます。 各同期アダプタは 1 つのアカウント タイプで動作しますが、そのタイプに対して複数のアカウント名をサポートできます。アカウントの種類とアカウント名については、未加工連絡先データのソースで簡単に説明しています。以下の定義では、アカウントの種類と名前が同期アダプタとサービスにどのように関連しているかについて詳しく説明します。

アカウントの種類
ユーザーがデータを保存しているサービスを識別します。ほとんどの場合、ユーザーはサービスで認証を行う必要があります。たとえば、Google コンタクトはアカウントの種類であり、コード google.com で識別されます。この値は、AccountManager で使用されるアカウントの種類に対応しています。
アカウント名
アカウントの種類のアカウントまたはログインを識別します。Google コンタクト アカウントは Google アカウントと同じで、アカウント名としてメールアドレスを使用します。他のサービスでは、1 語のユーザー名または数値 ID を使用することがあります。

アカウントの種類は一意である必要はありません。ユーザーは複数の Google コンタクト アカウントを設定し、そのデータを連絡先プロバイダにダウンロードできます。これは、ユーザーが個人アカウント名に 1 つの個人用連絡先セットを持ち、仕事用にもう 1 つのセットを持っている場合に発生する可能性があります。アカウント名は通常一意です。これらを組み合わせることで、連絡先プロバイダと外部サービス間の特定のデータフローを識別します。

サービスのデータを連絡先プロバイダに転送する場合は、独自の同期アダプタを作成する必要があります。詳しくは、連絡先プロバイダの同期アダプターをご覧ください。

図 4 は、連絡先プロバイダがユーザーに関するデータのフローにどのように適合するかを示しています。[同期アダプター] というボックスでは、各アダプターにアカウントの種類を示すラベルが付いています。

ユーザーに関するデータの流れ

図 4. 連絡先プロバイダのデータフロー。

必要な権限

連絡先プロバイダにアクセスするアプリは、次の権限をリクエストする必要があります。

1 つ以上のテーブルに対する読み取りアクセス権
READ_CONTACTS。AndroidManifest.xml で <uses-permission> 要素を <uses-permission android:name="android.permission.READ_CONTACTS"> として指定します。
1 つ以上のテーブルへの書き込みアクセス権
WRITE_CONTACTS。AndroidManifest.xml で <uses-permission> 要素を <uses-permission android:name="android.permission.WRITE_CONTACTS"> として指定します。

これらの権限は、ユーザー プロフィール データには適用されません。ユーザー プロファイルと必要な権限については、次のセクションのユーザー プロファイルをご覧ください。

ユーザーの連絡先データは個人情報や機密情報です。ユーザーはプライバシーを懸念しているため、アプリがユーザー自身や連絡先に関するデータを収集することを望んでいません。連絡先データへのアクセス権限が必要な理由が明確でない場合、ユーザーはアプリに低い評価を付けたり、インストールを拒否したりする可能性があります。

ユーザー プロフィール

ContactsContract.Contacts テーブルには、デバイスのユーザーのプロファイル データを含む 1 つの行があります。このデータは、ユーザーの連絡先ではなく、デバイスの user を表します。プロファイル連絡先の行は、プロファイルを使用するシステムごとに未加工連絡先の行にリンクされます。各プロファイルの未加工連絡先の行には、複数のデータ行を含めることができます。ユーザー プロファイルにアクセスするための定数は、ContactsContract.Profile クラスで使用できます。

ユーザー プロフィールにアクセスするには、特別な権限が必要です。読み取りと書き込みに必要な READ_CONTACTS 権限と WRITE_CONTACTS 権限に加えて、ユーザー プロファイルにアクセスするには、読み取りアクセスと書き込みアクセスにそれぞれ android.Manifest.permission#READ_PROFILE 権限と android.Manifest.permission#WRITE_PROFILE 権限が必要です。

ユーザーのプロフィールは機密情報として扱う必要があります。権限 android.Manifest.permission#READ_PROFILE を使用すると、デバイス ユーザーの個人を特定できるデータにアクセスできます。アプリの説明で、ユーザー プロフィールへのアクセス権限が必要な理由をユーザーに伝えるようにしてください。

ユーザーのプロフィールを含む連絡先行を取得するには、ContentResolver.query() を呼び出します。コンテンツ URI を CONTENT_URI に設定し、選択条件を指定しません。このコンテンツ URI を、プロファイルの未加工連絡先またはデータを取得するためのベース URI として使用することもできます。たとえば、次のスニペットはプロファイルのデータを取得します。

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

注: 複数の連絡先行を取得し、そのうちの 1 つがユーザー プロフィールかどうかを判断する場合は、行の IS_USER_PROFILE 列をテストします。連絡先がユーザー プロフィールの場合は、この列が「1」に設定されます。

連絡先プロバイダのメタデータ

連絡先プロバイダは、リポジトリ内の連絡先データの状態を追跡するデータを管理します。このリポジトリに関するメタデータは、未加工連絡先、データ、連絡先テーブルの行、ContactsContract.Settings テーブル、ContactsContract.SyncState テーブルなど、さまざまな場所に保存されます。次の表に、これらのメタデータの影響を示します。

表 3: 連絡先プロバイダのメタデータ

テーブル 列 値 意味
ContactsContract.RawContacts DIRTY 「0」- 前回の同期以降に変更されていません。 デバイス上で変更され、サーバーに同期する必要がある未加工連絡先をマークします。この値は、Android アプリケーションが行を更新するときに、連絡先プロバイダによって自動的に設定されます。

未加工連絡先またはデータテーブルを変更する同期アダプタは、使用するコンテンツ URI に常に文字列 CALLER_IS_SYNCADAPTER を追加する必要があります。これにより、プロバイダが行をダーティとしてマークするのを防ぐことができます。 そうしないと、同期アダプタの変更がローカルの変更として表示され、サーバーが変更のソースであってもサーバーに送信されます。

「1」- 最後の同期以降に変更されたため、サーバーに同期し直す必要があります。
ContactsContract.RawContacts VERSION この行のバージョン番号。 連絡先プロバイダは、行または関連するデータが変更されるたびに、この値を自動的にインクリメントします。
ContactsContract.Data DATA_VERSION この行のバージョン番号。 連絡先プロバイダは、データ行が変更されるたびにこの値を自動的にインクリメントします。
ContactsContract.RawContacts SOURCE_ID 作成されたアカウントに対してこの未加工連絡先を一意に識別する文字列値。 同期アダプタが新しい未加工連絡先を作成するときに、この列は、未加工連絡先のサーバーの一意の ID に設定する必要があります。Android アプリケーションが新しい未加工連絡先を作成する場合、アプリケーションはこの列を空のままにする必要があります。これにより、サーバーに新しい未加工連絡先を作成し、SOURCE_ID の値を取得する必要があることが同期アダプタに通知されます。

特に、ソース ID はアカウント タイプごとに一意である必要があり、同期全体で安定している必要があります。

  • 一意: アカウントの未加工連絡先ごとに、独自のソース ID が必要です。これを適用しないと、連絡先アプリケーションで問題が発生します。 同じアカウント タイプの 2 つの未加工連絡先が、同じソース ID を持つ場合があることに注意してください。たとえば、アカウント emily.dickinson@gmail.com の未加工連絡先「Thomas Higginson」は、アカウント emilyd@gmail.com の未加工連絡先「Thomas Higginson」と同じソース ID を持つことができます。
  • 安定: ソース ID は、未加工連絡先のオンライン サービスのデータの一部として永続的に存在します。たとえば、ユーザーが [アプリ] 設定から [連絡先の保存] を消去して再同期した場合、復元された未加工連絡先は以前と同じソース ID を持つ必要があります。これを適用しないと、ショートカットは機能しなくなります。
ContactsContract.Groups GROUP_VISIBLE 「0」- このグループの連絡先は、Android アプリの UI に表示されません。 この列は、ユーザーが特定のグループの連絡先を非表示にできるサーバーとの互換性を確保するためのものです。
「1」- このグループの連絡先は、アプリケーション UI に表示できます。
ContactsContract.Settings UNGROUPED_VISIBLE "0" - このアカウントとアカウント タイプでは、グループに属していない連絡先は Android アプリの UI に表示されません。 デフォルトでは、未加工連絡先がグループに属していない場合、連絡先は表示されません(未加工連絡先のグループ メンバーシップは、ContactsContract.Data テーブルの 1 つ以上の ContactsContract.CommonDataKinds.GroupMembership 行で示されます)。アカウントの種類とアカウントの ContactsContract.Settings テーブル行にこのフラグを設定すると、グループのない連絡先を強制的に表示できます。このフラグの用途の 1 つは、グループを使用しないサーバーの連絡先を表示することです。
"1" - このアカウントとアカウント タイプでは、グループに属していない連絡先がアプリの UI に表示されます。
ContactsContract.SyncState (すべて) このテーブルは、同期アダプタのメタデータを保存するために使用します。 このテーブルを使用すると、同期状態やその他の同期関連データをデバイスに永続的に保存できます。

連絡先プロバイダへのアクセス

このセクションでは、連絡先プロバイダからデータにアクセスするためのガイドラインについて説明します。特に、次の点に重点を置いています。

  • エンティティ クエリ。
  • 一括変更。
  • インテントを使用した取得と変更。
  • データの完全性。

同期アダプターから変更を加える方法については、連絡先プロバイダの同期アダプターのセクションで詳しく説明します。

エンティティのクエリ

連絡先プロバイダのテーブルは階層構造になっているため、1 つの行と、その行にリンクされているすべての「子」行を取得すると便利なことがよくあります。たとえば、あるユーザーのすべての情報を表示するには、1 つの ContactsContract.Contacts 行のすべての ContactsContract.RawContacts 行、または 1 つの ContactsContract.RawContacts 行のすべての ContactsContract.CommonDataKinds.Email 行を取得します。これを容易にするために、連絡先プロバイダは、テーブル間のデータベース結合のように機能するエンティティ構造を提供します。

エンティティは、親テーブルとその子テーブルから選択された列で構成されるテーブルのようなものです。 エンティティをクエリする場合は、エンティティから取得可能な列に基づいて、プロジェクションと検索条件を指定します。結果は、取得された子テーブルの行ごとに 1 行を含む Cursor です。たとえば、ContactsContract.Contacts.Entity に対して連絡先の名前と、その名前のすべての未加工連絡先のすべての ContactsContract.CommonDataKinds.Email 行をクエリすると、各 ContactsContract.CommonDataKinds.Email 行に対して 1 行を含む Cursor が返されます。

エンティティを使用すると、クエリが簡素化されます。エンティティを使用すると、最初に親テーブルをクエリして ID を取得し、次にその ID を使用して子テーブルをクエリするのではなく、連絡先または未加工連絡先のすべての連絡先データを一度に取得できます。また、連絡先プロバイダは、単一のトランザクションでエンティティに対するクエリを処理するため、取得されたデータの内部整合性が保証されます。

注: 通常、エンティティには親テーブルと子テーブルのすべての列が含まれているわけではありません。エンティティの列名定数のリストにない列名を操作しようとすると、Exception が返されます。

次のスニペットは、連絡先のすべての未加工連絡先行を取得する方法を示しています。このスニペットは、「メイン」と「詳細」の 2 つのアクティビティを持つ大規模なアプリケーションの一部です。メイン アクティビティには連絡先の行のリストが表示されます。ユーザーが 1 つ選択すると、アクティビティはその ID を詳細アクティビティに送信します。詳細アクティビティは、ContactsContract.Contacts.Entity を使用して、選択した連絡先に関連付けられているすべての未加工連絡先のすべてのデータ行を表示します。

このスニペットは「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.
}

読み込みが完了すると、LoaderManager は onLoadFinished() へのコールバックを呼び出します。このメソッドへの入力引数の 1 つは、クエリの結果を含む Cursor です。独自のアプリでは、この Cursor からデータを取得して表示したり、さらに操作したりできます。

一括変更

可能な限り、連絡先プロバイダのデータの挿入、更新、削除は「バッチモード」で行う必要があります。そのためには、ContentProviderOperation オブジェクトの ArrayList を作成し、applyBatch() を呼び出します。連絡先プロバイダは、applyBatch() 内のすべてのオペレーションを 1 つのトランザクションで実行するため、変更によって連絡先リポジトリが不整合状態になることはありません。バッチ変更を使用すると、未加工連絡先とその詳細データを同時に挿入することも容易になります。

注: 1 つの未加工連絡先を変更するには、アプリで変更を処理するのではなく、デバイスの連絡先アプリケーションにインテントを送信することを検討してください。これについては、インテントを使用した取得と変更のセクションで詳しく説明します。

明け渡し点

多数のオペレーションを含むバッチ変更は、他のプロセスをブロックし、全体的なユーザー エクスペリエンスの低下につながる可能性があります。実行するすべての変更をできるだけ少ない個別のリストに整理し、同時にシステムをブロックしないようにするには、1 つ以上のオペレーションに明け渡し点を設定する必要があります。明け渡し点は、isYieldAllowed() 値が true に設定された ContentProviderOperation オブジェクトです。連絡先プロバイダは、明け渡し点に到達すると、処理を一時停止して他のプロセスを実行できるようにし、現在のトランザクションを閉じます。プロバイダが再起動すると、ArrayList の次のオペレーションを続行し、新しいトランザクションを開始します。

明け渡し点は、applyBatch() の呼び出しごとに複数のトランザクションを生成します。このため、関連する一連の行の最後のオペレーションに明け渡し点を設定する必要があります。たとえば、未加工連絡先の行とそれに関連付けられたデータ行を追加する一連のオペレーションの最後のオペレーションや、1 つの連絡先に関連する一連の行の最後のオペレーションに、明け渡し点(#など適宜文脈に応じて)を設定する必要があります。

明け渡し点も、アトミック オペレーションの単位でもあります。2 つの yield ポイント間のすべてのアクセスは、1 つの単位として成功または失敗します。イールド ポイントを設定しない場合、最小のアトミック オペレーションはオペレーションのバッチ全体になります。明け渡し点を使用すると、オペレーションによるシステム パフォーマンスの低下を防ぐと同時に、オペレーションのサブセットをアトミックにすることができます。

変更のバックリファレンス

新しい未加工連絡先の行とそれに関連付けられたデータ行を ContentProviderOperation オブジェクトのセットとして挿入する場合は、未加工連絡先の _ID 値を RAW_CONTACT_ID 値として挿入することで、データ行を未加工連絡先の行にリンクする必要があります。ただし、この値は、データ行の ContentProviderOperation を作成するときには使用できません。未加工連絡先行の ContentProviderOperation がまだ適用されていないためです。この問題を回避するため、ContentProviderOperation.Builder クラスには withValueBackReference() メソッドが用意されています。このメソッドを使用すると、前のオペレーションの結果を含む列を挿入または変更できます。

withValueBackReference() メソッドには 2 つの引数があります。

key
Key-Value ペアのキー。この引数の値は、変更するテーブルの列の名前にする必要があります。
previousResult
applyBatch() の ContentProviderResult オブジェクトの配列内の値の 0 から始まるインデックス。バッチ オペレーションが適用されると、各オペレーションの結果が結果の中間配列に格納されます。previousResult 値は、これらの結果のいずれかのインデックスです。このインデックスは、key 値とともに取得されて保存されます。これにより、新しい未加工連絡先レコードを挿入してその _ID 値を取得し、ContactsContract.Data 行を追加するときにその値に「バック リファレンス」を作成できます。

結果配列全体は、最初に applyBatch() を呼び出したときに作成されます。サイズは、指定した ContentProviderOperation オブジェクトの ArrayList のサイズと同じです。ただし、結果配列内のすべての要素は null に設定されます。まだ適用されていないオペレーションの結果をバックリファレンスしようとすると、withValueBackReference() は Exception をスローします。

次のスニペットは、新しい未加工連絡先とデータを一括で挿入する方法を示しています。これらには、明け渡し点(#など適宜文脈に応じて)を確立し、後方参照を使用するコードが含まれています。

最初のスニペットは、UI から連絡先データを取得します。この時点で、ユーザーは新しい未加工連絡先を追加するアカウントをすでに選択しています。

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

次のスニペットでは、未加工連絡先の行を 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());

次に、表示名、電話番号、メールアドレスの行のデータ行を作成します。

各オペレーション ビルダー オブジェクトは、withValueBackReference() を使用して RAW_CONTACT_ID を取得します。この参照は、最初のオペレーションの ContentProviderResult オブジェクトを指します。このオペレーションは、未加工連絡先の行を追加し、新しい _ID 値を返します。その結果、各データ行は、その RAW_CONTACT_ID によって、属する新しい ContactsContract.RawContacts 行に自動的にリンクされます。

メール行を追加する ContentProviderOperation.Builder オブジェクトには withYieldAllowed() のフラグが付けられ、これにより明け渡し点が設定されます。

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

最後のスニペットは、新しい未加工連絡先とデータ行を挿入する applyBatch() の呼び出しを示しています。

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

バッチ オペレーションでは、オプティミスティック同時実行制御を実装することもできます。これは、基盤となるリポジトリをロックせずに変更トランザクションを適用する方法です。この方法を使用するには、トランザクションを適用してから、同時に行われた可能性のある他の変更を確認します。不整合な変更が発生した場合は、トランザクションをロールバックして再試行します。

楽観的同時実行制御は、一度に 1 人のユーザーしか存在せず、データ リポジトリへの同時アクセスがまれなモバイル デバイスに役立ちます。ロックは使用されないため、ロックの設定や、他のトランザクションによるロックの解放を待つために時間が無駄になることはありません。

単一の ContactsContract.RawContacts 行の更新中に楽観的同時実行制御を使用する手順は次のとおりです。

  1. 取得する他のデータとともに、未加工連絡先の VERSION 列を取得します。
  2. メソッド newAssertQuery(Uri) を使用して、制約の適用に適した ContentProviderOperation.Builder オブジェクトを作成します。コンテンツ URI には、未加工連絡先の _ID が追加された RawContacts.CONTENT_URI を使用します。
  3. ContentProviderOperation.Builder オブジェクトに対して withValue() を呼び出し、VERSION 列を、先ほど取得したバージョン番号と比較します。
  4. 同じ ContentProviderOperation.Builder に対して withExpectedCount() を呼び出し、このアサーションによって 1 行のみがテストされるようにします。
  5. build() を呼び出して ContentProviderOperation オブジェクトを作成し、このオブジェクトを applyBatch() に渡す ArrayList の最初のオブジェクトとして追加します。
  6. バッチ トランザクションを適用します。

行を読み取ってから変更を試みるまでの間に、別のオペレーションによって未加工連絡先の行が更新された場合、「アサート」ContentProviderOperation は失敗し、オペレーションのバッチ全体が取り消されます。その後、バッチを再試行するか、他のアクションを実行するかを選択できます。

次のスニペットは、CursorLoader を使用して 1 つの未加工連絡先をクエリした後に「アサート」ContentProviderOperation を作成する方法を示しています。

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
    }

インテントを使用した取得と変更

デバイスの連絡先アプリケーションにインテントを送信すると、連絡先プロバイダに間接的にアクセスできます。このインテントは、デバイスの連絡先アプリケーション UI を起動します。ユーザーは、この UI で連絡先関連の作業を行うことができます。このタイプのアクセス権を持つユーザーは、次のことができます。

  • リストから連絡先を選択し、アプリに返してさらに処理します。
  • 既存の連絡先のデータを編集します。
  • いずれかのアカウントに新しい未加工連絡先を挿入する。
  • 連絡先または連絡先データを削除する。

ユーザーがデータを挿入または更新する場合は、まずデータを収集し、インテントの一部として送信できます。

インテントを使用してデバイスの連絡先アプリケーション経由で連絡先プロバイダにアクセスする場合、プロバイダにアクセスするための独自の UI やコードを記述する必要はありません。また、プロバイダの読み取りまたは書き込みの権限をリクエストする必要もありません。デバイスの連絡先アプリケーションは、連絡先の読み取り権限を委任できます。また、別のアプリケーションを介してプロバイダを変更するため、書き込み権限は必要ありません。

プロバイダにアクセスするためのインテントを送信する一般的なプロセスについては、 コンテンツ プロバイダの基本ガイドの「インテントを介したデータアクセス」セクションで詳しく説明されています。利用可能なタスクに使用するアクション、MIME タイプ、データ値の概要を表 4 に示します。putExtra() で使用できるエクストラ値は、ContactsContract.Intents.Insert のリファレンス ドキュメントに記載されています。

表 4. 連絡先プロバイダのインテント。

タスク アクション データ MIME タイプ メモ
リストから連絡先を選択する ACTION_PICK 次のいずれか: 使用されません。 指定したコンテンツ URI タイプに応じて、未加工連絡先のリストまたは未加工連絡先のデータのリストを表示します。

startActivityForResult() を呼び出します。これにより、選択した行のコンテンツ URI が返されます。URI の形式は、テーブルのコンテンツ URI に行の LOOKUP_ID を追加したものです。デバイスの連絡帳アプリは、アクティビティの存続期間中、このコンテンツ URI に読み取りと書き込み権限を委任します。詳しくは、 コンテンツ プロバイダの基本ガイドをご覧ください。

新しい未加工連絡先を挿入する Insert.ACTION なし RawContacts.CONTENT_TYPE、未加工連絡先のセットの MIME タイプ。 デバイスの連絡先アプリケーションの [連絡先を追加] 画面を表示します。インテントに追加したエクストラ値が表示されます。startActivityForResult() とともに送信された場合、新しく追加された未加工連絡先のコンテンツ URI は、Intent 引数の「data」フィールドで、アクティビティの onActivityResult() コールバック メソッドに返されます。値を取得するには、getData() を呼び出します。
連絡先を編集する ACTION_EDIT 連絡先の CONTENT_LOOKUP_URI。エディタ アクティビティを使用すると、ユーザーはこの連絡先に関連付けられているデータを編集できます。 Contacts.CONTENT_ITEM_TYPE、1 件の連絡先。 連絡先アプリケーションに [連絡先の編集] 画面を表示します。インテントに追加したエクストラ値が表示されます。ユーザーが [完了] をクリックして編集内容を保存すると、アクティビティがフォアグラウンドに戻ります。
データの追加も可能な選択ツールを表示する。 ACTION_INSERT_OR_EDIT なし CONTENT_ITEM_TYPE このインテントは、常に連絡帳アプリの選択ツール画面を表示します。ユーザーは、編集する連絡先を選択するか、新しい連絡先を追加できます。ユーザーの選択に応じて、編集画面または追加画面が表示され、インテントで渡したエクストラデータが表示されます。アプリでメールアドレスや電話番号などの連絡先データを表示する場合は、このインテントを使用して、ユーザーが既存の連絡先にデータを追加できるようにします。お問い合わせください。

注: ユーザーは常に既存の名前を選択するか、新しい名前を追加するため、このインテントの extra で名前の値を送信する必要はありません。さらに、名前を送信し、ユーザーが編集を選択した場合、連絡帳アプリは送信された名前を表示し、以前の値を上書きします。ユーザーがこれに気付かずに編集内容を保存すると、古い値は失われます。

デバイスの連絡帳アプリでは、インテントを使用して未加工連絡先やそのデータを削除することはできません。代わりに、未加工連絡先を削除するには、ContentResolver.delete() または ContentProviderOperation.newDelete() を使用します。

次のスニペットは、新しい未加工連絡先とデータを挿入するインテントを作成して送信する方法を示しています。

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

データの整合性

連絡先リポジトリには、ユーザーが正確かつ最新であることを期待する重要かつ機密性の高いデータが含まれているため、連絡先プロバイダにはデータの完全性に関する明確なルールが定められています。連絡先データを変更する際は、これらのルールに準拠する必要があります。重要なルールは次のとおりです。

ContactsContract.RawContacts 行を追加するたびに、ContactsContract.CommonDataKinds.StructuredName 行を追加します。
ContactsContract.Data テーブルに ContactsContract.CommonDataKinds.StructuredName 行がない ContactsContract.RawContacts 行があると、集計中に問題が発生する可能性があります。
新しい ContactsContract.Data 行は、常に親の ContactsContract.RawContacts 行にリンクします。
ContactsContract.RawContacts にリンクされていない ContactsContract.Data 行は、デバイスの連絡先アプリケーションに表示されず、同期アダプターで問題が発生する可能性があります。
所有している未加工連絡先のデータのみを変更します。
連絡先プロバイダは通常、複数の異なるアカウント タイプ/オンライン サービスからのデータを管理していることに注意してください。アプリが、自身に属する行のデータのみを変更または削除し、自身が管理するアカウントの種類と名前を持つデータのみを挿入するようにする必要があります。
オーソリティ、コンテンツ URI、URI パス、列名、MIME タイプ、TYPE 値には、常に ContactsContract とそのサブクラスで定義されている定数を使用します。
これらの定数を使用すると、エラーを回避できます。また、いずれかの定数が非推奨になっている場合は、コンパイラ警告で通知されます。

カスタムデータ行

独自のカスタム MIME タイプを作成して使用することで、ContactsContract.Data テーブルに独自のデータ行を挿入、編集、削除、取得できます。行は ContactsContract.DataColumns で定義された列の使用に制限されますが、独自のタイプ固有の列名をデフォルトの列名にマッピングできます。デバイスの連絡先アプリケーションでは、行のデータは表示されますが、編集や削除はできません。また、ユーザーはデータを追加できません。ユーザーがカスタムデータ行を変更できるようにするには、独自のアプリケーションでエディタ アクティビティを提供する必要があります。

カスタムデータを表示するには、<ContactsAccountType> 要素とその 1 つ以上の <ContactsDataKind> 子要素を含む contacts.xml ファイルを指定します。これについては、<ContactsDataKind> element のセクションで詳しく説明します。

カスタム MIME タイプの詳細については、 コンテンツ プロバイダを作成するガイドをご覧ください。

連絡先プロバイダの同期アダプター

連絡先プロバイダは、デバイスとオンライン サービスの間で連絡先データの同期を処理するように特別に設計されています。これにより、ユーザーは既存のデータを新しいデバイスにダウンロードしたり、既存のデータを新しいアカウントにアップロードしたりできます。また、同期により、追加や変更のソースに関係なく、ユーザーは最新のデータを手元に置くことができます。同期のもう 1 つの利点は、デバイスがネットワークに接続されていない場合でも、連絡先データを利用できることです。

同期はさまざまな方法で実装できますが、Android システムには、次のタスクを自動化するプラグイン同期フレームワークが用意されています。

  • ネットワークの可用性を確認しています。
  • ユーザー設定に基づいて同期をスケジュール設定して実行する。
  • 停止した同期を再開する。

このフレームワークを使用するには、同期アダプター プラグインを提供します。各同期アダプタはサービスとコンテンツ プロバイダに固有のものですが、同じサービスに対して複数のアカウント名を処理できます。また、このフレームワークでは、同じサービスとプロバイダに対して複数の同期アダプタを使用できます。

同期アダプターのクラスとファイル

同期アダプタは、AbstractThreadedSyncAdapter のサブクラスとして実装し、Android アプリの一部としてインストールします。システムは、アプリ マニフェスト内の要素と、マニフェストが参照する特別な XML ファイルから、同期アダプターに関する情報を取得します。XML ファイルでは、オンライン サービスのアカウント タイプとコンテンツ プロバイダのオーソリティを定義します。これらを組み合わせることで、アダプターを一意に識別できます。同期アダプターは、ユーザーが同期アダプターのアカウント タイプのアカウントを追加し、同期アダプターが同期するコンテンツ プロバイダの同期を有効にするまで、アクティブになりません。この時点で、システムはアダプターの管理を開始し、必要に応じてアダプターを呼び出して、コンテンツ プロバイダとサーバーの間で同期を行います。

注: 同期アダプターの識別の一部としてアカウントの種類を使用すると、同じ組織の異なるサービスにアクセスする同期アダプターをシステムが検出してグループ化できます。たとえば、Google オンライン サービスの同期アダプタはすべて、同じアカウント タイプ com.google を持ちます。ユーザーがデバイスに Google アカウントを追加すると、Google サービス用にインストールされているすべての同期アダプターがまとめて一覧表示されます。一覧表示された各同期アダプターは、デバイス上の異なるコンテンツ プロバイダと同期します。

ほとんどのサービスでは、データにアクセスする前にユーザーの本人確認が必要になるため、Android システムには、同期アダプタ フレームワークに似た認証フレームワークが用意されており、多くの場合、同期アダプタ フレームワークと組み合わせて使用されます。認証フレームワークは、AbstractAccountAuthenticator のサブクラスであるプラグイン認証システムを使用します。認証システムは、次の手順でユーザーの ID を検証します。

  1. ユーザーの名前、パスワード、または同様の情報(ユーザーの認証情報)を収集します。
  2. 認証情報をサービスに送信します。
  3. サービスの返信を調べます。

サービスが認証情報を受け入れると、認証システムは後で使用できるように認証情報を保存できます。プラグイン認証システム フレームワークにより、AccountManager は、認証システムがサポートし、公開することを選択した認証トークン(OAuth2 認証トークンなど)へのアクセスを提供できます。

認証は必須ではありませんが、ほとんどの連絡先サービスで使用されています。 ただし、認証を行うために Android 認証フレームワークを使用する必要はありません。

同期アダプターの実装

連絡先プロバイダの同期アダプタを実装するには、まず、次のものを含む Android アプリを作成します。

同期アダプタにバインドするシステムからのリクエストに応答する Service コンポーネント。
システムが同期を実行しようとすると、サービスの onBind() メソッドを呼び出して、同期アダプターの IBinder を取得します。これにより、システムはアダプタのメソッドに対してプロセス間呼び出しを行うことができます。
実際の同期アダプター。AbstractThreadedSyncAdapter の具体的なサブクラスとして実装されます。
このクラスは、サーバーからのデータのダウンロード、デバイスからのデータのアップロード、競合の解決を行います。アダプターの主な処理は、onPerformSync() メソッドで行われます。このクラスはシングルトンとしてインスタンス化する必要があります。
Application のサブクラス。
このクラスは、同期アダプタ シングルトンのファクトリとして機能します。onCreate() メソッドを使用して同期アダプターをインスタンス化し、静的な「getter」メソッドを提供して、同期アダプターのサービスの onBind() メソッドにシングルトンを返します。
省略可: ユーザー認証を求めるシステムからのリクエストに応答する Service コンポーネント。
AccountManager は、このサービスを開始して認証プロセスを開始します。サービスの onCreate() メソッドが認証システム オブジェクトをインスタンス化します。システムは、アプリの同期アダプターのユーザー アカウントを認証する際に、サービスの onBind() メソッドを呼び出して、認証システムの IBinder を取得します。これにより、システムは認証ツールのメソッドに対してプロセス間呼び出しを行うことができます。
省略可: 認証のリクエストを処理する AbstractAccountAuthenticator の具体的なサブクラス。
このクラスは、AccountManager が呼び出してサーバーでユーザーの認証情報を認証するメソッドを提供します。認証プロセスの詳細は、使用するサーバー テクノロジーによって大きく異なります。認証の詳細については、サーバー ソフトウェアのドキュメントをご覧ください。
システムに対する同期アダプターと認証システムを定義する XML ファイル。
前述の同期アダプタと認証サービス コンポーネントは、アプリ マニフェストの <service> 要素で定義されています。これらの要素には、システムに特定のデータを提供する <meta-data> 子要素が含まれています。
  • 同期アダプタ サービスの <meta-data> 要素は、XML ファイル res/xml/syncadapter.xml を指します。このファイルでは、連絡先プロバイダと同期するウェブサービスの URI と、ウェブサービスのアカウント タイプを指定します。
  • 省略可: 認証ツールの <meta-data> 要素は、XML ファイル res/xml/authenticator.xml を指します。このファイルでは、この認証システムがサポートするアカウントの種類と、認証プロセス中に表示される UI リソースを指定します。この要素で指定するアカウント タイプは、同期アダプターで指定するアカウント タイプと同じである必要があります。

ソーシャル ストリーム データ

android.provider.ContactsContract.StreamItems テーブルと android.provider.ContactsContract.StreamItemPhotos テーブルは、ソーシャル ネットワークから受信したデータを管理します。独自のネットワークからこれらのテーブルにストリーム データを追加する同期アダプターを作成したり、これらのテーブルからストリーム データを読み取って独自のアプリに表示したり、その両方を行ったりできます。これらの機能を使用すると、ソーシャル ネットワーキング サービスやアプリを Android のソーシャル ネットワーキング エクスペリエンスに統合できます。

ソーシャル ストリームのテキスト

ストリーム アイテムは常に未加工連絡先に関連付けられます。android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID は、未加工連絡先の _ID 値にリンクします。未加工連絡先のアカウントの種類とアカウント名も、ストリーム アイテム行に保存されます。

ストリームのデータを次の列に保存します。

android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
必須。 このストリーム アイテムに関連付けられている未加工連絡先のユーザーのアカウント タイプ。ストリーム アイテムを挿入するときに、この値を設定してください。
android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
必須。 このストリーム アイテムに関連付けられている未加工連絡先のユーザーのアカウント名。ストリーム アイテムを挿入するときに、この値を設定してください。
識別子列
必須。 ストリーム アイテムを挿入するときは、次の識別子列を挿入する必要があります。
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: このストリーム アイテムが関連付けられている連絡先の android.provider.BaseColumns#_ID 値。
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: このストリーム アイテムが関連付けられている連絡先の android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY 値。
  • android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: このストリーム アイテムが関連付けられている未加工連絡先の android.provider.BaseColumns#_ID 値。
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
省略可。ストリーム アイテムの先頭に表示できる概要情報を保存します。
android.provider.ContactsContract.StreamItemsColumns#TEXT
ストリーム アイテムのテキスト。アイテムのソースによって投稿されたコンテンツ、またはストリーム アイテムを生成したアクションの説明。この列には、fromHtml() でレンダリングできる任意の書式設定と埋め込みリソース画像を含めることができます。プロバイダは、長いコンテンツを切り捨てたり、省略したりすることがありますが、タグを壊さないようにします。
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
ストリーム アイテムが挿入または更新された時刻を含むテキスト文字列。エポックからの経過時間(ミリ秒単位)の形式。ストリーム アイテムを挿入または更新するアプリは、この列を管理する責任があります。連絡先プロバイダによって自動的に管理されることはありません。

ストリーム アイテムの識別情報を表示するには、android.provider.ContactsContract.StreamItemsColumns#RES_ICON、android.provider.ContactsContract.StreamItemsColumns#RES_LABEL、android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE を使用して、アプリ内のリソースにリンクします。

android.provider.ContactsContract.StreamItems テーブルには、同期アダプター専用の android.provider.ContactsContract.StreamItemsColumns#SYNC1 から android.provider.ContactsContract.StreamItemsColumns#SYNC4 までの列も含まれています。

ソーシャル ストリームの写真

android.provider.ContactsContract.StreamItemPhotos テーブルには、ストリーム アイテムに関連付けられた写真が格納されます。このテーブルの android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID 列は、android.provider.ContactsContract.StreamItems テーブルの _ID 列の値にリンクしています。写真参照は、テーブルの次の列に保存されます。

android.provider.ContactsContract.StreamItemPhotos#PHOTO 列(BLOB)。
写真のバイナリ表現。保存と表示のためにプロバイダによってサイズ変更されます。 この列は、写真の保存に使用されていた以前のバージョンの連絡先プロバイダとの下位互換性を維持するために用意されています。ただし、現在のバージョンでは、この列を写真の保存に使用しないでください。代わりに、android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID または android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI(どちらも以下のポイントで説明します)を使用して、写真をファイルに保存します。この列には、読み取り可能な写真のサムネイルが含まれるようになりました。
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
未加工連絡先の写真の数値識別子。この値を定数 DisplayPhoto.CONTENT_URI に追加して、1 つの写真ファイルを指すコンテンツ URI を取得し、openAssetFileDescriptor() を呼び出して写真ファイルのハンドルを取得します。
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
この行で表される写真の写真ファイルを直接指すコンテンツ URI。 この URI を指定して openAssetFileDescriptor() を呼び出し、写真ファイルのハンドルを取得します。

ソーシャル ストリーム テーブルの使用

これらのテーブルは、次の点を除き、連絡先プロバイダの他のメインテーブルと同じように機能します。

  • これらのテーブルには追加のアクセス権限が必要です。それらから読み取るには、アプリに android.Manifest.permission#READ_SOCIAL_STREAM 権限が必要です。変更するには、アプリに android.Manifest.permission#WRITE_SOCIAL_STREAM 権限が必要です。
  • android.provider.ContactsContract.StreamItems テーブルの場合、未加工連絡先ごとに保存される行数には制限があります。この上限に達すると、連絡先プロバイダは、最も古い android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP を持つ行を自動的に削除して、新しいストリーム アイテム行のスペースを確保します。上限を取得するには、コンテンツ URI android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI にクエリを発行します。コンテンツ URI 以外のすべての引数は null に設定したままにできます。このクエリは、単一の列 android.provider.ContactsContract.StreamItems#MAX_ITEMS を含む単一の行を含む Cursor を返します。

android.provider.ContactsContract.StreamItems.StreamItemPhotos クラスは、1 つのストリーム アイテムの写真行を含む android.provider.ContactsContract.StreamItemPhotos のサブテーブルを定義します。

ソーシャル ストリームのインタラクション

連絡先プロバイダが管理するソーシャル ストリーム データは、デバイスの連絡先アプリケーションと連携して、ソーシャル ネットワーキング システムを既存の連絡先に接続する強力な方法を提供します。利用できる対象物は次のとおりです。

  • 同期アダプターを使用してソーシャル ネットワーキング サービスを連絡先プロバイダに同期すると、ユーザーの連絡先の最近のアクティビティを取得し、後で使用できるように android.provider.ContactsContract.StreamItems テーブルと android.provider.ContactsContract.StreamItemPhotos テーブルに保存できます。
  • 通常の同期に加えて、ユーザーが連絡先を選択して表示したときに追加のデータを取得するように同期アダプターをトリガーできます。これにより、同期アダプタは、連絡先の高解像度の写真と最新のストリーム アイテムを取得できます。
  • デバイスの連絡先アプリケーションと連絡先プロバイダに通知を登録すると、連絡先が表示されたときにインテントを受信し、その時点でサービスから連絡先のステータスを更新できます。このアプローチは、同期アダプターを使用して完全同期を行うよりも高速で、帯域幅の使用量も少なくなります。
  • ユーザーは、デバイスの連絡先アプリケーションで連絡先を表示しながら、ソーシャル ネットワーキング サービスに連絡先を追加できます。この機能は「連絡先の招待」機能で有効にします。この機能は、既存の連絡先をネットワークに追加するアクティビティと、デバイスの連絡先アプリケーションと連絡先プロバイダにアプリケーションの詳細を提供する XML ファイルを組み合わせて有効にします。

ストリーム アイテムと連絡先プロバイダの定期的な同期は、他の同期と同じです。同期の詳細については、連絡先プロバイダの同期アダプターをご覧ください。通知の登録と連絡先の招待については、次の 2 つのセクションで説明します。

ソーシャル ネットワーキング ビューを処理するための登録

同期アダプターを登録して、同期アダプターによって管理されている連絡先をユーザーが表示したときに通知を受け取るには、次のようにします。

  1. プロジェクトの res/xml/ ディレクトリに contacts.xml という名前のファイルを作成します。このファイルがすでにある場合は、この手順をスキップできます。
  2. このファイルに、<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 要素を追加します。 この要素がすでに存在する場合は、この手順をスキップできます。
  3. ユーザーがデバイスの連絡先アプリケーションで連絡先の詳細ページを開いたときに通知されるサービスを登録するには、viewContactNotifyService="serviceclass" 属性を要素に追加します。ここで、serviceclass は、デバイスの連絡先アプリケーションからインテントを受信するサービスの完全修飾クラス名です。通知サービスには、IntentService を拡張するクラスを使用して、サービスがインテントを受信できるようにします。受信インテントのデータには、ユーザーがクリックした未加工連絡先のコンテンツ URI が含まれています。通知サービスから、同期アダプターにバインドして呼び出し、未加工連絡先のデータを更新できます。

ユーザーがストリーム アイテムまたは写真、あるいはその両方をクリックしたときに呼び出されるアクティビティを登録するには、次のようにします。

  1. プロジェクトの res/xml/ ディレクトリに contacts.xml という名前のファイルを作成します。このファイルがすでにある場合は、この手順をスキップできます。
  2. このファイルに、<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 要素を追加します。 この要素がすでに存在する場合は、この手順をスキップできます。
  3. デバイスの連絡先アプリケーションでユーザーがストリーム アイテムをクリックしたときに処理するアクティビティを登録するには、viewStreamItemActivity="activityclass" 属性を要素に追加します。ここで、activityclass は、デバイスの連絡先アプリケーションからインテントを受け取るアクティビティの完全修飾クラス名です。
  4. デバイスの連絡先アプリケーションでユーザーがストリーム写真をクリックしたときに処理するアクティビティを登録するには、属性 viewStreamItemPhotoActivity="activityclass" を要素に追加します。ここで、activityclass は、デバイスの連絡先アプリケーションからインテントを受け取るアクティビティの完全修飾クラス名です。

<ContactsAccountType> 要素の詳細については、<ContactsAccountType> 要素のセクションをご覧ください。

受信インテントには、ユーザーがクリックしたアイテムまたは写真のコンテンツ URI が含まれています。 テキスト アイテムと写真に別々のアクティビティを設定するには、同じファイルで両方の属性を使用します。

ソーシャル ネットワーキング サービスとのやり取り

ユーザーは、デバイスの連絡先アプリケーションを離れることなく、ソーシャル ネットワーク サイトに連絡先を招待できます。代わりに、デバイスの連絡帳アプリから、連絡先をアクティビティの 1 つに招待するインテントを送信できます。これを設定する方法は、次のとおりです。

  1. プロジェクトの res/xml/ ディレクトリに contacts.xml という名前のファイルを作成します。このファイルがすでにある場合は、この手順をスキップできます。
  2. このファイルに、<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 要素を追加します。 この要素がすでに存在する場合は、この手順をスキップできます。
  3. 次の属性を追加します。
    • inviteContactActivity="activityclass"
    • inviteContactActionLabel="@string/invite_action_label"
    activityclass 値は、インテントを受け取るアクティビティの完全修飾クラス名です。invite_action_label 値は、デバイスの連絡先アプリケーションの [接続を追加] メニューに表示されるテキスト文字列です。

注: ContactsSource は、ContactsAccountType の非推奨のタグ名です。

contacts.xml リファレンス

contacts.xml ファイルには、同期アダプターとアプリケーションが連絡先アプリケーションおよび連絡先プロバイダとやり取りする方法を制御する XML 要素が含まれています。これらの要素については、以降のセクションで説明します。

<ContactsAccountType> 要素

<ContactsAccountType> 要素は、アプリと連絡先アプリケーションのやり取りを制御します。構文は次のとおりです。

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

含まれる場所:

res/xml/contacts.xml

以下を含めることができます。

<ContactsDataKind>

説明:

ユーザーが連絡先の 1 人をソーシャル ネットワークに招待したり、ソーシャル ネットワーク ストリームの 1 つが更新されたときにユーザーに通知したりできるようにする Android コンポーネントと UI ラベルを宣言します。

<ContactsAccountType> の属性には、属性接頭辞 android: は必要ありません。

属性:

inviteContactActivity
ユーザーがデバイスの連絡先アプリケーションから [接続を追加] を選択したときに有効にする、アプリケーション内のアクティビティの完全修飾クラス名。
inviteContactActionLabel
inviteContactActivity で指定されたアクティビティの [接続を追加] メニューに表示されるテキスト文字列。 たとえば、「ネットワークでフォローする」という文字列を使用できます。このラベルには文字列リソース識別子を使用できます。
viewContactNotifyService
ユーザーが連絡先を表示したときに通知を受け取る、アプリ内のサービスの完全修飾クラス名。この通知はデバイスの連絡先アプリケーションから送信されます。これにより、アプリはデータ量の多いオペレーションを必要になるまで延期できます。たとえば、アプリはこの通知に応答して、連絡先の高解像度の写真と最新のソーシャル ストリーム アイテムを読み取って表示できます。この機能の詳細については、ソーシャル ストリームのインタラクションのセクションをご覧ください。
viewGroupActivity
グループ情報を表示できるアプリ内のアクティビティの完全修飾クラス名。ユーザーがデバイスの連絡先アプリケーションでグループラベルをクリックすると、このアクティビティの UI が表示されます。
viewGroupActionLabel
連絡先アプリケーションが、ユーザーがアプリケーション内のグループを表示できるようにする UI コントロールに表示するラベル。

この属性には文字列リソース識別子を使用できます。

viewStreamItemActivity
ユーザーが未加工連絡先のストリーム アイテムをクリックしたときに、デバイスの連絡先アプリケーションが起動する、アプリケーション内のアクティビティの完全修飾クラス名。
viewStreamItemPhotoActivity
ユーザーが未加工連絡先のストリーム アイテム内の写真をクリックしたときに、デバイスの連絡先アプリケーションが起動する、アプリケーション内のアクティビティの完全修飾クラス名。

<ContactsDataKind> 要素

<ContactsDataKind> 要素は、連絡先アプリケーションの UI でのアプリケーションのカスタムデータ行の表示を制御します。構文は次のとおりです。

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

含まれる場所:

<ContactsAccountType>

説明:

この要素を使用すると、連絡先アプリケーションで、未加工連絡先の詳細の一部としてカスタムデータ行の内容を表示できます。<ContactsAccountType> の各 <ContactsDataKind> 子要素は、同期アダプタが ContactsContract.Data テーブルに追加するカスタム データ行のタイプを表します。使用するカスタム MIME タイプごとに <ContactsDataKind> 要素を 1 つ追加します。データを表示しないカスタムデータ行がある場合は、この要素を追加する必要はありません。

属性:

android:mimeType
ContactsContract.Data テーブルのカスタムデータ行タイプの 1 つに定義したカスタム MIME タイプ。たとえば、値 vnd.android.cursor.item/vnd.example.locationstatus は、連絡先の最後に確認された位置情報を記録するデータ行のカスタム MIME タイプです。
android:icon
連絡先アプリケーションがデータの横に表示する Android ドローアブル リソース。これを使用して、データがサービスから取得されたことをユーザーに示します。
android:summaryColumn
データ行から取得された 2 つの値のうち、最初の値の列名。この値は、このデータ行のエントリの最初の行として表示されます。最初の行はデータの概要として使用することを目的としていますが、これは省略可能です。android:detailColumn もご覧ください。
android:detailColumn
データ行から取得された 2 つの値のうち、2 番目の値の列名。この値は、このデータ行のエントリの 2 行目に表示されます。android:summaryColumn もご覧ください。

連絡先プロバイダのその他の機能

前のセクションで説明した主な機能に加えて、連絡先プロバイダには、連絡先データを操作する際に役立つ次のような機能があります。

  • 連絡先グループ
  • 写真の機能

連絡先グループ

連絡先プロバイダは、必要に応じて、関連する連絡先のコレクションにグループデータでラベルを付けることができます。ユーザー アカウントに関連付けられたサーバーでグループを管理する場合は、そのアカウントの種類に対応する同期アダプターが、連絡先プロバイダとサーバーの間でグループデータを転送する必要があります。ユーザーがサーバーに新しい連絡先を追加し、その連絡先を新しいグループに配置すると、同期アダプターは新しいグループを ContactsContract.Groups テーブルに追加する必要があります。未加工連絡先が属するグループは、ContactsContract.CommonDataKinds.GroupMembership MIME タイプを使用して ContactsContract.Data テーブルに保存されます。

サーバーから連絡先プロバイダに未加工連絡先データを追加する同期アダプターを設計していて、グループを使用していない場合は、データを表示するようにプロバイダに指示する必要があります。ユーザーがデバイスにアカウントを追加したときに実行されるコードで、連絡先プロバイダがアカウントに追加する ContactsContract.Settings 行を更新します。この行で、Settings.UNGROUPED_VISIBLE 列の値を 1 に設定します。この場合、グループを使用していなくても、連絡先プロバイダは常に連絡先データを表示します。

連絡先の写真

ContactsContract.Data テーブルには、MIME タイプ Photo.CONTENT_ITEM_TYPE の行として写真が保存されます。行の CONTACT_ID 列は、その行が属する未加工連絡先の _ID 列にリンクされています。ContactsContract.Contacts.Photo クラスは、連絡先のメインの写真(連絡先のメインの未加工連絡先のメインの写真)に関する写真情報を含む ContactsContract.Contacts のサブテーブルを定義します。同様に、クラス ContactsContract.RawContacts.DisplayPhoto は、未加工連絡先のメインの写真に関する写真情報を含む ContactsContract.RawContacts のサブテーブルを定義します。

ContactsContract.Contacts.Photo と ContactsContract.RawContacts.DisplayPhoto のリファレンス ドキュメントには、写真情報を取得する例が記載されています。未加工連絡先のメインのサムネイルを取得するための便利なクラスはありませんが、ContactsContract.Data テーブルにクエリを送信し、未加工連絡先の _ID、Photo.CONTENT_ITEM_TYPE、IS_PRIMARY 列を選択して、未加工連絡先のメインの写真の行を見つけることができます。

ユーザーのソーシャル ストリーム データには、写真が含まれることもあります。これらは android.provider.ContactsContract.StreamItemPhotos テーブルに保存されます。詳しくは、ソーシャル ストリームの写真をご覧ください。