連絡先プロバイダ

連絡先プロバイダは、人のデータに関する端末の中央リポジトリを管理する、柔軟で効果的な Android コンポーネントです。連絡先プロバイダは、端末の連絡先アプリに表示されるデータのソースであり、さらに独自アプリで連絡先プロバイダのデータにアクセスし、端末とオンライン サービスとの間でデータを転送することもできます。このプロバイダは幅広いデータソースに対応しており、各人に関してできるだけ多くのデータを管理しようとするため、複雑な構造をしています。そのため、連絡先プロバイダの API には、データの取得と変更をどちらもやりやすくするクラスやインターフェースが多数用意されています。

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

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

このガイドは、Android のコンテンツ プロバイダの基礎知識がある読者を対象としています。Android のコンテンツ プロバイダについて詳しくは、コンテンツ プロバイダの基本をご覧ください。

連絡先プロバイダの構成

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

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

この 3 つのテーブルは、一般にそれぞれのコントラクト クラス名で呼ばれます。各クラスは、テーブルによって使用されるコンテンツ URI、列名、列の値のための定数を定義しています。

ContactsContract.Contacts テーブル
各行は、未加工連絡先の行の集約に基づいて異なる人を表します。
ContactsContract.RawContacts テーブル
各行には、ユーザーのアカウントとタイプに固有の、人に関するデータの概要が格納されています。
ContactsContract.Data テーブル
各行には、メールアドレスや電話番号など、未加工連絡先の詳細情報が格納されています。

ContactsContract に含まれるコントラクト クラスによって表現される他のテーブルは補助テーブルであり、連絡先プロバイダがその操作を管理したり、端末の連絡先アプリや電話アプリの特定機能をサポートしたりするために使用します。

未加工連絡先

未加工連絡先は、アカウント タイプとアカウント名の 1 つのペアを使用して得られる人に関するデータです。連絡先プロバイダでは、1 人に関するデータのソースとして複数のオンライン サービスを使用できるため、同じ個人に対して複数の未加工連絡先が許されます。未加工連絡先が複数ある場合、ユーザーは同じアカウント タイプの複数のアカウントから取得したその人のデータを組み合わせることができます。

未加工連絡先データの大半は、ContactsContract.RawContacts テーブルには格納されません。その代わり、ContactsContract.Data テーブルの 1 つまたは複数の行に格納されています。各データ行には列 Data.RAW_CONTACT_ID があり、親 ContactsContract.RawContacts 行の RawContacts._ID 値が格納されています。

重要な未加工連絡先列

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

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

列名 用途
ACCOUNT_NAME この未加工連絡先のソースであるアカウント タイプにおけるアカウント名。たとえば、Google アカウントのアカウント名は、端末オーナーの Gmail アドレスのどれかです。詳しくは、次の ACCOUNT_TYPE の項目をご覧ください。 この名前の形式は、アカウント タイプによって異なります。必ずしもメールアドレスとは限りません。
ACCOUNT_TYPE この未加工連絡先のソースであるアカウント タイプ。たとえば、Google アカウントのアカウント タイプは com.google です。アカウント タイプは必ず、所有または管理しているドメインのドメイン ID で修飾してください。そうすることで、アカウント タイプが確実に一意になります。 連絡先データを提供するアカウント タイプには、通常、連絡先プロバイダと同期する関連同期アダプタが用意されています。
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)を追加しておくことで、アプリが未加工連絡先の行を追加できるようになります。この要件については、文書で説明するか、ユーザーにタイプと名前を追加するよう求めるか、あるいはその両方を実施してください。アカウントのタイプと名前については、次のセクションで詳しく説明します。

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

未加工連絡先の働きを理解するために、「Emily Dickinson」というユーザーについて考えてみましょう。彼女は自分の端末に次の 3 つのアカウントを定義しています。

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

このユーザーは、[Accounts] の設定でこの 3 つのアカウントすべてについて [Sync Contacts] を有効にしています。

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

