連絡先プロバイダ

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

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

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

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

連絡先プロバイダの組織

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

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

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

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

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

未加工連絡先

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

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

重要な連絡先(未加工)の列

ContactsContract.RawContacts テーブルの重要な列は、表 1 に記載されています。表の後に記載されている注をお読みください。

表 1. 重要な連絡先項目の列。

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

備考

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

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

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

連絡先データのソース

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

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

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

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

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

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

データ

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

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

わかりやすい列名

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

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

一般的な列名

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

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

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

型固有の列名

特定のタイプの行の列を操作しやすくするために、連絡先プロバイダは、ContactsContract.CommonDataKinds のサブクラスで定義されたタイプ固有の列名定数も提供します。定数は、同じ列名に異なる定数名を指定するだけです。これにより、特定の型の行のデータにアクセスできます。

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

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

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

型固有の列名が汎用列名にマッピングされる方法

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

型固有の列名クラス

表 2 に、最も一般的に使用される型固有の列名クラスを示します。

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

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

連絡先

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

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

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

連絡先プロバイダは、Contacts テーブルの連絡先行の _ID 列を使用して、連絡先行をその未加工連絡先行にリンクします。未加工の連絡先テーブルの CONTACT_IDContactsContract.RawContacts には、各未加工の連絡先行に関連付けられた連絡先行の _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 つ以上のテーブルに対する読み取りアクセス権
AndroidManifest.xml <uses-permission> 要素を <uses-permission android:name="android.permission.READ_CONTACTS"> として指定した READ_CONTACTS
1 つ以上のテーブルに対する書き込みアクセス権
AndroidManifest.xml <uses-permission> 要素を <uses-permission android:name="android.permission.WRITE_CONTACTS"> として指定した 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」)を持つ大規模なアプリケーションの一部です。メイン アクティビティには連絡先行のリストが表示されます。ユーザーが 1 つを選択すると、アクティビティはその ID を詳細アクティビティに送信します。詳細アクティビティは ContactsContract.Contacts.Entity を使用して、選択した連絡先に関連付けられたすべての未加工の連絡先のすべてのデータ行を表示します。

このスニペットは「詳細」アクティビティから取得したものです。

Kotlin

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

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

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

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

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

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

Java

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

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

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

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

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

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

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

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

一括変更

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

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

収益ポイント

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

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

Yield ポイントはアトミック オペレーションの単位でもあります。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.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() で使用できる extras 値は、ContactsContract.Intents.Insert のリファレンス ドキュメントに記載されています。

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

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

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

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

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

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

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

Kotlin

// Gets values from the UI
val name = contactNameEditText.text.toString()
val phone = contactPhoneEditText.text.toString()
val email = contactEmailEditText.text.toString()

val company = companyName.text.toString()
val jobtitle = jobTitle.text.toString()

/*
 * Demonstrates adding data rows as an array list associated with the DATA key
 */

// Defines an array list to contain the ContentValues objects for each row
val contactData = arrayListOf<ContentValues>()

/*
 * Defines the raw contact row
 */

// Sets up the row as a ContentValues object
val rawContactRow = ContentValues().apply {
    // Adds the account type and name to the row
    put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.type)
    put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.name)
}

// Adds the row to the array
contactData.add(rawContactRow)

/*
 * Sets up the phone number data row
 */

// Sets up the row as a ContentValues object
val phoneRow = ContentValues().apply {
    // Specifies the MIME type for this data row (all data rows must be marked by their type)
    put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

    // Adds the phone number and its type to the row
    put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
}

// Adds the row to the array
contactData.add(phoneRow)

/*
 * Sets up the email data row
 */

// Sets up the row as a ContentValues object
val emailRow = ContentValues().apply {
    // Specifies the MIME type for this data row (all data rows must be marked by their type)
    put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

    // Adds the email address and its type to the row
    put(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
}

// Adds the row to the array
contactData.add(emailRow)

// Creates a new intent for sending to the device's contacts application
val insertIntent = Intent(ContactsContract.Intents.Insert.ACTION).apply {
    // Sets the MIME type to the one expected by the insertion activity
    type = ContactsContract.RawContacts.CONTENT_TYPE

    // Sets the new contact name
    putExtra(ContactsContract.Intents.Insert.NAME, name)

    // Sets the new company and job title
    putExtra(ContactsContract.Intents.Insert.COMPANY, company)
    putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle)

    /*
    * Adds the array to the intent's extras. It must be a parcelable object in order to
    * travel between processes. The device's contacts app expects its key to be
    * Intents.Insert.DATA
    */
    putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData)
}

// Send out the intent to start the device's contacts app in its add contact activity.
startActivity(insertIntent)

Java

// Gets values from the UI
String name = contactNameEditText.getText().toString();
String phone = contactPhoneEditText.getText().toString();
String email = contactEmailEditText.getText().toString();

String company = companyName.getText().toString();
String jobtitle = jobTitle.getText().toString();

// Creates a new intent for sending to the device's contacts application
Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);

// Sets the MIME type to the one expected by the insertion activity
insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);

// Sets the new contact name
insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);

// Sets the new company and job title
insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);

/*
 * Demonstrates adding data rows as an array list associated with the DATA key
 */

// Defines an array list to contain the ContentValues objects for each row
ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();


/*
 * Defines the raw contact row
 */

