連絡先プロバイダ

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

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

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

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

連絡先プロバイダの組織

連絡先プロバイダは、Android コンテンツ プロバイダ コンポーネントです。個人に関する 3 種類のデータを保持します。各データは、図 1 に示すように、プロバイダが提供するテーブルに対応しています。

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

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

ContactsContract.Contacts テーブル
未加工連絡先行の集計に基づく、さまざまな人を表す行。
ContactsContract.RawContacts テーブル
ユーザー アカウントと種類に固有のデータの概要が含まれる行。
ContactsContract.Data テーブル
メールアドレスや電話番号など、未加工の連絡先の詳細を含む行。

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

未加工連絡先

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

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

重要な未加工連絡先列

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

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

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

Notes

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 にログインし、連絡先を開いて「Thomas Higginson」を追加したとします。その後、彼女は emilyd@gmail.com として Gmail にログインし、「Thomas Higginson」にメールを送信すると、自動的に Thomas Higginson を連絡先として追加します。また、Twitter で「colonel_tom」(Thomas Higginson の Twitter ID)をフォローしています。

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

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

データ

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

この 1 つのテーブルにはさまざまな種類のデータが格納されています。表示名、電話番号、メールアドレス、住所、写真、ウェブサイトの詳細の行はすべて ContactsContract.Data テーブルにあります。これを管理できるように、ContactsContract.Data テーブルには、わかりやすい名前の列と汎用名の列があります。説明名の列の内容は、行内のデータの種類に関係なく同じ意味を持ちます。一方、汎用名列の内容は、データのタイプによって意味が異なります。

わかりやすい列名

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