連絡先プロバイダは、この操作の結果として次の 3 行の未加工連絡先を作成します。

  1. 「Thomas Higginson」の、emily.dickinson@gmail.com に関連付けられた未加工連絡先。ユーザー アカウントのタイプは Google です。
  2. 「Thomas Higginson」の、emilyd@gmail.com に関連付けられた新たな未加工連絡先。ユーザー アカウントのタイプはやはり Google です。名前が前の行とまったく同じなのに新たな未加工連絡先が作成されたのは、この個人が異なるユーザー アカウントに追加されたからです。
  3. 「Thomas Higginson」の、「belle_of_amherst」に関連付けられた未加工連絡先。ユーザー アカウントのタイプは Twitter です。

データ

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

異なるタイプのデータが同じテーブルに格納されることに注目してください。表示名、電話番号、メール、住所、写真、ウェブサイトに関する詳細行はすべて、ContactsContract.Data テーブルにあります。これを管理しやすくするため、ContactsContract.Data テーブルの一部の列には説明的な名前が、それ以外には一般的な名前がそれぞれ付いています。説明的な名前が付いた列の内容は、行データのタイプによらず意味が同じなのに対し、一般的な名前が付いた列の内容は、データのタイプによって意味が異なります。

説明的な名前の列

説明的な名前が付いた列の例をいくつか示します。

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

汎用名の列

自由に使用できる DATA1DATA15 という 15 個の汎用列の他に、同期アダプタしか使用してはいけない SYNC1SYNC4 という 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 つの未加工連絡先は、この行を 1 行だけ持ちます。
ContactsContract.CommonDataKinds.Photo このデータ行に関連付けられている未加工連絡先のメインの写真。 1 つの未加工連絡先は、この行を 1 行だけ持ちます。
ContactsContract.CommonDataKinds.Email このデータ行に関連付けられている未加工連絡先のメールアドレス。 1 つの未加工連絡先は複数のメールアドレスを持つことができます。
ContactsContract.CommonDataKinds.StructuredPostal このデータ行に関連付けられている未加工連絡先の住所。 1 つの未加工連絡先は複数の住所を持つことができます。
ContactsContract.CommonDataKinds.GroupMembership その未加工連絡先を連絡先プロバイダのグループのどれかにリンクする識別子。 グループは、アカウント タイプとアカウント名のオプション機能です。詳しくは、連絡先グループをご覧ください。

連絡先

連絡先プロバイダは、すべてのアカウント タイプとアカウント名にわたって未加工連絡先の行を結び付けて 1 つの連絡先を形成します。これにより、1 人の人についてユーザーが集めた全データを表示したり変更したりしやすくなります。連絡先プロバイダは、新しい連絡先の行の作成と、既存の連絡先の行を使用した未加工連絡先の集約を管理します。アプリと同期アダプタは連絡先の追加はできず、連絡先の行の一部の列は読み取り専用です。

注:連絡先を連絡先プロバイダに insert() を使用して追加しようとすると、UnsupportedOperationException 例外が発生します。「読み取り専用」になっている列をアップデートしようとしても、そのアップデートは無視されます。

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

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

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

図 3 に、3 つのテーブルの相互関係を示します。

連絡先プロバイダの主なテーブル

図 3. Contacts、RawContacts、Details の各テーブルの関係

注意:2019 年 1 月 7 日の時点で、一部の連絡先データ フィールドおよびメソッドは廃止されています。

次のデータ フィールドに書き込まれているすべての値は定期的に消去されます。

上記のデータ フィールドの設定に使用される次の API も廃止されています。

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

アプリがこれらのフィールドまたは API にアクセスまたはアップデートしている場合は、別のメソッドを使用してください。たとえば、プライベート コンテンツ プロバイダ、またはアプリやバックエンド システムに保存されているその他のデータを使用することで、特定のユースケースを満たすことができます。

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

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

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

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

Android では、同期アダプタと連携するウェブサービスをアカウント タイプで識別します。各同期アダプタが扱うアカウント タイプは 1 つですが、そのタイプのアカウント名を複数サポートできます。アカウント タイプとアカウント名については、未加工連絡先データのソースで簡単に説明しています。次に、アカウント タイプとアカウント名が同期アダプタやサービスとどのような関係にあるかを詳しく説明します。

