連絡先プロバイダ

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

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

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

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

連絡先プロバイダの組織

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

図 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 未加工の連絡先の「deleted」フラグ。 このフラグにより、同期アダプタがサーバーから行を削除し、最終的にリポジトリから行を削除できるようになるまで、コンタクト プロバイダが行を内部で維持できます。

メモ

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

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

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

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

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

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

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

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

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

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

データ

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

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

説明的な列名

説明的な列名の例を次に示します。

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

一般的な列名

DATA1DATA15 という名前の 15 個の汎用列が一般提供されており、さらに SYNC1SYNC4 という名前の 4 個の汎用列が同期アダプタでのみ使用されます。汎用列名定数は、行に含まれるデータの種類に関係なく常に機能します。

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

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

タイプ固有の列名

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

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

注意: プロバイダの事前定義された MIME タイプのいずれかを含む行を使用して、独自のカスタムデータを ContactsContract.Data テーブルに追加しないでください。電源を切ると、データが失われたり、プロバイダが誤動作したりする可能性があります。たとえば、MIME タイプが Email.CONTENT_ITEM_TYPE の行を追加する場合、列 DATA1 にメールアドレスではなくユーザー名を含める必要があります。行に独自のカスタム MIME タイプを使用する場合は、独自のタイプ固有の列名を定義し、列を自由に使用できます。

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

型固有の列名が汎用列名にマッピングされる仕組み

図 2. 型固有の列名と汎用列名。

型固有の列名クラス

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

表 2. 型固有の列名クラス

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

連絡先

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

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

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

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

ContactsContract.Contacts テーブルには、連絡先行への「永続的な」リンクである LOOKUP_KEY 列もあります。連絡先プロバイダは連絡先を自動的に維持するため、集計または同期に応じて連絡先行の _ID 値が変更される場合があります。このような場合でも、連絡先の LOOKUP_KEY と組み合わせたコンテンツ URI CONTENT_LOOKUP_URI は引き続き連絡先行を参照するため、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 コンタクト アカウントを構成し、そのデータを連絡先プロバイダにダウンロードできます。たとえば、個人アカウント名用に 1 つの個人用連絡先セットと、仕事用に別の連絡先セットがある場合です。通常、アカウント名は一意です。これらを組み合わせることで、連絡先プロバイダと外部サービス間の特定のデータフローを特定できます。

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

図 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」に設定されます。

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

連絡先プロバイダは、リポジトリ内の連絡先データの状態を追跡するデータを管理します。リポジトリに関するこのメタデータは、Raw Contacts、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」 - このグループ内の連絡先は、アプリの 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 行を取得するか、1 つの ContactsContract.RawContacts 行のすべての ContactsContract.CommonDataKinds.Email 行を取得します。これを容易にするため、連絡先プロバイダには、テーブル間のデータベース結合のように機能するエンティティ構造が用意されています。

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

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

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

次のスニペットは、連絡先の未加工の行をすべて取得する方法を示しています。このスニペットは、2 つのアクティビティ(main と detail)を持つ大規模なアプリケーションの一部です。メイン アクティビティには連絡先行のリストが表示されます。ユーザーがいずれかを選択すると、その 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() を呼び出して、連絡先プロバイダでデータを「バッチモード」で挿入、更新、削除する必要があります。コンタクト プロバイダは、applyBatch() 内のすべてのオペレーションを 1 つのトランザクションで実行するため、変更によってコンタクト リポジトリが不整合状態になることはありません。バッチ変更では、未加工の連絡先とその詳細データを同時に挿入することもできます。

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

収益ポイント

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

収益ポイントを使用すると、applyBatch() の呼び出しごとに複数のトランザクションが発生します。そのため、関連する行のセットの最後のオペレーションに終了ポイントを設定する必要があります。たとえば、未加工の連絡先行とそれに関連するデータ行を追加するセットの最後のオペレーション、または 1 つの連絡先に関連する行のセットに対する最後のオペレーションに、終了ポイントを設定する必要があります。

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

変更の参照先

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

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

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

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

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

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

Kotlin

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

    val phoneType: String = contactPhoneTypes[mContactPhoneTypeSpinner.selectedItemPosition]

    val emailType: String = contactEmailTypes[mContactEmailTypeSpinner.selectedItemPosition]

Java

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

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

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

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

Kotlin

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

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

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

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

Java

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

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

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

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

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

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

メール行を追加する ContentProviderOperation.Builder オブジェクトには withYieldAllowed() というフラグが設定されており、これは終了ポイントを設定します。

Kotlin

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

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

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

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

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

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

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

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

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

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

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

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

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

Java

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Kotlin

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

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

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

Java

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

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

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

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

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

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

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

楽観的同時実行制御は、一度に 1 人のユーザーしか使用せず、データ リポジトリへの同時アクセスがまれなモバイル デバイスに適しています。ロックが使用されないため、ロックの設定や他のトランザクションによるロックの解除を待機する時間は浪費されません。

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

  1. 取得する他のデータとともに、未加工の連絡先の VERSION 列を取得します。
  2. newAssertQuery(Uri) メソッドを使用して、制約の適用に適した ContentProviderOperation.Builder オブジェクトを作成します。コンテンツ URI には、未加工の連絡先の _ID が付加された RawContacts.CONTENT_URI を使用します。
  3. ContentProviderOperation.Builder オブジェクトの場合は、withValue() を呼び出して、VERSION 列を取得したバージョン番号と比較します。
  4. 同じ ContentProviderOperation.BuilderwithExpectedCount() を呼び出し、このアサーションによってテストされる行が 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 やコードを記述する必要はありません。また、プロバイダへの読み取りや書き込みの権限をリクエストする必要もありません。デバイスの連絡先アプリは、連絡先の読み取り権限をアプリに委任できます。また、別のアプリからプロバイダを変更するため、書き込み権限は必要ありません。

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