RAW_CONTACT_ID
このデータの未加工連絡先の _ID 列の値。
MIMETYPE
この行に保存されているデータタイプ。カスタム MIME タイプで表されます。連絡先プロバイダは、ContactsContract.CommonDataKinds のサブクラスで定義された MIME タイプを使用します。これらの MIME タイプはオープンソースであり、連絡先プロバイダと連携するあらゆるアプリや同期アダプターで使用できます。
IS_PRIMARY
未加工連絡先に対して、このタイプのデータ行が複数回出現する可能性がある場合、IS_PRIMARY 列ではそのタイプのプライマリ データを含むデータ行にフラグが設定されます。たとえば、ユーザーが連絡先の電話番号を長押しして [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. タイプ固有の列名クラス

マッピング クラス データの種類 Notes
ContactsContract.CommonDataKinds.StructuredName このデータ行に関連付けられている未加工連絡先の名前データ。 未加工連絡先は、これらの行を 1 つだけ持ちます。
ContactsContract.CommonDataKinds.Photo このデータ行に関連付けられている未加工連絡先のメインの写真。 未加工連絡先は、これらの行を 1 つだけ持ちます。
ContactsContract.CommonDataKinds.Email このデータ行に関連付けられている未加工連絡先のメールアドレス。 未加工連絡先は複数のメールアドレスを持つことができます。
ContactsContract.CommonDataKinds.StructuredPostal このデータ行に関連付けられている未加工連絡先の住所。 未加工連絡先は複数の住所を持つことができます。
ContactsContract.CommonDataKinds.GroupMembership 未加工連絡先を連絡先プロバイダのグループの 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、Raw Contacts、Details テーブルの関係。

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

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

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

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

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

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

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

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

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

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

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

アカウントの種類は一意である必要はありません。ユーザーは複数の Google 連絡先アカウントを設定して、そのデータを連絡先プロバイダにダウンロードできます。これは、個人のアカウント名と仕事用の個人用の連絡先が混在した場合に発生することがあります。通常、アカウント名は一意です。これらにより、連絡先プロバイダと外部サービス間の特定のデータフローを識別します。

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

図 4 は、ユーザーに関するデータの流れにおける連絡先プロバイダの位置付けを示しています。[同期アダプター] ボックスの各アダプターには、アカウントの種類のラベルが付けられています。

人物に関するデータの流れ

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

必要な権限

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

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

これらの権限は、ユーザー プロファイル データには適用されません。ユーザー プロファイルとそれに必要な権限については、この後のユーザー プロファイルで説明します。

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

ユーザー プロフィール

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

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

ユーザーのプロフィールは慎重に扱うべきものであることを覚えておいてください。権限 android.Manifest.permission#READ_PROFILE を使用すると、デバイス ユーザーの個人識別データにアクセスできます。アプリの説明で、ユーザー プロファイルへのアクセス権限が必要な理由をユーザーに説明してください。

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

Kotlin

// Sets the columns to retrieve for the user profile
projection = arrayOf(
        ContactsContract.Profile._ID,
        ContactsContract.Profile.DISPLAY_NAME_PRIMARY,
        ContactsContract.Profile.LOOKUP_KEY,
        ContactsContract.Profile.PHOTO_THUMBNAIL_URI
)

// Retrieves the profile from the Contacts Provider
profileCursor = contentResolver.query(
        ContactsContract.Profile.CONTENT_URI,
        projection,
        null,
        null,
        null
)

Java

// Sets the columns to retrieve for the user profile
projection = new String[]
    {
        Profile._ID,
        Profile.DISPLAY_NAME_PRIMARY,
        Profile.LOOKUP_KEY,
        Profile.PHOTO_THUMBNAIL_URI
    };

// Retrieves the profile from the Contacts Provider
profileCursor =
        getContentResolver().query(
                Profile.CONTENT_URI,
                projection ,
                null,
                null,
                null);

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

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

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

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

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

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

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

特に、ソース ID はアカウント タイプごとに一意でなければならず、同期後も変わらない必要があります。

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

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

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

  • エンティティ クエリ。
  • バッチ変更。
  • インテントによる取得と変更
  • データの整合性。

同期アダプターからの変更方法について詳しくは、連絡先プロバイダの同期アダプターをご覧ください。

エンティティのクエリ

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

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

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

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

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

次のスニペットは「detail」アクティビティから取得されます。

Kotlin

...
    /*
     * Appends the entity path to the URI. In the case of the Contacts Provider, the
     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
     */
    contactUri = Uri.withAppendedPath(
            contactUri,
            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY
    )

    // Initializes the loader identified by LOADER_ID.
    loaderManager.initLoader(
            LOADER_ID,  // The identifier of the loader to initialize
            null,       // Arguments for the loader (in this case, none)
            this        // The context of the activity
    )

    // Creates a new cursor adapter to attach to the list view
    cursorAdapter = SimpleCursorAdapter(
            this,                       // the context of the activity
            R.layout.detail_list_item,  // the view item containing the detail widgets
            mCursor,                    // the backing cursor
            fromColumns,               // the columns in the cursor that provide the data
            toViews,                   // the views in the view item that display the data
            0)                          // flags

    // Sets the ListView's backing adapter.
    rawContactList.adapter = cursorAdapter
...
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
    /*
     * Sets the columns to retrieve.
     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
     * DATA1 contains the first column in the data row (usually the most important one).
     * MIMETYPE indicates the type of data in the data row.
     */
    val projection: Array<String> = arrayOf(
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
            ContactsContract.Contacts.Entity.DATA1,
            ContactsContract.Contacts.Entity.MIMETYPE
    )

    /*
     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
     * contact collated together.
     */
    val sortOrder = "${ContactsContract.Contacts.Entity.RAW_CONTACT_ID} ASC"

    /*
     * Returns a new CursorLoader. The arguments are similar to
     * ContentResolver.query(), except for the Context argument, which supplies the location of
     * the ContentResolver to use.
     */
    return CursorLoader(
            applicationContext, // The activity's context
            contactUri,        // The entity content URI for a single contact
            projection,         // The columns to retrieve
            null,               // Retrieve all the raw contacts and their data rows.
            null,               //
            sortOrder           // Sort by the raw contact ID.
    )
}