アカウント タイプ
ユーザーがデータを格納しているサービスを示します。ほとんどの場合、ユーザーにはサービスに対する認証が求められます。たとえば、Google コンタクトはアカウント タイプの 1 つで、コード google.com で識別されます。この値は、AccountManager によって使用されるアカウント タイプに対応します。
アカウント名
あるアカウント タイプで使用する特定のアカウントまたはログインを示します。Google コンタクト アカウントは Google アカウントと同じで、アカウント名としてメールアドレスを使用します。他のサービスでは、1 語のユーザー名や数字の ID が使われていることもあります。

アカウント タイプは、一意である必要はありません。1 人のユーザーが複数の Google コンタクト アカウントを設定し、それぞれのデータを連絡先プロバイダにダウンロードするということが可能です。このような使い方は、そのユーザーが個人用のアカウント名で私用の連絡先を、仕事用のアカウント名で仕事用の連絡先を管理している場合にありえます。アカウント名は、普通は一意です。この 2 つを組み合わせて、連絡先プロバイダと外部サービスとの間のある決まったデータフローを識別します。

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

図 4 に、人に関するデータの流れにおける連絡先プロバイダの位置付けを示します。右から 2 列目の各ボックス内のアダプタには、そのアダプタのアカウント タイプが示されています。

人に関するデータの流れ

図 4. 連絡先プロバイダに絡んだデータの流れ

必要なパーミッション

連絡先プロバイダにアクセスするアプリは、次のパーミッションを要求する必要があります。

1 つ以上のテーブルに対する読み取りアクセス
READ_CONTACTSAndroidManifest.xml <uses-permission> 要素を使用して <uses-permission android:name="android.permission.READ_CONTACTS"> のように指定します。
1 つ以上のテーブルに対する書き込みアクセス
WRITE_CONTACTSAndroidManifest.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」に設定されています。

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

連絡先プロバイダは、連絡先データの状態を継続的に追跡するためのデータをリポジトリで管理します。リポジトリに関するこのメタデータは、RawContacts、Data、Contacts の各テーブルの行、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 はアカウント タイプごとに一意で、同期中に安定している必要があります。

  • Unique:アカウントの各未加工連絡先には独自のソース ID が必要です。これを強制しないと、連絡先アプリに問題を引き起こすことになります。同じアカウント タイプに対する 2 つの未加工連絡先が、同じソース ID を持つことがありえます。たとえば、アカウント emily.dickinson@gmail.com の未加工連絡先「Thomas Higginson」は、アカウント emilyd@gmail.com の未加工連絡先「Thomas Higginson」と同じソース ID を持つことができます。
  • Stable:ソース ID は、未加工連絡先のオンライン サービスデータにおいて変わらない部分です。たとえば、ユーザーが [Apps] 設定から [Contacts Storage] を消去し、再同期したとしても、復元された未加工連絡先のソース 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 (すべて) このテーブルを使用して、同期アダプタのメタデータを格納します。 このテーブルを使用すると、同期状態などの同期関連データを永続的に端末上に格納できます。

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

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

  • エンティティ クエリ。
  • バッチ変更。
  • インテントを使用した取得と変更。
  • データの整合性。

同期アダプタからの変更については、連絡先プロバイダの同期アダプタでも詳しく説明しています。

連絡先を検索するときに連絡先プロバイダを使用する方法の例については、Basic Contactables のサンプルをご覧ください。

エンティティのクエリ

連絡先プロバイダのテーブルは階層構造になっており、ある行とその「子」の行すべてを取得すると便利なことがよくあります。たとえば、ある人に関するすべての情報を表示するために、ContactsContract.Contacts 行 1 行に対するすべての ContactsContract.RawContacts 行や、ContactsContract.RawContacts 行 1 行に対するすべての ContactsContract.CommonDataKinds.Email 行を取得することが考えられます。こうした処理をやりやすくするため、連絡先プロバイダにはエンティティ構造が用意されています。これは、テーブル間でのデータベースの和集合のように機能します。

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

