連絡先プロバイダ

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

このガイドでは、次のことを説明します。

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

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

連絡先プロバイダの組織

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

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

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

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

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

未加工連絡先

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

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

重要な未加工連絡先列

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」

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

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

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

  1. emily.dickinson@gmail.com に関連付けられた「Thomas Higginson」の未加工連絡先。 ユーザー アカウントの種類は Google です。
  2. 「Thomas Higginson」の、emilyd@gmail.com に関連付けられた新たな未加工連絡先。 ユーザー アカウントのタイプも Google です。名前が以前の名前と同じであっても、2 つ目の未加工連絡先が存在するのは、その人物が別のユーザー アカウントに追加されたためです。
  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
このタイプのデータ行が未加工連絡先に対して複数回出現する可能性がある場合は、そのタイプのプライマリ データを含むデータ行に対して、IS_PRIMARY 列のフラグを設定します。たとえば、ユーザーがある連絡先の電話番号を長押しし、[デフォルトに設定] を選択すると、その番号を含む ContactsContract.Data 行の IS_PRIMARY 列にゼロでない値が設定されます。

汎用名の列

一般提供されている DATA1DATA15 という名前の 15 個の汎用列と、同期アダプターでのみ使用される 4 つの汎用列 SYNC1SYNC4 があります。汎用名列の定数は、行に指定されているデータのタイプによらず、常に機能します。

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、Data の各テーブル間の関係。

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

上記の条件下で、システムは次のデータフィールドに書き込まれた値を定期的に消去します。

上記のデータフィールドの設定に使用される API も非推奨です。

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

アプリがこれらのフィールドや 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 Contacts アカウントは Google アカウントと同じで、アカウント名としてメールアドレスを使用します。他のサービスでは、1 単語のユーザー名または数値 ID を使用する場合があります。

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

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

図 4 に、人に関するデータの流れにおける連絡先プロバイダの位置付けを示します。[アダプターを同期] と書かれたボックスでは、各アダプターにアカウントの種類が表示されます。

人に関するデータの流れ

図 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 はアカウント タイプごとに一意で、同期間で安定している必要があります。

  • 一意: アカウントの各未加工連絡先には独自のソース 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」- このグループに属する連絡先を Android アプリケーションの UI に表示できます。
ContactsContract.Settings UNGROUPED_VISIBLE 「0」- このアカウントとアカウント タイプについて、グループに属さない連絡先は Android アプリケーションの UI に表示されなくなります。 デフォルトでは、グループに属する未加工連絡先がないなら連絡先は表示されません(未加工連絡先のグループ メンバーシップは、ContactsContract.Data テーブルの 1 つないし複数の ContactsContract.CommonDataKinds.GroupMembership 行によって示されます)。あるアカウントとアカウント タイプについて、ContactsContract.Settings テーブルの行でこのフラグを設定すると、グループのない連絡先を強制的に表示できます。このフラグの用途の 1 つとして、グループを使用しないサーバーからの連絡先を表示することが挙げられます。
「1」- このアカウントとアカウント タイプについて、グループに属さない連絡先が Android アプリケーションの UI に表示されます。
ContactsContract.SyncState (すべて) このテーブルを使用して、同期アダプタのメタデータを格納します。 このテーブルを使用すると、同期状態などの同期関連データを永続的に端末上に格納できます。

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

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

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

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

エンティティのクエリ

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

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

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

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

次のスニペットは、連絡先のすべての未加工連絡先行を取得する方法を示しています。このスニペットは、「main」と「detail」という 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
Key-Value ペアのキー。この引数の値は、変更中のテーブルに含まれる列の名前であることが必要です。
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 を取得します。この参照は、未加工連絡先の行を追加して新しい _ID 値を返す最初のオペレーションの ContentProviderResult オブジェクトを参照します。その結果、各データ行はその 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 人でデータリポジトリへの同時アクセスがまれなモバイル端末に便利です。ロックが使用されないため、ロックの設定や他のトランザクションによるロックの解放待ちで時間を無駄にしません。

1 つの ContactsContract.RawContacts 行を更新するときに楽観的並行性制御を使用する手順は次のとおりです。

  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 で連絡先関連の作業を行うことができます。このタイプのアクセスで、ユーザーは次の作業ができます。

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

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

インテントを使用して端末の連絡先アプリケーション経由で連絡先プロバイダにアクセスする場合、連絡先プロバイダにアクセスするための独自の 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 タイプ。 デバイスの連絡先アプリケーションの [連絡先を追加] 画面が表示されます。インテントに追加したエクストラ値が表示されます。startActivityForResult() を使用して送信すると、新たに追加された未加工連絡先の URI が、アクティビティの onActivityResult() コールバック メソッドの Intent 引数の「data」フィールドに格納されて戻ってきます。値を取得するには、getData() を呼び出します。
連絡先を編集する ACTION_EDIT 連絡先の CONTENT_LOOKUP_URI。エディタ アクティビティを使用すると、ユーザーがその連絡先に関連付けられている任意のデータを編集できます。 Contacts.CONTENT_ITEM_TYPE: 単一の連絡先。 連絡先アプリケーションに [連絡先の編集] 画面を表示します。インテントに追加したエクストラ値が表示されます。ユーザーが [完了] をクリックして編集内容を保存すると、アクティビティがフォアグラウンドに戻ります。
データの追加もできる選択ツールを表示します。 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.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> 要素とその <ContactsDataKind> 子要素が 1 つ以上含まれている contacts.xml ファイルを指定します。詳しくは、<ContactsDataKind> element のセクションをご覧ください。

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

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

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

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

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

  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. プロジェクトの 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 値は、デバイスの連絡先アプリの [接続を追加] メニューに表示される文字列です。

注: ContactsSourceContactsAccountType の非推奨のタグ名です。

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
ユーザーがデバイスの連絡先アプリケーションで [接続を追加] を選択したときに起動する、アプリケーション内のアクティビティの完全修飾クラス名。
inviteContactActionLabel
[接続を追加] メニューで、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.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.PhotoContactsContract.RawContacts.DisplayPhoto のリファレンス ドキュメントには、写真情報の取得例が含まれています。未加工連絡先のプライマリ サムネイルを取得するためのコンビニエンス クラスはありませんが、ContactsContract.Data テーブルにクエリを送信し、未加工連絡先の _IDPhoto.CONTENT_ITEM_TYPEIS_PRIMARY 列を選択して、未加工連絡先のプライマリ フォトの行を見つけることができます。

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