Java

...
    /*
     * Appends the entity path to the URI. In the case of the Contacts Provider, the
     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
     */
    contactUri = Uri.withAppendedPath(
            contactUri,
            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);

    // Initializes the loader identified by LOADER_ID.
    getLoaderManager().initLoader(
            LOADER_ID,  // The identifier of the loader to initialize
            null,       // Arguments for the loader (in this case, none)
            this);      // The context of the activity

    // Creates a new cursor adapter to attach to the list view
    cursorAdapter = new SimpleCursorAdapter(
            this,                        // the context of the activity
            R.layout.detail_list_item,   // the view item containing the detail widgets
            mCursor,                     // the backing cursor
            fromColumns,                // the columns in the cursor that provide the data
            toViews,                    // the views in the view item that display the data
            0);                          // flags

    // Sets the ListView's backing adapter.
    rawContactList.setAdapter(cursorAdapter);
...
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {

    /*
     * Sets the columns to retrieve.
     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
     * DATA1 contains the first column in the data row (usually the most important one).
     * MIMETYPE indicates the type of data in the data row.
     */
    String[] projection =
        {
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
            ContactsContract.Contacts.Entity.DATA1,
            ContactsContract.Contacts.Entity.MIMETYPE
        };

    /*
     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
     * contact collated together.
     */
    String sortOrder =
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
            " ASC";

    /*
     * Returns a new CursorLoader. The arguments are similar to
     * ContentResolver.query(), except for the Context argument, which supplies the location of
     * the ContentResolver to use.
     */
    return new CursorLoader(
            getApplicationContext(),  // The activity's context
            contactUri,              // The entity content URI for a single contact
            projection,               // The columns to retrieve
            null,                     // Retrieve all the raw contacts and their data rows.
            null,                     //
            sortOrder);               // Sort by the raw contact ID.
}

読み込みが完了すると、LoaderManageronLoadFinished() へのコールバックを呼び出します。このメソッドの受信引数の 1 つは、クエリの結果を含む Cursor です。実際のアプリでは、この Cursor からデータを取得して表示や操作を行うことができます。

バッチ変更

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

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

明け方

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

明白な点では、applyBatch() の呼び出しごとに複数のトランザクションが発生します。そのため、関連する一連の行に対する最後のオペレーションに明け渡し点を設定する必要があります。たとえば、未加工連絡先行とそれに関連付けられたデータ行を追加するセットの最後のオペレーションには、1 つの連絡先に関連する行セットに対する最後のオペレーションには明け点を設定する必要があります。

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

後方参照の修正

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

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

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

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

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

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

Kotlin

// Creates a contact entry from the current UI values, using the currently-selected account.
private fun createContactEntry() {
    /*
     * Gets values from the UI
     */
    val name = contactNameEditText.text.toString()
    val phone = contactPhoneEditText.text.toString()
    val email = contactEmailEditText.text.toString()

    val phoneType: String = contactPhoneTypes[mContactPhoneTypeSpinner.selectedItemPosition]

    val emailType: String = contactEmailTypes[mContactEmailTypeSpinner.selectedItemPosition]

Java

// Creates a contact entry from the current UI values, using the currently-selected account.
protected void createContactEntry() {
    /*
     * Gets values from the UI
     */
    String name = contactNameEditText.getText().toString();
    String phone = contactPhoneEditText.getText().toString();
    String email = contactEmailEditText.getText().toString();

    int phoneType = contactPhoneTypes.get(
            contactPhoneTypeSpinner.getSelectedItemPosition());

    int emailType = contactEmailTypes.get(
            contactEmailTypeSpinner.getSelectedItemPosition());

次のスニペットは、未加工連絡先の行を ContactsContract.RawContacts テーブルに挿入するオペレーションを作成します。

Kotlin

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

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

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

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

Java

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

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

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

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

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

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

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

Kotlin

    // Creates the display name for the new raw contact, as a StructuredName data row.
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * withValueBackReference sets the value of the first argument to the value of
             * the ContentProviderResult indexed by the second argument. In this particular
             * call, the raw contact ID column of the StructuredName data row is set to the
             * value of the result returned by the first operation, which is the one that
             * actually adds the raw contact row.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to StructuredName
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)

            // Sets the data row's display name to the name in the UI.
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)

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

    // Inserts the specified phone number and type as a Phone data row
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Phone
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

            // Sets the phone number and type
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType)

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

    // Inserts the specified email and type as a Phone data row
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Email
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

            // Sets the email address and type
            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType)

    /*
     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
     * will yield priority to other threads. Use after every set of operations that affect a
     * single contact, to avoid degrading performance.
     */
    op.withYieldAllowed(true)

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