エンティティにより、クエリが簡素化されます。エンティティを使用することで、ある連絡先または未加工連絡先の連絡先データをすべて取得できるので、まず親テーブルにクエリして ID を取得し、次にその ID を使用して子テーブルにクエリする必要がありません。また、連絡先プロバイダは、エンティティに対するクエリを 1 回のトランザクションで処理することから、取得されたデータの内部的な整合性が保証されます。

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

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

このスニペットは「詳細」アクティビティからの抜粋です。

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

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

バッチ変更

可能であれば必ず、連絡先プロバイダのデータを「バッチモード」で挿入、アップデート、削除してください。そのためには、ContentProviderOperation オブジェクトの ArrayList を作成し、applyBatch() を呼び出します。連絡先プロバイダはすべての操作を 1 つのトランザクションの 1 つの applyBatch() で行うため、変更内容が連絡先リポジトリで不整合のままになることは決してありません。また、バッチ変更では、未加工連絡先とその詳細データを同時に挿入しやすくなっています。

注:1 つの未加工連絡先を変更する場合は、変更を独自アプリ内で処理するのではなく、インテントを端末の連絡先アプリに送信することを検討してください。この方法については、インテントを使用した取得と変更で詳しく説明しています。

明け渡し点

多数の操作を伴うバッチ変更は、他のプロセスをブロックしてユーザーにとっての全体的な使用感を悪化させかねません。意図したすべての変更をできるだけ少ない個別リストに整理するとともに、それらがシステムをブロックしないようにするために、1 つまたは複数の操作に明け渡し点を設定してください。明け渡し点の実体は、isYieldAllowed() の値が true に設定された ContentProviderOperation オブジェクトです。連絡先プロバイダが明け渡し点に遭遇すると、作業を一時中断して他のプロセスを実行させ、現在のトランザクションをクローズします。作業を再開した連絡先プロバイダは、ArrayList に含まれている次の操作を行い、新しいトランザクションを始めます。

明け渡し点により、applyBatch() の呼び出し 1 回につき複数のトランザクションが発生します。そのため、明け渡し点は 1 組の関連する行への最後の操作に設定してください。たとえば、未加工連絡先の行とその関連データ行を追加する一連の操作の最後に、あるいは 1 人の連絡先に関連する 1 組の行に対する最後の操作に、明け渡し点を設定します。

明け渡し点は、アトミック操作の単位でもあります。2 つの明け渡し点間のすべてのアクセスは、1 つのまとまりとして成功または失敗します。明け渡し点を設定しない場合、最小のアトミック操作は操作のバッチ全体になります。明け渡し点を使用すると、操作がシステムのパフォーマンスを低下させるのを防ぐと同時に、操作の一部分が確実にアトミックになります。

変更の後方参照

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

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

key
キーと値のペアのキーです。この引数の値は、変更中のテーブルに含まれる列の名前であることが必要です。
previousResult
applyBatch() から取得した ContentProviderResult オブジェクトの配列に含まれる値の、0 ベースのインデックスです。バッチ操作が適用されると、各操作の結果は結果用の中間配列に格納されます。previousResult 値はそうした結果の 1 つのインデックスで、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 行更新している間に楽観的同時実行制御を使用する方法は次のとおりです。

  1. 取得する他のデータとともに、未加工連絡先の VERSION 列を取得します。
  2. 制約を強制するのに適した ContentProviderOperation.Builder オブジェクトを、メソッド newAssertQuery(Uri) を使用して作成します。コンテンツ 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 やコードを作成する必要はありません。また、連絡先プロバイダに対する読み取りや書き込みのパーミッションを要求する必要もありません。端末の連絡先アプリは、連絡先に対する読み取りパーミッションをデリゲートできます。また、連絡先プロバイダへの変更を他のアプリ経由で行っていることから、書き込みパーミッションは不要です。