// Sets up the row as a ContentValues object
ContentValues rawContactRow = new ContentValues();

// Adds the account type and name to the row
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType());
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName());

// Adds the row to the array
contactData.add(rawContactRow);

/*
 * Sets up the phone number data row
 */

// Sets up the row as a ContentValues object
ContentValues phoneRow = new ContentValues();

// Specifies the MIME type for this data row (all data rows must be marked by their type)
phoneRow.put(
        ContactsContract.Data.MIMETYPE,
        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
);

// Adds the phone number and its type to the row
phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);

// Adds the row to the array
contactData.add(phoneRow);

/*
 * Sets up the email data row
 */

// Sets up the row as a ContentValues object
ContentValues emailRow = new ContentValues();

// Specifies the MIME type for this data row (all data rows must be marked by their type)
emailRow.put(
        ContactsContract.Data.MIMETYPE,
        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
);

// Adds the email address and its type to the row
emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);

// Adds the row to the array
contactData.add(emailRow);

/*
 * Adds the array to the intent's extras. It must be a parcelable object in order to
 * travel between processes. The device's contacts app expects its key to be
 * Intents.Insert.DATA
 */
insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);

// Send out the intent to start the device's contacts app in its add contact activity.
startActivity(insertIntent);

データの整合性

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

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

カスタムデータ行

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

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

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

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

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

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

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

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

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

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

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

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

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

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

認証は必須ではありませんが、ほとんどの連絡先サービスで使用されています。ただし、認証を行うために 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
必須。このストリーム アイテムに関連付けられている未加工の連絡先のユーザー アカウント名。ストリーム アイテムを挿入するときは、この値を設定してください。
識別子列
必須。ストリーム アイテムを挿入するときは、次の識別子列を挿入する必要があります。
  • 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 テーブルでは、各未加工の連絡先に対して保存される行数が制限されます。この上限に達すると、連絡先プロバイダは、最も古い android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP を持つ行を自動的に削除して、新しいストリーム アイテムの行のためのスペースを確保します。上限を取得するには、コンテンツ URI android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI にクエリを発行します。コンテンツ URI 以外のすべての引数は null に設定したままにします。このクエリは、単一の行を含む Cursor を返します。この行には、単一の列 android.provider.ContactsContract.StreamItems#MAX_ITEMS が含まれます。

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

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

連絡先プロバイダによって管理されるソーシャル ストリーム データは、デバイスの連絡先アプリと連携して、ソーシャル ネットワーキング システムを既存の連絡先と結びつける強力な方法を提供します。次の機能を利用できます。

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

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

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

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

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

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

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

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

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

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

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

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

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

contacts.xml リファレンス

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

<ContactsAccountType> 要素

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

<ContactsAccountType
        xmlns:android="http://schemas.android.com/apk/res/android"
        inviteContactActivity="activity_name"
        inviteContactActionLabel="invite_command_text"
        viewContactNotifyService="view_notify_service"
        viewGroupActivity="group_view_activity"
        viewGroupActionLabel="group_action_text"
        viewStreamItemActivity="viewstream_activity_name"
        viewStreamItemPhotoActivity="viewphotostream_activity_name">

含まれるもの:

res/xml/contacts.xml

には次のものを含めることができます。

<ContactsDataKind>

説明:

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

<ContactsAccountType> の属性には属性接頭辞 android: は不要です。

属性:

inviteContactActivity
ユーザーがデバイスの連絡先アプリで [接続を追加] を選択したときにアクティブにするアプリ内のアクティビティの完全修飾クラス名。
inviteContactActionLabel
inviteContactActivity で指定されたアクティビティに対して、[接続を追加] メニューに表示されるテキスト文字列。たとえば、「Follow in my network」という文字列を使用できます。このラベルには文字列リソース識別子を使用できます。
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 テーブルでカスタムデータ行の種類の 1 つに定義したカスタム MIME タイプ。たとえば、値 vnd.android.cursor.item/vnd.example.locationstatus は、連絡先の最後に確認された位置情報を記録するデータ行のカスタム MIME タイプです。
android:icon
連絡先アプリがデータの横に表示する Android のドローアブル リソース。これは、データがサービスから取得されたことをユーザーに伝えるために使用します。
android:summaryColumn
データ行から取得された 2 つの値のうち、最初の値の列名。この値は、このデータ行のエントリの最初の行として表示されます。最初の行はデータの概要として使用することを想定していますが、省略可能です。android:detailColumn もご覧ください。
android:detailColumn
データ行から取得された 2 つの値のうち、2 番目の値の列名。値は、このデータ行のエントリの 2 行目に表示されます。android:summaryColumn もご覧ください。

連絡先プロバイダの追加機能

連絡先プロバイダは、前のセクションで説明した主な機能のほかに、連絡先データの操作に役立つ次の機能を提供します。

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

連絡先グループ

連絡先プロバイダは、関連する連絡先のコレクションに group データでラベル付けできます。ユーザー アカウントに関連付けられたサーバーがグループを維持する場合、アカウントのアカウント タイプの同期アダプターは、連絡先プロバイダとサーバーの間でグループデータを転送する必要があります。ユーザーがサーバーに新しい連絡先を追加し、その連絡先を新しいグループに入れる場合、同期アダプタは新しいグループを 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 テーブルに保存されます。このテーブルについては、ソーシャル ストリームの写真のセクションで詳しく説明します。