Java

    // Creates the display name for the new raw contact, as a StructuredName data row.
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * withValueBackReference sets the value of the first argument to the value of
             * the ContentProviderResult indexed by the second argument. In this particular
             * call, the raw contact ID column of the StructuredName data row is set to the
             * value of the result returned by the first operation, which is the one that
             * actually adds the raw contact row.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to StructuredName
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)

            // Sets the data row's display name to the name in the UI.
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);

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

    // Inserts the specified phone number and type as a Phone data row
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Phone
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

            // Sets the phone number and type
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);

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

    // Inserts the specified email and type as a Phone data row
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Email
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

            // Sets the email address and type
            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);

    /*
     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
     * will yield priority to other threads. Use after every set of operations that affect a
     * single contact, to avoid degrading performance.
     */
    op.withYieldAllowed(true);

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

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

Kotlin

    // Ask the Contacts Provider to create a new contact
    Log.d(TAG, "Selected account: ${mSelectedAccount.name} (${mSelectedAccount.type})")
    Log.d(TAG, "Creating contact: $name")

    /*
     * Applies the array of ContentProviderOperation objects in batch. The results are
     * discarded.
     */
    try {
        contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
    } catch (e: Exception) {
        // Display a warning
        val txt: String = getString(R.string.contactCreationFailure)
        Toast.makeText(applicationContext, txt, Toast.LENGTH_SHORT).show()

        // Log exception
        Log.e(TAG, "Exception encountered while inserting contact: $e")
    }
}

Java

    // Ask the Contacts Provider to create a new contact
    Log.d(TAG,"Selected account: " + selectedAccount.getName() + " (" +
            selectedAccount.getType() + ")");
    Log.d(TAG,"Creating contact: " + name);

    /*
     * Applies the array of ContentProviderOperation objects in batch. The results are
     * discarded.
     */
    try {

            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
    } catch (Exception e) {

            // Display a warning
            Context ctx = getApplicationContext();

            CharSequence txt = getString(R.string.contactCreationFailure);
            int duration = Toast.LENGTH_SHORT;
            Toast toast = Toast.makeText(ctx, txt, duration);
            toast.show();

            // Log exception
            Log.e(TAG, "Exception encountered while inserting contact: " + e);
    }
}

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

オプティミスティック同時実行制御は、同時にユーザーが 1 人しかなく、データ リポジトリへの同時アクセスがまれなモバイル デバイスに有用です。ロックを使用しないため、ロックの設定や、他のトランザクションによるロックの解放の待機に時間を費やす必要はありません。

ContactsContract.RawContacts の単一の行を更新する際にオプティミスティック同時実行制御を使用するには、次の手順を行います。

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

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

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

Kotlin

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

...

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

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

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

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

ops.add(assertOp.build())

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

...

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

Java

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

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

...

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

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

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

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

ops.add(assertOp.build());

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