プロバイダにアクセスするためのインテントを送信する汎用プロセスについては、コンテンツ プロバイダの基本のセクション「インテント経由のデータアクセス」で詳しく説明しています。表 4 に、利用可能なタスクで使用できるアクション、MIME タイプ、データ値をまとめます。putExtra() で使用できるエクストラ値については、ContactsContract.Intents.Insert のリファレンス ドキュメントに一覧があります。

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

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

startActivityForResult() を呼び出してください。それにより、選択した行のコンテンツ URI が返されます。URI の形式は、テーブルのコンテンツ URI の末尾にその行の LOOKUP_ID が追加されたものです。端末の連絡先アプリは、このアクティビティの実行中を通して、読み取りと書き込みのパーミッションをこのコンテンツ URI にデリゲートします。詳しくは、コンテンツ プロバイダの基本をご覧ください。

新しい未加工連絡先を挿入する Insert.ACTION 該当せず RawContacts.CONTENT_TYPE。未加工連絡先のセットに対する MIME タイプです。 端末の連絡先アプリの [Add Contact] 画面を表示します。インテントに追加したエクストラ値が表示されます。startActivityForResult() を使用して送信すると、新たに追加された未加工連絡先の URI が、アクティビティの onActivityResult() コールバック メソッドの Intent 引数の「data」フィールドに格納されて戻ってきます。この値を取得するには、getData() を呼び出します。
連絡先を編集する ACTION_EDIT 連絡先の CONTENT_LOOKUP_URI。エディタ アクティビティを使用すると、ユーザーがその連絡先に関連付けられている任意のデータを編集できます。 Contacts.CONTENT_ITEM_TYPE。1 つの連絡先です。 連絡先アプリに連絡先の編集画面を表示します。インテントに追加したエクストラ値が表示されます。ユーザーが [Done] をタップして編集内容を保存すると、アクティビティがフォアグラウンドに戻ります。
ピッカー(データ追加も可能)を表示 ACTION_INSERT_OR_EDIT 該当せず CONTENT_ITEM_TYPE このインテントは、連絡先アプリのピッカー画面を常に表示します。ユーザーは、編集する連絡先を選ぶか、新しい連絡先を追加できます。ユーザーの選択に応じて編集画面か追加画面が開き、インテントに含めて渡したエクストラ データが表示されます。メールアドレスや電話番号などの連絡先データを独自アプリに表示する場合は、このインテントを使用すると、ユーザーが既存の連絡先にデータを追加できます。

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

端末の連絡先アプリは、インテントを使用して未加工連絡先や任意のデータを削除することを許可しません。そのため、未加工連絡先を削除するには 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.CommonDataKinds.StructuredName 行を、追加する ContactsContract.RawContacts 行ごとに必ず追加してください。
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 に定義された列だけですが、独自タイプに固有の列名をデフォルトの列名にマッピングできます。端末の連絡先アプリで、独自行のデータは表示されますが、編集または削除はできず、ユーザーはデータの追加ができません。ユーザーがカスタムデータ行を変更できるようにするには、独自のアプリでエディタ アクティビティを提供する必要があります。

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

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

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

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

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

  • ネットワークの可用性のチェック。
  • ユーザーのプリファレンスに基づく同期のスケジュールと実施。
  • 停止された同期の再開。

このフレームワークを使用するには、同期アダプタ プラグインを提供する必要があります。各同期アダプタはサービスと連絡先プロバイダに対して一意ですが、同じサービスに対して複数のアカウント名を処理できます。また、このフレームワークでは同じサービスとプロバイダに対して複数の同期アダプタが可能です。

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

同期アダプタは、AbstractThreadedSyncAdapter のサブクラスとして実装し、Android アプリの一部としてインストールします。システムは同期アダプタに関する知識を、アプリのマニフェストの要素からと、マニフェストが指す専用の XML ファイルから取得します。この XML ファイルは、オンライン サービスのアカウント タイプと連絡先プロバイダに絡む権限を定義しており、この組み合わせでアダプタを一意に識別します。同期アダプタは、ユーザーが同期アダプタのアカウント タイプに対してアカウントを追加し、同期アダプタの同期対象である連絡先プロバイダで同期を有効にすることで、アクティブになります。この時点で、システムはアダプタの管理を開始し、連絡先プロバイダとサーバーとの同期が必要になるとそれを呼び出します。