表 4. 連絡先プロバイダの Intent。

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

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

新しい未加工の連絡先を挿入する Insert.ACTION なし RawContacts.CONTENT_TYPE: 未加工の連絡先セットの MIME タイプ。 デバイスの連絡帳アプリの [連絡先を追加] 画面が表示されます。インテントに追加した追加値が表示されます。startActivityForResult() で送信された場合、新しく追加された未加工の連絡先のコンテンツ URI は、Intent 引数の「data」フィールドでアクティビティの onActivityResult() コールバック メソッドに渡されます。値を取得するには、getData() を呼び出します。
連絡先を編集する ACTION_EDIT CONTENT_LOOKUP_URI は連絡先です。エディタ アクティビティでは、ユーザーはこの連絡先に関連付けられたデータを編集できます。 Contacts.CONTENT_ITEM_TYPE: 単一の連絡先。 連絡先アプリに [連絡先を編集] 画面が表示されます。インテントに追加した追加値が表示されます。ユーザーが [完了] をクリックして編集を保存すると、アクティビティがフォアグラウンドに戻ります。
データを追加できる選択ツールを表示する。 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 行は、デバイスの連絡先アプリには表示されません。また、同期アダプタで問題が発生する可能性があります。
所有する未加工の連絡先のデータのみを変更します。
通常、連絡先プロバイダは複数の異なるアカウント タイプやオンライン サービスからのデータの管理を行っています。アプリが、自身が所有する行のデータのみを変更または削除し、自身が管理するアカウントの種類と名前のデータのみを挿入するようにする必要があります。
常に、ContactsContract とそのサブクラスで定義された定数を、オーソリティ、コンテンツ URI、URI パス、列名、MIME タイプ、TYPE 値に使用します。
これらの定数を使用すると、エラーを回避できます。定数のいずれかが非推奨になっている場合は、コンパイラ警告も表示されます。

カスタムデータ行

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

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

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

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

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

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

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

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

アダプター クラスとファイルを同期する

同期アダプターは AbstractThreadedSyncAdapter のサブクラスとして実装し、Android アプリの一部としてインストールします。システムは、アプリのマニフェストの要素と、マニフェストが参照する特別な XML ファイルから同期アダプターについて学習します。XML ファイルでは、オンライン サービスのアカウント タイプとコンテンツ プロバイダの権限を定義します。これらは、アダプターを一意に識別します。同期アダプターが有効になるのは、ユーザーが同期アダプターのアカウントの種類のアカウントを追加し、同期アダプターが同期するコンテンツ プロバイダの同期を有効にした後です。この時点で、システムはアダプターの管理を開始し、コンテンツ プロバイダとサーバーの間で同期するために必要に応じてアダプターを呼び出します。

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

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

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

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

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

同期アダプターの実装

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

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

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

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

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

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

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

android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
必須。このストリーム アイテムに関連付けられている未加工連絡先のユーザーのアカウント タイプ。ストリーム アイテムを挿入するときに、この値を設定してください。
android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
必須。このストリーム アイテムに関連付けられている未加工の連絡先のユーザーのアカウント名。ストリーム アイテムを挿入するときに、この値を設定してください。
識別子列
必須。ストリーム アイテムを挿入するときに、次の 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 に追加して、単一の写真ファイルを指すコンテンツ 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 テーブルでは、各未加工の連絡先に保存される行数に上限があります。この上限に達すると、Contacts プロバイダは、最も古い android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP を持つ行を自動的に削除して、新しいストリーム アイテム行用のスペースを確保します。上限を取得するには、コンテンツ URI android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI に対してクエリを実行します。コンテンツ URI 以外のすべての引数は null に設定したままにしておきます。このクエリは、1 つの行と 1 つの列 android.provider.ContactsContract.StreamItems#MAX_ITEMS を含む 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 は、デバイスの連絡先アプリからインテントを受け取るサービスの完全修飾クラス名です。通知サービスの場合は、IntentService を拡張するクラスを使用して、サービスがインテントを受信できるようにします。受信したインテントのデータには、ユーザーがクリックした未加工の連絡先のコンテンツ URI が含まれています。通知サービスから同期アダプターにバインドして呼び出し、未加工の連絡先のデータを更新できます。

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

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

<ContactsAccountType> 要素について詳しくは、<ContactsAccountType> 要素をご覧ください。

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

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

ユーザーは、ソーシャル ネットワーキング サイトに連絡先を招待するために、デバイスの連絡帳アプリを離れる必要はありません。代わりに、デバイスの連絡帳アプリに、連絡先をアクティビティに招待するためのインテントを送信させることができます。設定方法は次のとおりです。

  1. プロジェクトの 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>

説明:

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

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

属性:

inviteContactActivity
ユーザーがデバイスの連絡先アプリで [接続を追加] を選択したときにアクティブにする、アプリ内のアクティビティの完全修飾クラス名。
inviteContactActionLabel
inviteContactActivity で指定されたアクティビティに対して、[接続を追加] メニューに表示されるテキスト文字列。 たとえば、「自分のネットワークでフォロー」という文字列を使用できます。このラベルには文字列リソース 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 テーブルに保存されます。詳しくは、ソーシャル ストリームの写真のセクションをご覧ください。