...

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

    } catch (OperationApplicationException e) {

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

インテントによる取得と変更

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

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

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

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

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

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

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

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

新しい未加工連絡先を挿入する Insert.ACTION なし RawContacts.CONTENT_TYPE: 未加工連絡先のセットの MIME タイプ。 デバイスの連絡先アプリケーションの [連絡先の追加] 画面が表示されます。インテントに追加したエクストラ値が表示されます。startActivityForResult() で送信した場合、新しく追加された未加工連絡先のコンテンツ URI は、Intent 引数の「data」フィールドでアクティビティの onActivityResult() コールバック メソッドに返されます。値を取得するには、getData() を呼び出します。
連絡先を編集する ACTION_EDIT 連絡先の CONTENT_LOOKUP_URI。エディタ アクティビティを使用すると、ユーザーはこの連絡先に関連付けられているすべてのデータを編集できます。 Contacts.CONTENT_ITEM_TYPE さん(1 件の連絡先)。 連絡先アプリケーションに [連絡先の編集] 画面を表示します。インテントに追加したエクストラ値が表示されます。ユーザーが [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.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
必須。このストリーム アイテムに関連付けられている未加工連絡先のユーザー アカウント名。ストリーム アイテムを挿入するときは、この値を設定してください。
ID 列
必須。ストリーム アイテムを挿入するときは、次の識別子列を挿入する必要があります。
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: このストリーム アイテムが関連付けられている連絡先の android.provider.BaseColumns#_ID 値。
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: このストリーム アイテムが関連付けられている連絡先の android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY 値。
  • android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: このストリーム アイテムが関連付けられている未加工連絡先の android.provider.BaseColumns#_ID 値。
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
(省略可)ストリーム アイテムの先頭に表示できる概要情報を保存します。
android.provider.ContactsContract.StreamItemsColumns#TEXT
ストリーム アイテムのテキスト。アイテムのソースによって投稿されたコンテンツか、ストリーム アイテムを生成したアクションの説明のいずれかです。この列には、fromHtml() でレンダリング可能な任意の書式設定と埋め込みリソース画像を含めることができます。プロバイダは長いコンテンツを切り捨てたり省略したりすることがありますが、タグが壊れることがないようにします。
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
ストリーム アイテムが挿入または更新された時刻を含むテキスト文字列。エポックからのミリ秒数で指定します。ストリーム アイテムを挿入または更新するアプリケーションでこの列を維持します。この列は連絡先プロバイダによって自動的に管理されません。

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

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

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

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

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

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

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

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

クラス android.provider.ContactsContract.StreamItems.StreamItemPhotos は、単一のストリーム アイテムの写真行を含む 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 は、デバイスの連絡先アプリからインテントを受け取るサービスの完全修飾クラス名です。Notifier サービスには、IntentService を拡張するクラスを使用して、サービスがインテントを受信できるようにします。受信インテントのデータには、ユーザーがクリックした未加工連絡先のコンテンツ URI が含まれます。Notifier サービスから同期アダプターにバインドしてから呼び出し、未加工連絡先のデータを更新できます。

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

  1. プロジェクトの res/xml/ ディレクトリに contacts.xml という名前のファイルを作成します。このファイルがすでに存在する場合は、この手順をスキップできます。
  2. このファイルに <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 要素を追加します。この要素がすでに存在する場合は、この手順をスキップできます。
  3. デバイスの連絡先アプリでストリーム アイテムをクリックしたユーザーによる操作を処理するアクティビティの 1 つを登録するには、属性 viewStreamItemActivity="activityclass" を要素に追加します。ここで、activityclass は、デバイスの連絡先アプリからインテントを受け取るアクティビティの完全修飾クラス名です。
  4. デバイスの連絡先アプリでストリーム写真をユーザーがクリックしたことを処理するアクティビティを 1 つ登録するには、属性 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 値は、デバイスの連絡先アプリの [接続を追加] メニューに表示されるテキスト文字列です。

注: 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 ラベルを宣言します。

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

属性:

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

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

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

<ContactsDataKind> 要素

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

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

次に含まれる要素:

<ContactsAccountType>

説明:

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

属性:

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 テーブルに格納されます。このテーブルについて詳しくは、ソーシャル ストリームの写真のセクションをご覧ください。