注:アカウント タイプを同期アダプタの識別の一環として使用することにより、システムは同じ組織から異なるサービスにアクセスする同期アダプタを検出してグループ化できます。たとえば、Google オンライン サービス用の同期アダプタでは、すべて同じアカウント タイプ com.google です。ユーザーが Google アカウントを端末に追加すると、Google サービス用にインストールされているすべての同期アダプタがひとまとめにリストされ、リストされている各同期アダプタは端末上の異なる連絡先プロバイダと同期します。

データにアクセスするたび、ほとんどのサービスがユーザーに ID の確認を要求するため、Android システムでは、同期アダプタ フレームワークと類似した認証フレームワークを提供しており、往々にして同期アダプタ フレームワークと組み合わせて使用します。この認証フレームワークでは、AbstractAccountAuthenticator のサブクラスであるプラグイン認証システムを使用します。認証システムは、次の手順でユーザーの身元を検証します。

  1. ユーザー名とパスワードか、それに準ずる情報(ユーザーの認証情報)を収集します。
  2. 収集した認証情報をサービスに送信します。
  3. サービスの返答を検証します。

サービスが認証情報を受け入れた場合、認証システムはその認証情報を後で使用するために格納します。AccountManager はプラグイン認証フレームワークのおかげで、OAuth2 認証トークンなど、認証システムがサポートして公開することを選択している任意の認証トークンへのアクセスを提供できます。

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

同期アダプタの実装

連絡先プロバイダ用の同期アダプタを実装するには、まず以下を備えた Android アプリを作成します。

システムからの同期アダプタとのバインド要求に対処する Service コンポーネント。
システムは、同期しようとするとき、サービスの onBind() メソッドを呼び出して、同期アダプタの IBinder を取得します。これにより、システムはアダプタのメソッドに対するプロセス間呼び出しを実行できます。
AbstractThreadedSyncAdapter の具象サブクラスとして実装された、実際の同期アダプタ。
このクラスが、サーバーからのデータのダウンロード、端末からのデータのアップロード、競合の解消といった作業を実施します。アダフタの主な作業は、メソッド onPerformSync() で実行されます。このクラスは、シングルトンとしてインスタンス化する必要があります。
Application のサブクラス。
このクラスは同期アダプタ シングルトンのファクトリとして機能します。同期アダプタのインスタンス化には onCreate() メソッドを使用し、同期アダプタ シングルトンを同期アダプタのサービスの onBind() メソッドに返すための静的な「getter」メソッドを提供します。
任意:システムからのユーザー認証要求に対処する 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
未加工連絡先の写真の数値 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 だけの 1 行を含む Cursor を返します。

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

ソーシャル ストリーム操作

連絡先プロバイダによって端末の連絡先アプリと連携して管理されるソーシャル ストリーム データは、ソーシャル ネットワーキング システムと既存の連絡先を接続するためのとても便利な手段を用意しています。利用できる機能は次のとおりです。

  • 同期アダプタを使用してソーシャル ネットワーキング サービスを連絡先プロバイダに同期することで、ユーザーの連絡先の最近のアクティビティを取得し、それを android.provider.ContactsContract.StreamItems テーブルや android.provider.ContactsContract.StreamItemPhotos テーブルに格納して後で使えるようにできます。
  • ユーザーが連絡先を選択して表示したときには、定期的な同期とは別に同期アダプタを起動して追加データを取得できます。この機能により、同期アダプタは連絡先の高解像度の写真や直近のストリーム アイテムを取得できます。
  • 端末の連絡先アプリと連絡先プロバイダに関する通知を登録すると、連絡先が表示された場合にインテントを受信し、その時点で連絡先のステータスをサービスからアップデートできます。このアプローチは、同期アダプタを使用してフル同期を実施するより、処理が速く、帯域幅が節約されます。
  • ユーザーが端末の連絡先アプリで連絡先を見ている間に、その連絡先をソーシャル ネットワーキング サービスに追加できます。この機能は「連絡先の招待」機能を使用して実現できます。連絡先の招待は、既存の連絡先をネットワークに追加するアクティビティと、端末の連絡先アプリと連絡先プロバイダにアプリの詳細を提供する XML ファイルを組み合わせて実現できます。

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

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

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

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

ユーザーがストリーム アイテムかストリーム フォトかその両方をクリックしたときに呼び出されるアクティビティを登録する方法は次のとおりです。

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

<ContactsAccountType> 要素については、<ContactsAccountType> 要素で詳しく説明しています。

受け取るインテントには、ユーザーがクリックしたアイテムまたは写真のコンテンツ URI が格納されます。テキスト アイテムと写真とで異なるアクティビティを使用するには、同じファイルで両方の属性を使用します。

ソーシャル ネットワーキング サービスの操作

ユーザーは、連絡先をソーシャル ネットワーキング サービスに招待するのに、端末の連絡先アプリを離れる必要はありません。その代わりとして、連絡先をアクティビティのどれかに招待するインテントを端末の連絡先アプリケーションに送信させることができます。これをセットアップする方法は次のとおりです。

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

注: 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 人をソーシャル ネットワーキングに招待できるようにしたり、ソーシャル ネットワーキング ストリームのどれかがアップデートされたらユーザーに通知したりするための、Android コンポーネントや UI ラベルを宣言します。

属性プレフィックス android: は、<ContactsAccountType> の属性に必須ではないことに注目してください。

属性:

inviteContactActivity
ユーザーが端末の連絡先アプリで [Add Connection] を選択したときに起動する、アプリ内のアクティビティの完全修飾クラス名。
inviteContactActionLabel
[Add Connection] メニューで、inviteContactActivity に指定されたアクティビティ用に表示されるテキスト。たとえば、文字列「Follow in my network」を指定できます。このラベルには文字列リソース ID を使用できます。
viewContactNotifyService
ユーザーが連絡先を表示したときに通知を受け取ることになる、独自アプリ内のサービスの完全修飾クラス名。この通知は端末の連絡先アプリによって送信されます。これを使用することで、アプリはデータ処理の多い操作を必要になるまで延期できます。たとえば、アプリはこの通知への対応として、連絡先の高解像度写真と直近のソーシャル ストリーム アイテムを読み込んで表示できます。この機能について詳しくは、ソーシャル ストリーム操作で説明しています。
viewGroupActivity
グループ情報を表示できる、独自アプリ内のアクティビティの完全修飾クラス名。ユーザーが端末の連絡先アプリでグループラベルをタップすると、このアクティビティの UI が表示されます。
viewGroupActionLabel
ユーザーがアプリでグループを表示できるようにする UI コントロールに対し、連絡先アプリが表示するラベル。

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

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 テーブルに含まれるカスタムデータ行のどれかに定義したカスタム 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.Data テーブルに、ContactsContract.CommonDataKinds.GroupMembership MIME タイプを使用して格納されます。

サーバーからの未加工連絡先データを連絡先プロバイダに追加する同期アダプタを設計している場合、グループを使用しないのであれば、連絡先プロバイダに対してデータが可視であることを通知する必要があります。ユーザーがアカウントを端末に追加したときに実行されるコード内で、連絡先プロバイダがそのアカウントに対して追加する 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.PhotoContactsContract.RawContacts.DisplayPhoto の参照ドキュメントには、写真情報の取得例が含まれています。未加工連絡先のプライマリ サムネイルを取得するための便利なクラスはありませんが、ContactsContract.Data テーブルにクエリを送信して、未加工連絡先の _IDPhoto.CONTENT_ITEM_TYPEIS_PRIMARY 列を選んでその未加工連絡先のプライマリ フォト行を探すことができます。

人のソーシャル ストリーム データにも写真が含まれていることがあります。その場合は android.provider.ContactsContract.StreamItemPhotos テーブルに格納されています。このテーブルについては、ソーシャル ストリーム フォトで詳しく説明しています。