連絡先プロバイダは、人のデータに関する端末の中央リポジトリを管理する、柔軟で効果的な Android コンポーネントです。連絡先プロバイダは、端末の連絡先アプリに表示されるデータのソースであり、さらに独自アプリで連絡先プロバイダのデータにアクセスし、端末とオンライン サービスとの間でデータを転送することもできます。このプロバイダは幅広いデータソースに対応しており、各人に関してできるだけ多くのデータを管理しようとするため、複雑な構造をしています。そのため、連絡先プロバイダの API には、データの取得と変更をどちらもやりやすくするクラスやインターフェースが多数用意されています。
このガイドでは、以下について説明します。
- プロバイダの基本構造。
- プロバイダからのデータの取得方法。
- プロバイダでのデータの変更方法。
- サーバーから連絡先プロバイダとデータを同期する同期アダプタの作成方法。
このガイドは、Android のコンテンツ プロバイダの基礎知識がある読者を対象としています。Android のコンテンツ プロバイダについて詳しくは、コンテンツ プロバイダの基本をご覧ください。
連絡先プロバイダの構成
連絡先プロバイダは、Android のコンテンツ プロバイダ コンポーネントです。人に関する 3 種類のデータを保持しており、それぞれがコンテンツ プロバイダによって提供される 1 つのテーブルに対応しています。この構成を図 1 に示します。

図 1. 連絡先プロバイダのテーブル構造
この 3 つのテーブルは、一般にそれぞれのコントラクト クラス名で呼ばれます。各クラスは、テーブルによって使用されるコンテンツ URI、列名、列の値のための定数を定義しています。
-
ContactsContract.Contacts
テーブル - 各行は、未加工連絡先の行の集約に基づいて異なる人を表します。
-
ContactsContract.RawContacts
テーブル - 各行には、ユーザーのアカウントとタイプに固有の、人に関するデータの概要が格納されています。
-
ContactsContract.Data
テーブル - 各行には、メールアドレスや電話番号など、未加工連絡先の詳細情報が格納されています。
ContactsContract
に含まれるコントラクト クラスによって表現される他のテーブルは補助テーブルであり、連絡先プロバイダがその操作を管理したり、端末の連絡先アプリや電話アプリの特定機能をサポートしたりするために使用します。
未加工連絡先
未加工連絡先は、アカウント タイプとアカウント名の 1 つのペアを使用して得られる人に関するデータです。連絡先プロバイダでは、1 人に関するデータのソースとして複数のオンライン サービスを使用できるため、同じ個人に対して複数の未加工連絡先が許されます。未加工連絡先が複数ある場合、ユーザーは同じアカウント タイプの複数のアカウントから取得したその人のデータを組み合わせることができます。
未加工連絡先データの大半は、ContactsContract.RawContacts
テーブルには格納されません。その代わり、ContactsContract.Data
テーブルの 1 つまたは複数の行に格納されています。各データ行には列 Data.RAW_CONTACT_ID
があり、親 ContactsContract.RawContacts
行の RawContacts._ID
値が格納されています。
重要な未加工連絡先列
表 1 に、ContactsContract.RawContacts
テーブルの重要な列を示します。表の後の注もお読みください。
表 1. 重要な未加工連絡先列
列名 | 用途 | 注 |
---|---|---|
ACCOUNT_NAME
|
この未加工連絡先のソースであるアカウント タイプにおけるアカウント名。たとえば、Google アカウントのアカウント名は、端末オーナーの Gmail アドレスのどれかです。詳しくは、次の ACCOUNT_TYPE の項目をご覧ください。
|
この名前の形式は、アカウント タイプによって異なります。必ずしもメールアドレスとは限りません。 |
ACCOUNT_TYPE
|
この未加工連絡先のソースであるアカウント タイプ。たとえば、Google アカウントのアカウント タイプは com.google です。アカウント タイプは必ず、所有または管理しているドメインのドメイン ID で修飾してください。そうすることで、アカウント タイプが確実に一意になります。
|
連絡先データを提供するアカウント タイプには、通常、連絡先プロバイダと同期する関連同期アダプタが用意されています。 |
DELETED
|
未加工連絡先の「削除済み」フラグ。 | 連絡先プロバイダは、同期アダプタが該当行をサーバーから削除し、最終的にリポジトリから削除するまで、このフラグを基に行を内部的に管理します。 |
注
次に、ContactsContract.RawContacts
テーブルに関する重要な注を示します。
-
未加工連絡先の名前は、
ContactsContract.RawContacts
の行には格納されません。その代わり、ContactsContract.Data
テーブルのContactsContract.CommonDataKinds.StructuredName
行に格納されます。未加工連絡先は、このタイプの行をContactsContract.Data
テーブルに 1 行だけ持ちます。 -
注意:未加工連絡先の行で独自のアカウント データを使用する場合は、まずそれを
AccountManager
に登録する必要があります。登録するには、ユーザーに対し、アカウントのリストにアカウント タイプとアカウント名を追加するよう求めます。そうしないと、連絡先プロバイダによって未加工連絡先の行が自動的に削除されてしまいます。たとえば、ドメインが
com.example.dataservice
であるウェブベースのサービス用に連絡先データをアプリで管理する場合、そのサービス用のユーザー アカウントがbecky.sharp@dataservice.example.com
であれば、そのユーザーが先にそのアカウントの「タイプ」(com.example.dataservice
)と「名前」(becky.smart@dataservice.example.com
)を追加しておくことで、アプリが未加工連絡先の行を追加できるようになります。この要件については、文書で説明するか、ユーザーにタイプと名前を追加するよう求めるか、あるいはその両方を実施してください。アカウントのタイプと名前については、次のセクションで詳しく説明します。
未加工連絡先データのソース
未加工連絡先の働きを理解するために、「Emily Dickinson」というユーザーについて考えてみましょう。彼女は自分の端末に次の 3 つのアカウントを定義しています。
emily.dickinson@gmail.com
emilyd@gmail.com
- Twitter アカウント「belle_of_amherst」
このユーザーは、[Accounts] の設定でこの 3 つのアカウントすべてについて [Sync Contacts] を有効にしています。
Emily Dickinson がブラウザのウィンドウを開き、Gmail に emily.dickinson@gmail.com
としてログインし、[Contacts] を開いて「Thomas Higginson」を追加したとしましょう。後日、彼女が Gmail に emilyd@gmail.com
としてログインし、メールを「Thomas Higginson」宛てに送信すると、彼は自動的に連絡先として追加されます。彼女はまた、Twitter で「colonel_tom」(Thomas Higginson の Twitter ID)をフォローしています。
連絡先プロバイダは、この操作の結果として次の 3 行の未加工連絡先を作成します。
-
「Thomas Higginson」の、
emily.dickinson@gmail.com
に関連付けられた未加工連絡先。ユーザー アカウントのタイプは Google です。 -
「Thomas Higginson」の、
emilyd@gmail.com
に関連付けられた新たな未加工連絡先。ユーザー アカウントのタイプはやはり Google です。名前が前の行とまったく同じなのに新たな未加工連絡先が作成されたのは、この個人が異なるユーザー アカウントに追加されたからです。 - 「Thomas Higginson」の、「belle_of_amherst」に関連付けられた未加工連絡先。ユーザー アカウントのタイプは Twitter です。
データ
前にも説明したように、未加工連絡先のデータは未加工連絡先の _ID
値にリンクされている ContactsContract.Data
行に格納されます。これにより、1 つの未加工連絡先が同じタイプのデータ(メールアドレスや電話番号など)のインスタンスを複数持つことができます。たとえば、emilyd@gmail.com
に対する「Thomas Higginson」(Google アカウント emilyd@gmail.com
に関連付けられた、Thomas Higginson の未加工連絡先の行)には、thigg@gmail.com
という個人用アドレスと thomas.higginson@gmail.com
という仕事用アドレスがあり、連絡先プロバイダはこの 2 つのメールアドレスの行を格納し、両方とも同じ未加工連絡先にリンクします。
異なるタイプのデータが同じテーブルに格納されることに注目してください。表示名、電話番号、メール、住所、写真、ウェブサイトに関する詳細行はすべて、ContactsContract.Data
テーブルにあります。これを管理しやすくするため、ContactsContract.Data
テーブルの一部の列には説明的な名前が、それ以外には一般的な名前がそれぞれ付いています。説明的な名前が付いた列の内容は、行データのタイプによらず意味が同じなのに対し、一般的な名前が付いた列の内容は、データのタイプによって意味が異なります。
説明的な名前の列
説明的な名前が付いた列の例をいくつか示します。
-
RAW_CONTACT_ID
-
このデータの、未加工連絡先の
_ID
列の値。 -
MIMETYPE
-
カスタム MIME タイプで表現した、この行に格納されているデータのタイプ。連絡先プロバイダは、
ContactsContract.CommonDataKinds
のサブクラスに定義されている MIME タイプを使用します。これらの MIME タイプはオープンソースで、連絡先プロバイダと連携する任意のアプリまたは同期アダプタで使用できます。 -
IS_PRIMARY
-
このタイプのデータ行が 1 つの未加工連絡先に対して複数発生する場合、
IS_PRIMARY
列はそのタイプに対するプライマリ データを含むデータ行を示します。たとえば、ユーザーがある連絡先の電話番号を長押しし、[Set default] を選択すると、その番号を含むContactsContract.Data
行のIS_PRIMARY
列にゼロでない値が設定されます。
汎用名の列
自由に使用できる 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
テーブルには、独自のカスタムデータを追加しないでください。追加すると、データが失われたり、連絡先プロバイダが誤動作したりすることがあります。たとえば、メールアドレスではなくユーザー名が格納されている MIME タイプ Email.CONTENT_ITEM_TYPE
の行は、列 DATA1
には追加しないでください。一方、行に独自のカスタム MIME タイプを使用している場合は、独自のタイプ固有の列名を定義して列を自由に使用してかまいません。
図 2 に、説明的な名前の列とデータ列で ContactsContract.Data
行がどう見えるか、そしてタイプ固有の列名が汎用列名をどう「オーバーレイ」するかを示します。

図 2. タイプ固有の列名と汎用列名
タイプ固有列名が使用されるクラス
表 2 に、最もよく用いられるタイプ固有列名クラスを示します。
表 2.タイプ固有列名が使用されるクラス
対応クラス | データのタイプ | 注 |
---|---|---|
ContactsContract.CommonDataKinds.StructuredName |
このデータ行に関連付けられている未加工連絡先の名前データ。 | 1 つの未加工連絡先は、この行を 1 行だけ持ちます。 |
ContactsContract.CommonDataKinds.Photo |
このデータ行に関連付けられている未加工連絡先のメインの写真。 | 1 つの未加工連絡先は、この行を 1 行だけ持ちます。 |
ContactsContract.CommonDataKinds.Email |
このデータ行に関連付けられている未加工連絡先のメールアドレス。 | 1 つの未加工連絡先は複数のメールアドレスを持つことができます。 |
ContactsContract.CommonDataKinds.StructuredPostal |
このデータ行に関連付けられている未加工連絡先の住所。 | 1 つの未加工連絡先は複数の住所を持つことができます。 |
ContactsContract.CommonDataKinds.GroupMembership |
その未加工連絡先を連絡先プロバイダのグループのどれかにリンクする識別子。 | グループは、アカウント タイプとアカウント名のオプション機能です。詳しくは、連絡先グループをご覧ください。 |
連絡先
連絡先プロバイダは、すべてのアカウント タイプとアカウント名にわたって未加工連絡先の行を結び付けて 1 つの連絡先を形成します。これにより、1 人の人についてユーザーが集めた全データを表示したり変更したりしやすくなります。連絡先プロバイダは、新しい連絡先の行の作成と、既存の連絡先の行を使用した未加工連絡先の集約を管理します。アプリと同期アダプタは連絡先の追加はできず、連絡先の行の一部の列は読み取り専用です。
注:連絡先を連絡先プロバイダに insert()
を使用して追加しようとすると、UnsupportedOperationException
例外が発生します。「読み取り専用」になっている列をアップデートしようとしても、そのアップデートは無視されます。
連絡先プロバイダは、既存の連絡先と一致しない新しい未加工連絡先が追加されると、それに対して新しい連絡先を作成します。また、既存の未加工連絡先のデータが変更され、それまで関連付けられていた連絡先と一致しなくなった場合にも、同じ処理がなされます。アプリまたは同期アダプタが、既存の連絡先と一致する新しい未加工連絡先を作成すると、その新しい未加工連絡先は既存の連絡先に集約されます。
連絡先プロバイダは、連絡先の行を未加工連絡先とリンクするのに、Contacts
テーブルの _ID
列を使用します。未加工連絡先テーブル ContactsContract.RawContacts
の CONTACT_ID
列には、未加工連絡先の各行に関連付けられている連絡先の行の _ID
値が格納されます。
ContactsContract.Contacts
テーブルには列 LOOKUP_KEY
もあり、こちらは連絡先の行への「永久」リンクです。連絡先プロバイダは連絡先を自動的に管理するため、集約や同期が行われると、それに応じて連絡先の行の _ID
値が変更される場合があります。この処理が行われても、コンテンツ URI CONTENT_LOOKUP_URI
と連絡先の LOOKUP_KEY
の組み合わせは、引き続きその連絡先の行を指すため、LOOKUP_KEY
を使用して「お気に入り」の連絡先へのリンクを管理することなどができます。この列には、_ID
列の形式と関係のない独自の形式があります。
図 3 に、3 つのテーブルの相互関係を示します。

図 3. Contacts、RawContacts、Details の各テーブルの関係
注意:アプリを Google Play ストアに公開する場合、またはアプリが Android 10 (API レベル 29) 以上を実行している端末上にある場合は、限られた一連の連絡先データ フィールドとメソッドが廃止されますので、注意してください。
上記の条件下では、以下のデータ フィールドに書き込まれている値が定期的に消去されます。
-
ContactsContract.ContactOptionsColumns.LAST_TIME_CONTACTED
-
ContactsContract.ContactOptionsColumns.TIMES_CONTACTED
-
ContactsContract.DataUsageStatColumns.LAST_TIME_USED
-
ContactsContract.DataUsageStatColumns.TIMES_USED
上記のデータ フィールドの設定に使用される次の API も廃止されています。
また、次のフィールドは頻繁に使用される連絡先を返さなくなりました。これらのフィールドの一部は、連絡先が特定のデータの種類の一部である場合にのみ連絡先のランキングに影響します。
-
ContactsContract.Contacts.CONTENT_FREQUENT_URI
-
ContactsContract.Contacts.CONTENT_STREQUENT_URI
-
ContactsContract.Contacts.CONTENT_STREQUENT_FILTER_URI
-
CONTENT_FILTER_URI
(Email、Phone、Callable、Contactables データの種類にのみ影響します) -
ENTERPRISE_CONTENT_FILTER_URI
(Email、Phone、Callable データの種類にのみ影響します)
アプリがこれらのフィールドまたは API にアクセスまたはアップデートしている場合は、別のメソッドを使用してください。たとえば、プライベート コンテンツ プロバイダ、またはアプリやバックエンド システムに保存されているその他のデータを使用することで、特定のユースケースを満たすことができます。
アプリの機能がこの変更の影響を受けていないことを確認するために、これらのデータ フィールドを手動で消去できます。これを行うには、Android 4.1(API レベル 16)以降を実行している端末で次の ADB コマンドを実行します。
adb shell content delete \ --uri content://com.android.contacts/contacts/delete_usage
同期アダプタからのデータ
ユーザーは端末に連絡先データを直接入力しますが、データはウェブサービスから同期アダプタを経由して連絡先プロバイダにも流れます。同期アダプタは、端末とサービスの間のデータ転送を自動化します。同期アダプタはシステムの管理下でバックグラウンドで実行され、ContentResolver
のメソッドを呼び出してデータを管理します。
Android では、同期アダプタと連携するウェブサービスをアカウント タイプで識別します。各同期アダプタが扱うアカウント タイプは 1 つですが、そのタイプのアカウント名を複数サポートできます。アカウント タイプとアカウント名については、未加工連絡先データのソースで簡単に説明しています。次に、アカウント タイプとアカウント名が同期アダプタやサービスとどのような関係にあるかを詳しく説明します。
- アカウント タイプ
-
ユーザーがデータを格納しているサービスを示します。ほとんどの場合、ユーザーにはサービスに対する認証が求められます。たとえば、Google コンタクトはアカウント タイプの 1 つで、コード
google.com
で識別されます。この値は、AccountManager
によって使用されるアカウント タイプに対応します。 - アカウント名
- あるアカウント タイプで使用する特定のアカウントまたはログインを示します。Google コンタクト アカウントは Google アカウントと同じで、アカウント名としてメールアドレスを使用します。他のサービスでは、1 語のユーザー名や数字の ID が使われていることもあります。
アカウント タイプは、一意である必要はありません。1 人のユーザーが複数の Google コンタクト アカウントを設定し、それぞれのデータを連絡先プロバイダにダウンロードするということが可能です。このような使い方は、そのユーザーが個人用のアカウント名で私用の連絡先を、仕事用のアカウント名で仕事用の連絡先を管理している場合にありえます。アカウント名は、普通は一意です。この 2 つを組み合わせて、連絡先プロバイダと外部サービスとの間のある決まったデータフローを識別します。
独自サービスのデータを連絡先プロバイダに転送する場合は、独自の同期アダプタを作成する必要があります。詳しくは、連絡先プロバイダの同期アダプタをご覧ください。
図 4 に、人に関するデータの流れにおける連絡先プロバイダの位置付けを示します。右から 2 列目の各ボックス内のアダプタには、そのアダプタのアカウント タイプが示されています。

図 4. 連絡先プロバイダに絡んだデータの流れ
必要なパーミッション
連絡先プロバイダにアクセスするアプリは、次のパーミッションを要求する必要があります。
- 1 つ以上のテーブルに対する読み取りアクセス
-
READ_CONTACTS
。AndroidManifest.xml
に<uses-permission>
要素を使用して<uses-permission android:name="android.permission.READ_CONTACTS">
のように指定します。 - 1 つ以上のテーブルに対する書き込みアクセス
-
WRITE_CONTACTS
。AndroidManifest.xml
に<uses-permission>
要素を使用して<uses-permission android:name="android.permission.WRITE_CONTACTS">
のように指定します。
これらのパーミッションは、ユーザー プロファイル データにまでは拡張されません。ユーザー プロファイルとそれに必要なパーミッションについては、次のセクションであるユーザー プロファイルで説明しています。
ユーザーの連絡先データは個人的で秘密性の高い情報であることを再度ご確認ください。ユーザーはプライバシーに敏感であり、アプリが自分や自分の連絡先に関するデータを集めることを望みません。連絡先データにアクセスするためのパーミッションが必要な理由が明らかでないと、ユーザーがアプリを低く評価したりインストールを拒否したりすることがあります。
ユーザー プロファイル
ContactsContract.Contacts
テーブルには、その端末のユーザーのプロファイル データを格納している行が 1 行あります。このデータが記述しているのは端末の user
であって、そのユーザーの連絡先ではありません。プロファイルの連絡先の行は、プロファイルを使用する各システムの未加工連絡先の行にリンクされています。プロファイルの未加工連絡先の各行は、複数のデータ行を持つことができます。ユーザー プロファイルにアクセスするための定数は、ContactsContract.Profile
クラスに用意されています。
ユーザー プロファイルにアクセスするには、特別なパーミッションが必要です。読み取りと書き込みに必要な READ_CONTACTS
パーミッションと WRITE_CONTACTS
パーミッションの他に、ユーザー プロファイルに対する読み取りと書き込みのために android.Manifest.permission#READ_PROFILE パーミッションと android.Manifest.permission#WRITE_PROFILE パーミッションがそれぞれ必要です。
ユーザーのプロファイルは秘密性の高い情報であることを再度ご確認ください。パーミッション android.Manifest.permission#READ_PROFILE を使用すると、端末ユーザーの個人識別データにアクセスできます。アプリの説明には、ユーザー プロファイルへのアクセス パーミッションが必要な理由を必ず記載してください。
ユーザーのプロファイルが格納された連絡先の行を取得するには、ContentResolver.query()
を呼び出します。コンテンツ URI を CONTENT_URI
に設定し、選択条件は何も指定しません。このコンテンツ URI は、そのプロファイルの未加工連絡先やデータを取得するためのベース URI としても使用できます。たとえば、次のスニペットは指定されたプロファイルのデータを取得します。
Kotlin
// Sets the columns to retrieve for the user profile projection = arrayOf( ContactsContract.Profile._ID, ContactsContract.Profile.DISPLAY_NAME_PRIMARY, ContactsContract.Profile.LOOKUP_KEY, ContactsContract.Profile.PHOTO_THUMBNAIL_URI ) // Retrieves the profile from the Contacts Provider profileCursor = contentResolver.query( ContactsContract.Profile.CONTENT_URI, projection, null, null, null )
Java
// Sets the columns to retrieve for the user profile projection = new String[] { Profile._ID, Profile.DISPLAY_NAME_PRIMARY, Profile.LOOKUP_KEY, Profile.PHOTO_THUMBNAIL_URI }; // Retrieves the profile from the Contacts Provider profileCursor = getContentResolver().query( Profile.CONTENT_URI, projection , null, null, null);
注:連絡先の行を複数取得する場合、その中の 1 つがユーザー プロファイルかどうかを確認するには、行の IS_USER_PROFILE
列をテストします。その連絡先がユーザー プロファイルであれば、この列は「1」に設定されています。
連絡先プロバイダのメタデータ
連絡先プロバイダは、連絡先データの状態を継続的に追跡するためのデータをリポジトリで管理します。リポジトリに関するこのメタデータは、RawContacts、Data、Contacts の各テーブルの行、ContactsContract.Settings
テーブル、ContactsContract.SyncState
テーブルなど、さまざまな場所に格納されています。次の表に、各メタデータの効果を示します。
表 3. 連絡先プロバイダに用意されているメタデータ
テーブル | 列 | 値 | 意味 |
---|---|---|---|
ContactsContract.RawContacts |
DIRTY |
「0」 - 前回の同期以降、変更はありません。 |
端末上で変更があり、サーバーと同期する必要がある未加工連絡先をマークします。Android アプリが行をアップデートすると、連絡先プロバイダによって値が自動的に設定されます。
未加工連絡先やデータのテーブルを変更する同期アダプタは、使用するコンテンツ URI の末尾に文字列 |
「1」 - 前回の同期以降に変更があり、サーバーへの同期が必要です。 | |||
ContactsContract.RawContacts |
VERSION |
この行のバージョン番号。 | この行またはその関連データが変更されるたび、連絡先プロバイダがこの値を自動的にインクリメントします。 |
ContactsContract.Data |
DATA_VERSION |
この行のバージョン番号。 | このデータ行が変更されるたび、連絡先プロバイダがこの値を自動的にインクリメントします。 |
ContactsContract.RawContacts |
SOURCE_ID |
この未加工連絡先をそれが作成されたアカウントと一意に結び付ける文字列値。 |
同期アダプタが新しい未加工連絡先を作成するたび、この列はその未加工連絡先に対するサーバーの一意の ID に設定される必要があります。Android アプリが新しい未加工連絡先を作成した場合、そのアプリはこの列を空欄のままにする必要があります。同期アダプタはこれを確認して、サーバー上に新しい未加工連絡先を作成し、SOURCE_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 |
(すべて) | このテーブルを使用して、同期アダプタのメタデータを格納します。 | このテーブルを使用すると、同期状態などの同期関連データを永続的に端末上に格納できます。 |
連絡先プロバイダへのアクセス
このセクションでは、連絡先プロバイダからのデータにアクセスするためのガイドラインについて、以下に注目して説明します。
- エンティティ クエリ。
- バッチ変更。
- インテントを使用した取得と変更。
- データの整合性。
同期アダプタからの変更については、連絡先プロバイダの同期アダプタでも詳しく説明しています。
連絡先を検索するときに連絡先プロバイダを使用する方法の例については、Basic Contactables のサンプルをご覧ください。
エンティティのクエリ
連絡先プロバイダのテーブルは階層構造になっており、ある行とその「子」の行すべてを取得すると便利なことがよくあります。たとえば、ある人に関するすべての情報を表示するために、ContactsContract.Contacts
行 1 行に対するすべての ContactsContract.RawContacts
行や、ContactsContract.RawContacts
行 1 行に対するすべての ContactsContract.CommonDataKinds.Email
行を取得することが考えられます。こうした処理をやりやすくするため、連絡先プロバイダにはエンティティ構造が用意されています。これは、テーブル間でのデータベースの和集合のように機能します。
1 つのエンティティは、ある親テーブルとその子テーブルから選ばれた列からなるテーブルのようなものです。エンティティをクエリする場合は、そのエンティティで使用できる列に基づいてプロジェクションと検索の条件を指定します。その結果が Cursor
で、取得された各子テーブル行につき 1 行を含みます。たとえば、ある連絡先名と、その名前のすべての未加工連絡先の ContactsContract.CommonDataKinds.Email
行について、ContactsContract.Contacts.Entity
をクエリすると、各 ContactsContract.CommonDataKinds.Email
行につき 1 行を含む Cursor
が得られます。
エンティティにより、クエリが簡素化されます。エンティティを使用することで、ある連絡先または未加工連絡先の連絡先データをすべて取得できるので、まず親テーブルにクエリして ID を取得し、次にその ID を使用して子テーブルにクエリする必要がありません。また、連絡先プロバイダは、エンティティに対するクエリを 1 回のトランザクションで処理することから、取得されたデータの内部的な整合性が保証されます。
注:エンティティには通常、親テーブルと子テーブルのすべての列が含まれるわけではありません。そのエンティティの列名定数のリストにない列名に対して作業しようとすると、Exception
が発生します。
次のスニペットは、ある連絡先のすべての未加工連絡先を取得する方法を示しています。このスニペットは、「メイン」と「詳細」という 2 つのアクティビティを持つ、もっと大きなアプリの一部です。メイン アクティビティは、連絡先の行の一覧を示します。ユーザーが 1 つを選択すると、このアクティビティはその ID を詳細アクティビティに送ります。詳細アクティビティは ContactsContract.Contacts.Entity
を使用して、選択した連絡先と関連付けられているすべての未加工連絡先から取得したすべてのデータ行を示します。
このスニペットは「詳細」アクティビティからの抜粋です。
Kotlin
... /* * Appends the entity path to the URI. In the case of the Contacts Provider, the * expected URI is content://com.google.contacts/#/entity (# is the ID value). */ contactUri = Uri.withAppendedPath( contactUri, ContactsContract.Contacts.Entity.CONTENT_DIRECTORY ) // Initializes the loader identified by LOADER_ID. loaderManager.initLoader( LOADER_ID, // The identifier of the loader to initialize null, // Arguments for the loader (in this case, none) this // The context of the activity ) // Creates a new cursor adapter to attach to the list view cursorAdapter = SimpleCursorAdapter( this, // the context of the activity R.layout.detail_list_item, // the view item containing the detail widgets mCursor, // the backing cursor fromColumns, // the columns in the cursor that provide the data toViews, // the views in the view item that display the data 0) // flags // Sets the ListView's backing adapter. rawContactList.adapter = cursorAdapter ... override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { /* * Sets the columns to retrieve. * RAW_CONTACT_ID is included to identify the raw contact associated with the data row. * DATA1 contains the first column in the data row (usually the most important one). * MIMETYPE indicates the type of data in the data row. */ val projection: Array<String> = arrayOf( ContactsContract.Contacts.Entity.RAW_CONTACT_ID, ContactsContract.Contacts.Entity.DATA1, ContactsContract.Contacts.Entity.MIMETYPE ) /* * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw * contact collated together. */ val sortOrder = "${ContactsContract.Contacts.Entity.RAW_CONTACT_ID} ASC" /* * Returns a new CursorLoader. The arguments are similar to * ContentResolver.query(), except for the Context argument, which supplies the location of * the ContentResolver to use. */ return CursorLoader( applicationContext, // The activity's context contactUri, // The entity content URI for a single contact projection, // The columns to retrieve null, // Retrieve all the raw contacts and their data rows. null, // sortOrder // Sort by the raw contact ID. ) }
Java
... /* * Appends the entity path to the URI. In the case of the Contacts Provider, the * expected URI is content://com.google.contacts/#/entity (# is the ID value). */ contactUri = Uri.withAppendedPath( contactUri, ContactsContract.Contacts.Entity.CONTENT_DIRECTORY); // Initializes the loader identified by LOADER_ID. getLoaderManager().initLoader( LOADER_ID, // The identifier of the loader to initialize null, // Arguments for the loader (in this case, none) this); // The context of the activity // Creates a new cursor adapter to attach to the list view cursorAdapter = new SimpleCursorAdapter( this, // the context of the activity R.layout.detail_list_item, // the view item containing the detail widgets mCursor, // the backing cursor fromColumns, // the columns in the cursor that provide the data toViews, // the views in the view item that display the data 0); // flags // Sets the ListView's backing adapter. rawContactList.setAdapter(cursorAdapter); ... @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { /* * Sets the columns to retrieve. * RAW_CONTACT_ID is included to identify the raw contact associated with the data row. * DATA1 contains the first column in the data row (usually the most important one). * MIMETYPE indicates the type of data in the data row. */ String[] projection = { ContactsContract.Contacts.Entity.RAW_CONTACT_ID, ContactsContract.Contacts.Entity.DATA1, ContactsContract.Contacts.Entity.MIMETYPE }; /* * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw * contact collated together. */ String sortOrder = ContactsContract.Contacts.Entity.RAW_CONTACT_ID + " ASC"; /* * Returns a new CursorLoader. The arguments are similar to * ContentResolver.query(), except for the Context argument, which supplies the location of * the ContentResolver to use. */ return new CursorLoader( getApplicationContext(), // The activity's context contactUri, // The entity content URI for a single contact projection, // The columns to retrieve null, // Retrieve all the raw contacts and their data rows. null, // sortOrder); // Sort by the raw contact ID. }
読み込みが完了すると、LoaderManager
は onLoadFinished()
に対するコールバックを起動します。このメソッドへの入力引数の 1 つは、クエリの結果を含む Cursor
です。独自アプリでは、データをこの Cursor
から取得して表示したりさらに処理したりできます。
バッチ変更
可能であれば必ず、連絡先プロバイダのデータを「バッチモード」で挿入、アップデート、削除してください。そのためには、ContentProviderOperation
オブジェクトの ArrayList
を作成し、applyBatch()
を呼び出します。連絡先プロバイダはすべての操作を 1 つのトランザクションの 1 つの applyBatch()
で行うため、変更内容が連絡先リポジトリで不整合のままになることは決してありません。また、バッチ変更では、未加工連絡先とその詳細データを同時に挿入しやすくなっています。
注:1 つの未加工連絡先を変更する場合は、変更を独自アプリ内で処理するのではなく、インテントを端末の連絡先アプリに送信することを検討してください。この方法については、インテントを使用した取得と変更で詳しく説明しています。
明け渡し点
多数の操作を伴うバッチ変更は、他のプロセスをブロックしてユーザーにとっての全体的な使用感を悪化させかねません。意図したすべての変更をできるだけ少ない個別リストに整理するとともに、それらがシステムをブロックしないようにするために、1 つまたは複数の操作に明け渡し点を設定してください。明け渡し点の実体は、isYieldAllowed()
の値が true
に設定された ContentProviderOperation
オブジェクトです。連絡先プロバイダが明け渡し点に遭遇すると、作業を一時中断して他のプロセスを実行させ、現在のトランザクションをクローズします。作業を再開した連絡先プロバイダは、ArrayList
に含まれている次の操作を行い、新しいトランザクションを始めます。
明け渡し点により、applyBatch()
の呼び出し 1 回につき複数のトランザクションが発生します。そのため、明け渡し点は 1 組の関連する行への最後の操作に設定してください。たとえば、未加工連絡先の行とその関連データ行を追加する一連の操作の最後に、あるいは 1 人の連絡先に関連する 1 組の行に対する最後の操作に、明け渡し点を設定します。
明け渡し点は、アトミック操作の単位でもあります。2 つの明け渡し点間のすべてのアクセスは、1 つのまとまりとして成功または失敗します。明け渡し点を設定しない場合、最小のアトミック操作は操作のバッチ全体になります。明け渡し点を使用すると、操作がシステムのパフォーマンスを低下させるのを防ぐと同時に、操作の一部分が確実にアトミックになります。
変更の後方参照
新しい未加工連絡先の行とその関連データの行を 1 組の ContentProviderOperation
オブジェクトとして挿入している場合は、データの行を未加工連絡先の行にリンクする必要があります。そのためには、未加工連絡先の _ID
値を RAW_CONTACT_ID
値として挿入します。ただし、この値は、データの行のために ContentProviderOperation
を作成している間は使用できません。未加工連絡先に ContentProviderOperation
操作がまだ適用されていないからです。これを回避するため、ContentProviderOperation.Builder
クラスにはメソッド withValueBackReference()
が用意されています。このメソッドを使用すると、前の操作の結果を使用して列を挿入または変更できます。
withValueBackReference()
メソッドには引数が 2 つあります。
-
key
- キーと値のペアのキーです。この引数の値は、変更中のテーブルに含まれる列の名前であることが必要です。
-
previousResult
-
applyBatch()
から取得したContentProviderResult
オブジェクトの配列に含まれる値の、0 ベースのインデックスです。バッチ操作が適用されると、各操作の結果は結果用の中間配列に格納されます。previousResult
値はそうした結果の 1 つのインデックスで、key
値を使用して取得され、格納されます。これにより、新しい未加工連絡先レコードを挿入してその_ID
値に戻り、次にContactsContract.Data
行を追加するときにその値を「後方参照」できます。applyBatch()
を初めて呼び出すと、指定したContentProviderOperation
オブジェクトのArrayList
と同じサイズを使用して、結果配列全体が作成されます。ただし、結果配列に含まれるすべての要素はnull
に設定され、そのためまだ適用されていない操作から結果を後方参照しようとするとwithValueBackReference()
がException
をスローします。
次のスニペットは、新しい未加工連絡先とデータをバッチで挿入する方法を示しています。その中には、明け渡し点を指定し、後方参照を使用するコードが含まれています。
最初のスニペットは、連絡先データを UI から取得します。この時点で、ユーザーは新しい未加工連絡先の追加先となるアカウントを既に選択しています。
Kotlin
// Creates a contact entry from the current UI values, using the currently-selected account. private fun createContactEntry() { /* * Gets values from the UI */ val name = contactNameEditText.text.toString() val phone = contactPhoneEditText.text.toString() val email = contactEmailEditText.text.toString() val phoneType: String = contactPhoneTypes[mContactPhoneTypeSpinner.selectedItemPosition] val emailType: String = contactEmailTypes[mContactEmailTypeSpinner.selectedItemPosition]
Java
// Creates a contact entry from the current UI values, using the currently-selected account. protected void createContactEntry() { /* * Gets values from the UI */ String name = contactNameEditText.getText().toString(); String phone = contactPhoneEditText.getText().toString(); String email = contactEmailEditText.getText().toString(); int phoneType = contactPhoneTypes.get( contactPhoneTypeSpinner.getSelectedItemPosition()); int emailType = contactEmailTypes.get( contactEmailTypeSpinner.getSelectedItemPosition());
次のスニペットは、未加工連絡先の行を ContactsContract.RawContacts
テーブルに挿入する操作を作成します。
Kotlin
/* * Prepares the batch operation for inserting a new raw contact and its data. Even if * the Contacts Provider does not have any data for this person, you can't add a Contact, * only a raw contact. The Contacts Provider will then add a Contact automatically. */ // Creates a new array of ContentProviderOperation objects. val ops = arrayListOf<ContentProviderOperation>() /* * Creates a new raw contact with its account type (server type) and account name * (user's account). Remember that the display name is not stored in this row, but in a * StructuredName data row. No other data is required. */ var op: ContentProviderOperation.Builder = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.name) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.type) // Builds the operation and adds it to the array of operations ops.add(op.build())
Java
/* * Prepares the batch operation for inserting a new raw contact and its data. Even if * the Contacts Provider does not have any data for this person, you can't add a Contact, * only a raw contact. The Contacts Provider will then add a Contact automatically. */ // Creates a new array of ContentProviderOperation objects. ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); /* * Creates a new raw contact with its account type (server type) and account name * (user's account). Remember that the display name is not stored in this row, but in a * StructuredName data row. No other data is required. */ ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType()) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName()); // Builds the operation and adds it to the array of operations ops.add(op.build());
次に、表示名、電話、メールのデータ行を作成します。
操作の各ビルダー オブジェクトは、withValueBackReference()
を使用して RAW_CONTACT_ID
を取得します。この参照が最初の操作からの ContentProviderResult
オブジェクトを後方参照しており、それが未加工連絡先の行を追加し、新しい _ID
値を返します。その結果、各データ行はその RAW_CONTACT_ID
によって、属する新しい ContactsContract.RawContacts
行に自動的にリンクされます。
メール行を追加する ContentProviderOperation.Builder
オブジェクトは、明け渡し点を設定する withYieldAllowed()
でフラグ付けされます。
Kotlin
// Creates the display name for the new raw contact, as a StructuredName data row. op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * withValueBackReference sets the value of the first argument to the value of * the ContentProviderResult indexed by the second argument. In this particular * call, the raw contact ID column of the StructuredName data row is set to the * value of the result returned by the first operation, which is the one that * actually adds the raw contact row. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to StructuredName .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) // Sets the data row's display name to the name in the UI. .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name) // Builds the operation and adds it to the array of operations ops.add(op.build()) // Inserts the specified phone number and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Phone .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) // Sets the phone number and type .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType) // Builds the operation and adds it to the array of operations ops.add(op.build()) // Inserts the specified email and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Email .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) // Sets the email address and type .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email) .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType) /* * Demonstrates a yield point. At the end of this insert, the batch operation's thread * will yield priority to other threads. Use after every set of operations that affect a * single contact, to avoid degrading performance. */ op.withYieldAllowed(true) // Builds the operation and adds it to the array of operations ops.add(op.build())
Java
// Creates the display name for the new raw contact, as a StructuredName data row. op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * withValueBackReference sets the value of the first argument to the value of * the ContentProviderResult indexed by the second argument. In this particular * call, the raw contact ID column of the StructuredName data row is set to the * value of the result returned by the first operation, which is the one that * actually adds the raw contact row. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to StructuredName .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) // Sets the data row's display name to the name in the UI. .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name); // Builds the operation and adds it to the array of operations ops.add(op.build()); // Inserts the specified phone number and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Phone .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) // Sets the phone number and type .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType); // Builds the operation and adds it to the array of operations ops.add(op.build()); // Inserts the specified email and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Email .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) // Sets the email address and type .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email) .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType); /* * Demonstrates a yield point. At the end of this insert, the batch operation's thread * will yield priority to other threads. Use after every set of operations that affect a * single contact, to avoid degrading performance. */ op.withYieldAllowed(true); // Builds the operation and adds it to the array of operations ops.add(op.build());
最後のスニペットは、新しい未加工連絡先とデータ行を挿入する applyBatch()
を呼び出しています。
Kotlin
// Ask the Contacts Provider to create a new contact Log.d(TAG, "Selected account: ${mSelectedAccount.name} (${mSelectedAccount.type})") Log.d(TAG, "Creating contact: $name") /* * Applies the array of ContentProviderOperation objects in batch. The results are * discarded. */ try { contentResolver.applyBatch(ContactsContract.AUTHORITY, ops) } catch (e: Exception) { // Display a warning val txt: String = getString(R.string.contactCreationFailure) Toast.makeText(applicationContext, txt, Toast.LENGTH_SHORT).show() // Log exception Log.e(TAG, "Exception encountered while inserting contact: $e") } }
Java
// Ask the Contacts Provider to create a new contact Log.d(TAG,"Selected account: " + selectedAccount.getName() + " (" + selectedAccount.getType() + ")"); Log.d(TAG,"Creating contact: " + name); /* * Applies the array of ContentProviderOperation objects in batch. The results are * discarded. */ try { getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (Exception e) { // Display a warning Context ctx = getApplicationContext(); CharSequence txt = getString(R.string.contactCreationFailure); int duration = Toast.LENGTH_SHORT; Toast toast = Toast.makeText(ctx, txt, duration); toast.show(); // Log exception Log.e(TAG, "Exception encountered while inserting contact: " + e); } }
バッチ処理を使用すると、楽観的同時実行制御を実装できます。これは、背後のリポジトリをロックする必要なく変更トランザクションを適用する手法です。この手法を使用するには、トランザクションを適用してから、同時に行うことのできる可能性のある他の変更をチェックします。一貫性のない変更が発生していることがわかった場合は、トランザクションをロールバックしてやり直します。
楽観的同時実行制御は、一度に使用するユーザーが 1 人でデータ リポジトリへの同時アクセスがまれなモバイル端末に便利です。ロックが使用されないため、ロックの設定や他のトランザクションによるロックの解放待ちで時間を無駄にしません。
ContactsContract.RawContacts
行を 1 行更新している間に楽観的同時実行制御を使用する方法は次のとおりです。
-
取得する他のデータとともに、未加工連絡先の
VERSION
列を取得します。 -
制約を強制するのに適した
ContentProviderOperation.Builder
オブジェクトを、メソッドnewAssertQuery(Uri)
を使用して作成します。コンテンツ URI に、未加工連絡先の_ID
が末尾に追加されたRawContacts.CONTENT_URI
を使用します。 -
ContentProviderOperation.Builder
オブジェクトに対し、withValue()
を呼び出して、VERSION
列と取得したばかりのバージョン番号を比較します。 -
同じ
ContentProviderOperation.Builder
に対し、withExpectedCount()
を呼び出して、このアサーションで 1 行だけがテストされるようにします。 -
build()
を呼び出してContentProviderOperation
オブジェクトを作成し、次のこのオブジェクトをapplyBatch()
に渡すArrayList
に先頭オブジェクトとして追加します。 - バッチ トランザクションを適用します。
目的の未加工連絡先の行が、行の読み込みからその変更の試行までの間に別の操作によってアップデートされた場合、ContentProviderOperation
の「アサート」が失敗し、操作のバッチ全体がバックアウトされます。バックアウトが終わったら、バッチをやり直すこと、または他のアクションを行うことができます。
次のスニペットは、CursorLoader
を使用して未加工連絡先を 1 つクエリした後に、ContentProviderOperation
を「アサート」します。
Kotlin
/* * The application uses CursorLoader to query the raw contacts table. The system calls this method * when the load is finished. */ override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) { // Gets the raw contact's _ID and VERSION values rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)) mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION)) } ... // Sets up a Uri for the assert operation val rawContactUri: Uri = ContentUris.withAppendedId( ContactsContract.RawContacts.CONTENT_URI, rawContactID ) // Creates a builder for the assert operation val assertOp: ContentProviderOperation.Builder = ContentProviderOperation.newAssertQuery(rawContactUri).apply { // Adds the assertions to the assert operation: checks the version withValue(SyncColumns.VERSION, mVersion) // and count of rows tested withExpectedCount(1) } // Creates an ArrayList to hold the ContentProviderOperation objects val ops = arrayListOf<ContentProviderOperation>() ops.add(assertOp.build()) // You would add the rest of your batch operations to "ops" here ... // Applies the batch. If the assert fails, an Exception is thrown try { val results: Array<ContentProviderResult> = contentResolver.applyBatch(AUTHORITY, ops) } catch (e: OperationApplicationException) { // Actions you want to take if the assert operation fails go here }
Java
/* * The application uses CursorLoader to query the raw contacts table. The system calls this method * when the load is finished. */ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { // Gets the raw contact's _ID and VERSION values rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)); mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION)); } ... // Sets up a Uri for the assert operation Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactID); // Creates a builder for the assert operation ContentProviderOperation.Builder assertOp = ContentProviderOperation.newAssertQuery(rawContactUri); // Adds the assertions to the assert operation: checks the version and count of rows tested assertOp.withValue(SyncColumns.VERSION, mVersion); assertOp.withExpectedCount(1); // Creates an ArrayList to hold the ContentProviderOperation objects ArrayList ops = new ArrayList<ContentProviderOperation>; ops.add(assertOp.build()); // You would add the rest of your batch operations to "ops" here ... // Applies the batch. If the assert fails, an Exception is thrown try { ContentProviderResult[] results = getContentResolver().applyBatch(AUTHORITY, ops); } catch (OperationApplicationException e) { // Actions you want to take if the assert operation fails go here }
インテントを使用した取得と変更
インテントを端末の連絡先アプリに送信すると、連絡先プロバイダに間接的にアクセスできます。インテントが端末の連絡先アプリ UI を起動し、ユーザーはそこで連絡先関連の作業を行えます。このタイプのアクセスで、ユーザーは次の作業ができます。
- 連絡先をリストから選び、アプリケーションに返してさらに作業する。
- 既存の連絡先データを編集する。
- 任意のアカウントに新しい未加工連絡先を挿入する。
- 連絡先または連絡先データを削除する。
ユーザーがデータを挿入またはアップデートしている場合は、まずデータを収集し、次にそれをインテントの一部として送信できます。
インテントを使用して端末の連絡先アプリ経由で連絡先プロバイダにアクセスする場合、連絡先プロバイダにアクセスするための独自の UI やコードを作成する必要はありません。また、連絡先プロバイダに対する読み取りや書き込みのパーミッションを要求する必要もありません。端末の連絡先アプリは、連絡先に対する読み取りパーミッションをデリゲートできます。また、連絡先プロバイダへの変更を他のアプリ経由で行っていることから、書き込みパーミッションは不要です。
プロバイダにアクセスするためのインテントを送信する汎用プロセスについては、コンテンツ プロバイダの基本のセクション「インテント経由のデータアクセス」で詳しく説明しています。表 4 に、利用可能なタスクで使用できるアクション、MIME タイプ、データ値をまとめます。putExtra()
で使用できるエクストラ値については、ContactsContract.Intents.Insert
のリファレンス ドキュメントに一覧があります。
表 4. 連絡先プロバイダのインテント
タスク | アクション | データ | MIME タイプ | 注 |
---|---|---|---|---|
連絡先をリストから選択 | ACTION_PICK |
次のどれかです。
|
使用せず |
指定したコンテンツ URI のタイプに応じて、未加工連絡先のリストか、未加工連絡先から取得されたデータのリストを表示します。
|
新しい未加工連絡先を挿入する | Insert.ACTION |
なし |
RawContacts.CONTENT_TYPE 。未加工連絡先のセットに対する MIME タイプです。
|
端末の連絡先アプリの [Add Contact] 画面を表示します。インテントに追加したエクストラ値が表示されます。startActivityForResult() を使用して送信すると、新たに追加された未加工連絡先の URI が、アクティビティの onActivityResult() コールバック メソッドの Intent 引数の「data」フィールドに格納されて戻ってきます。この値を取得するには、getData() を呼び出します。
|
連絡先を編集する | ACTION_EDIT |
連絡先の CONTENT_LOOKUP_URI 。エディタ アクティビティを使用すると、ユーザーがその連絡先に関連付けられている任意のデータを編集できます。
|
Contacts.CONTENT_ITEM_TYPE 。1 つの連絡先です。 |
連絡先アプリに連絡先の編集画面を表示します。インテントに追加したエクストラ値が表示されます。ユーザーが [Done] をタップして編集内容を保存すると、アクティビティがフォアグラウンドに戻ります。 |
ピッカー(データ追加も可能)を表示 | ACTION_INSERT_OR_EDIT |
なし |
CONTENT_ITEM_TYPE
|
このインテントは、連絡先アプリのピッカー画面を常に表示します。ユーザーは、編集する連絡先を選ぶか、新しい連絡先を追加できます。ユーザーの選択に応じて編集画面か追加画面が開き、インテントに含めて渡したエクストラ データが表示されます。メールアドレスや電話番号などの連絡先データを独自アプリに表示する場合は、このインテントを使用すると、ユーザーが既存の連絡先にデータを追加できます。
注:このインテントのエクストラで名前の値を送信する必要はありません。ユーザーは必ず既存の名前を選ぶか新しい名前を追加するからです。そのうえ、アプリが名前を送信し、ユーザーが編集することを選んだ場合、連絡先アプリは送信した名前を表示し、以前の値を上書きします。ユーザーがこのことに気付かず、編集内容を保存すると、以前の値が失われます。 |
端末の連絡先アプリは、インテントを使用して未加工連絡先や任意のデータを削除することを許可しません。そのため、未加工連絡先を削除するには ContentResolver.delete()
または ContentProviderOperation.newDelete()
を使用してください。
次のスニペットは、新しい未加工連絡先とデータをバッチで挿入するインテントをコンストラクトして送信する方法を示しています。
Kotlin
// Gets values from the UI val name = contactNameEditText.text.toString() val phone = contactPhoneEditText.text.toString() val email = contactEmailEditText.text.toString() val company = companyName.text.toString() val jobtitle = jobTitle.text.toString() /* * Demonstrates adding data rows as an array list associated with the DATA key */ // Defines an array list to contain the ContentValues objects for each row val contactData = arrayListOf<ContentValues>() /* * Defines the raw contact row */ // Sets up the row as a ContentValues object val rawContactRow = ContentValues().apply { // Adds the account type and name to the row put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.type) put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.name) } // Adds the row to the array contactData.add(rawContactRow) /* * Sets up the phone number data row */ // Sets up the row as a ContentValues object val phoneRow = ContentValues().apply { // Specifies the MIME type for this data row (all data rows must be marked by their type) put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) // Adds the phone number and its type to the row put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) } // Adds the row to the array contactData.add(phoneRow) /* * Sets up the email data row */ // Sets up the row as a ContentValues object val emailRow = ContentValues().apply { // Specifies the MIME type for this data row (all data rows must be marked by their type) put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) // Adds the email address and its type to the row put(ContactsContract.CommonDataKinds.Email.ADDRESS, email) } // Adds the row to the array contactData.add(emailRow) // Creates a new intent for sending to the device's contacts application val insertIntent = Intent(ContactsContract.Intents.Insert.ACTION).apply { // Sets the MIME type to the one expected by the insertion activity type = ContactsContract.RawContacts.CONTENT_TYPE // Sets the new contact name putExtra(ContactsContract.Intents.Insert.NAME, name) // Sets the new company and job title putExtra(ContactsContract.Intents.Insert.COMPANY, company) putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle) /* * Adds the array to the intent's extras. It must be a parcelable object in order to * travel between processes. The device's contacts app expects its key to be * Intents.Insert.DATA */ putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData) } // Send out the intent to start the device's contacts app in its add contact activity. startActivity(insertIntent)
Java
// Gets values from the UI String name = contactNameEditText.getText().toString(); String phone = contactPhoneEditText.getText().toString(); String email = contactEmailEditText.getText().toString(); String company = companyName.getText().toString(); String jobtitle = jobTitle.getText().toString(); // Creates a new intent for sending to the device's contacts application Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION); // Sets the MIME type to the one expected by the insertion activity insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE); // Sets the new contact name insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name); // Sets the new company and job title insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company); insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle); /* * Demonstrates adding data rows as an array list associated with the DATA key */ // Defines an array list to contain the ContentValues objects for each row ArrayList<ContentValues> contactData = new ArrayList<ContentValues>(); /* * Defines the raw contact row */ // Sets up the row as a ContentValues object ContentValues rawContactRow = new ContentValues(); // Adds the account type and name to the row rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType()); rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName()); // Adds the row to the array contactData.add(rawContactRow); /* * Sets up the phone number data row */ // Sets up the row as a ContentValues object ContentValues phoneRow = new ContentValues(); // Specifies the MIME type for this data row (all data rows must be marked by their type) phoneRow.put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE ); // Adds the phone number and its type to the row phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone); // Adds the row to the array contactData.add(phoneRow); /* * Sets up the email data row */ // Sets up the row as a ContentValues object ContentValues emailRow = new ContentValues(); // Specifies the MIME type for this data row (all data rows must be marked by their type) emailRow.put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE ); // Adds the email address and its type to the row emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email); // Adds the row to the array contactData.add(emailRow); /* * Adds the array to the intent's extras. It must be a parcelable object in order to * travel between processes. The device's contacts app expects its key to be * Intents.Insert.DATA */ insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData); // Send out the intent to start the device's contacts app in its add contact activity. startActivity(insertIntent);
データの整合性
連絡先リポジトリにはユーザーによる修正や更新が予想される重要で秘密性の高いデータが格納されるため、連絡先プロバイダにはデータの整合性に関して明確に定義されたルールがあります。連絡先データを変更する際にこれらのルールを守ることは、開発側の責任です。ここでは、重要なルールを示します。
-
ContactsContract.CommonDataKinds.StructuredName
行を、追加するContactsContract.RawContacts
行ごとに必ず追加してください。 -
ContactsContract.Data
テーブルにContactsContract.CommonDataKinds.StructuredName
行のないContactsContract.RawContacts
行は、集約中に問題を引き起こすことがあります。 -
新しい
ContactsContract.Data
行を、その親ContactsContract.RawContacts
行に必ずリンクしてください。 -
ContactsContract.RawContacts
にリンクされていないContactsContract.Data
行は端末の連絡先アプリで可視にならず、同期アダプタで問題になることがあります。 - 変更するのは、所有する未加工連絡先のデータに限ってください。
- 通常、連絡先プロバイダは複数の異なるアカウント タイプやオンライン サービスから取得したデータを管理しています。そのため、アプリが所有しているデータのみ変更または削除されるようにし、アプリが管理しているアカウント タイプとアカウント名のデータのみ挿入するようにすることが必要です。
-
権限、コンテンツ URI、URI パス、列名、MIME タイプ、
TYPE
値については、ContactsContract
とそのサブクラスに定義されている定数を必ず使用してください。 - そのような定数を使用することがエラーの予防に役立ちます。また、使われなくなった定数があれば、コンパイラから警告として通知されます。
カスタムデータ行
独自のカスタム MIME タイプを作成して使用すると、ContactsContract.Data
テーブルで独自データ行を挿入、編集、削除、取得できます。独自行で使用できるのは ContactsContract.DataColumns
に定義された列だけですが、独自タイプに固有の列名をデフォルトの列名にマッピングできます。端末の連絡先アプリで、独自行のデータは表示されますが、編集または削除はできず、ユーザーはデータの追加ができません。ユーザーがカスタムデータ行を変更できるようにするには、独自のアプリでエディタ アクティビティを提供する必要があります。
カスタムデータを表示するには、1 つの <ContactsAccountType>
要素と 1 つまたは複数の <ContactsDataKind>
子要素を含む contacts.xml
ファイルを提供します。詳しくは、<ContactsDataKind> element
のセクションで説明しています。
カスタム MIME タイプについて詳しくは、コンテンツ プロバイダの作成をご覧ください。
連絡先プロバイダの同期アダプタ
連絡先プロバイダは、特に端末とオンライン サービスとの間における連絡先データの同期を処理するために設計されています。これを使用することで、ユーザーは既存のデータを新しい端末にダウンロードしたり、既存のデータを新しいアカウントにアップロードしたりできています。また、同期によって、追加や変更のソースに関係なく、ユーザーは最新データを入手できます。同期の利点としては他にも、端末がネットワークに接続されていなくても連絡先データを使用できるようになることが挙げられます。
同期はさまざまな手法で実装できますが、Android システムでは次のタスクを自動化するプラグイン同期フレームワークを提供しています。
- ネットワークの可用性のチェック。
- ユーザーのプリファレンスに基づく同期のスケジュールと実施。
- 停止された同期の再開。
このフレームワークを使用するには、同期アダプタ プラグインを提供する必要があります。各同期アダプタはサービスと連絡先プロバイダに対して一意ですが、同じサービスに対して複数のアカウント名を処理できます。また、このフレームワークでは同じサービスとプロバイダに対して複数の同期アダプタが可能です。
同期アダプタのクラスとファイル
同期アダプタは、AbstractThreadedSyncAdapter
のサブクラスとして実装し、Android アプリの一部としてインストールします。システムは同期アダプタに関する知識を、アプリのマニフェストの要素からと、マニフェストが指す専用の XML ファイルから取得します。この XML ファイルは、オンライン サービスのアカウント タイプと連絡先プロバイダに絡む権限を定義しており、この組み合わせでアダプタを一意に識別します。同期アダプタは、ユーザーが同期アダプタのアカウント タイプに対してアカウントを追加し、同期アダプタの同期対象である連絡先プロバイダで同期を有効にすることで、アクティブになります。この時点で、システムはアダプタの管理を開始し、連絡先プロバイダとサーバーとの同期が必要になるとそれを呼び出します。
注:アカウント タイプを同期アダプタの識別の一環として使用することにより、システムは同じ組織から異なるサービスにアクセスする同期アダプタを検出してグループ化できます。たとえば、Google オンライン サービス用の同期アダプタでは、すべて同じアカウント タイプ com.google
です。ユーザーが Google アカウントを端末に追加すると、Google サービス用にインストールされているすべての同期アダプタがひとまとめにリストされ、リストされている各同期アダプタは端末上の異なる連絡先プロバイダと同期します。
データにアクセスするたび、ほとんどのサービスがユーザーに ID の確認を要求するため、Android システムでは、同期アダプタ フレームワークと類似した認証フレームワークを提供しており、往々にして同期アダプタ フレームワークと組み合わせて使用します。この認証フレームワークでは、AbstractAccountAuthenticator
のサブクラスであるプラグイン認証システムを使用します。認証システムは、次の手順でユーザーの身元を検証します。
- ユーザー名とパスワードか、それに準ずる情報(ユーザーの認証情報)を収集します。
- 収集した認証情報をサービスに送信します。
- サービスの返答を検証します。
サービスが認証情報を受け入れた場合、認証システムはその認証情報を後で使用するために格納します。AccountManager
はプラグイン認証フレームワークのおかげで、OAuth2 認証トークンなど、認証システムがサポートして公開することを選択している任意の認証トークンへのアクセスを提供できます。
認証は必須ではありませんが、たいていの連絡先サービスが使用しています。ただし、認証に Android 認証フレームワークを使用する必要はありません。
同期アダプタの実装
連絡先プロバイダ用の同期アダプタを実装するには、まず以下を備えた Android アプリを作成します。
-
システムからの同期アダプタとのバインド要求に対処する
Service
コンポーネント。 -
システムは、同期しようとするとき、サービスの
onBind()
メソッドを呼び出して、同期アダプタのIBinder
を取得します。これにより、システムはアダプタのメソッドに対するプロセス間呼び出しを実行できます。 -
AbstractThreadedSyncAdapter
の具象サブクラスとして実装された、実際の同期アダプタ。 -
このクラスが、サーバーからのデータのダウンロード、端末からのデータのアップロード、競合の解消といった作業を実施します。アダフタの主な作業は、メソッド
onPerformSync()
で実行されます。このクラスは、シングルトンとしてインスタンス化する必要があります。 -
Application
のサブクラス。 -
このクラスは同期アダプタ シングルトンのファクトリとして機能します。同期アダプタのインスタンス化には
onCreate()
メソッドを使用し、同期アダプタ シングルトンを同期アダプタのサービスのonBind()
メソッドに返すための静的な「getter」メソッドを提供します。 -
任意:システムからのユーザー認証要求に対処する
Service
コンポーネント。 -
AccountManager
は、このサービスを開始することによって認証プロセスを始めます。サービスのonCreate()
メソッドは、認証システム オブジェクトをインスタンス化します。システムは、アプリの同期アダプタのためにユーザー アカウントを認証しようとするとき、サービスのonBind()
メソッドを呼び出して、認証システムのIBinder
を取得します。これにより、システムは認証システムのメソッドに対するプロセス間呼び出しを実行できます。 -
任意:認証の要求を処理する
AbstractAccountAuthenticator
の具象サブクラス。 -
このクラスは、ユーザーの認証情報をサーバーに対して認証するために
AccountManager
が呼び出すメソッドを提供します。認証プロセスの詳細は、使用されているサーバー技術に応じて多岐にわたります。認証について詳しくは、使用するサーバー ソフトウェアのドキュメントをご覧ください。 - システムに対する同期アダプタと認証システムを定義する XML ファイル。
-
前述の同期アダプタと認証システム サービスのコンポーネントは、アプリのマニフェストの
<service>
要素に定義されます。これらの要素には、特定のデータをシステムに提供する次のような<meta-data>
子要素が含まれます。-
同期アダプタ サービス用の
<meta-data>
要素は、XML ファイルres/xml/syncadapter.xml
を指します。このファイルが、連絡先プロバイダと同期されるウェブサービスの URI と、そのウェブサービスで使用するアカウント タイプを指定します。 -
任意:認証システム用の
<meta-data>
要素は、XML ファイルres/xml/authenticator.xml
を指します。このファイルが、この認証システムがサポートするアカウント タイプと、認証プロセスの最中に表示される UI リソースを指定します。この要素に指定されるアカウント タイプは、同期アダプタ用に指定されるアカウント タイプと同じであることが必要です。
-
同期アダプタ サービス用の
ソーシャル ストリーム データ
android.provider.ContactsContract.StreamItems テーブルと android.provider.ContactsContract.StreamItemPhotos テーブルは、ソーシャル ネットワークからの受信データを管理します。独自ネットワークからのストリーム データをこれらのテーブルに追加する同期アダプタを作成したり、ストリーム データをこれらのテーブルから読み込んで独自アプリに表示したり、この両方を行ったりできます。このような機能があると、ソーシャル ネットワーキングのサービスやアプリを Android のソーシャル ネットワーキング機能に統合できます。
ソーシャル ストリーム テキスト
ストリーム アイテムは、必ず未加工連絡先に関連付けられます。android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID は、未加工連絡先の _ID
値にリンクされます。未加工連絡先のアカウント タイプとアカウント名は、ストリーム アイテム行にも格納されます。
ストリームからのデータを次の列に格納します。
- android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
- 必須。このストリーム アイテムに関連付けられている未加工連絡先の、ユーザーのアカウント タイプ。ストリーム アイテムを挿入する際には、この値を忘れずに設定してください。
- android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
- 必須。このストリーム アイテムに関連付けられている未加工連絡先の、ユーザーのアカウント名。ストリーム アイテムを挿入する際には、この値を忘れずに設定してください。
- 識別用列
-
必須。ストリーム アイテムを挿入する際には、次の識別用列を挿入する必要があります。
- android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID:このストリーム アイテムが関連付けられている連絡先の android.provider.BaseColumns#_ID 値。
- android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY:このストリーム アイテムが関連付けられている連絡先の android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY 値。
- android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID:このストリーム アイテムが関連付けられている未加工連絡先の android.provider.BaseColumns#_ID 値。
- android.provider.ContactsContract.StreamItemsColumns#COMMENTS
- 省略可能。ストリーム アイテムの冒頭に表示できる概要情報を格納します。
- android.provider.ContactsContract.StreamItemsColumns#TEXT
-
ストリーム アイテムのテキスト。アイテムのソースによって投稿されたコンテンツか、そのストリーム アイテムを生成したなんらかのアクションに関する説明です。この列には、
fromHtml()
でレンダリングできる任意の書式や埋め込みリソース画像を含めることができます。プロバイダは、長いコンテンツを切り捨てたり省いたりすることがありますが、タグは壊さないようにします。 - android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
- ストリーム アイテムが挿入またはアップデートされた時刻を含むテキスト。エポックからのミリ秒の形式です。ストリーム アイテムを挿入またはアップデートするアプリは、この列の管理を担当します。この列は、連絡先プロバイダによって自動的に管理されるわけではありません。
ストリーム アイテムの識別情報を表示するには、android.provider.ContactsContract.StreamItemsColumns#RES_ICON、android.provider.ContactsContract.StreamItemsColumns#RES_LABEL、android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE を使用してアプリ内のリソースにリンクします。
android.provider.ContactsContract.StreamItems テーブルには、同期アダプタ専用に列 android.provider.ContactsContract.StreamItemsColumns#SYNC1 ~ android.provider.ContactsContract.StreamItemsColumns#SYNC4 も含まれます。
ソーシャル ストリーム フォト
android.provider.ContactsContract.StreamItemPhotos テーブルには、ストリーム アイテムに関連付けられた写真が格納されます。このテーブルの android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID 列は、android.provider.ContactsContract.StreamItems テーブルの _ID
列の値にリンクされます。写真の参照は、テーブルの次の列に格納されます。
- android.provider.ContactsContract.StreamItemPhotos#PHOTO 列(BLOB)。
- 写真のバイナリ表現。格納や表示のためにプロバイダによってサイズが変更されます。この列は、写真を格納するために使用されていた連絡先プロバイダの従来のバージョンと下位互換性を保つために用意されています。ただし、現在のバージョンでは、写真の格納にこの列を使用しないでください。代わりに、android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID または android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI(どちらについても次の項目で説明します)を使用して、写真をファイルに保存します。現状では、この列には写真のサムネイルが格納されており、読み出し可能です。
- android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
-
未加工連絡先の写真の数値 ID。この値を定数
DisplayPhoto.CONTENT_URI
の末尾に追加して、1 つの写真ファイルを指すコンテンツ URI を取得し、openAssetFileDescriptor()
を呼び出して、その写真ファイルのハンドルを取得します。 - android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
-
この行によって表されている写真の写真ファイルを直接指すコンテンツ URI。この URI を使用して
openAssetFileDescriptor()
を呼び出して、写真ファイルのハンドルを取得します。
ソーシャル ストリーム テーブルの使用
これらのテーブルは、連絡先プロバイダの他の主なテーブルと同じように機能しますが、次のような例外があります。
- これらのテーブルには、追加のアクセス パーミッションが必要です。これらからの読み出しには、アプリにパーミッション android.Manifest.permission#READ_SOCIAL_STREAM が必要です。これらの変更には、アプリにパーミッション android.Manifest.permission#WRITE_SOCIAL_STREAM が必要です。
-
android.provider.ContactsContract.StreamItems テーブルについては、各未加工連絡先を格納できる行数に上限があります。上限に達すると、連絡先プロバイダは最も古い android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP を持つ行を自動的に削除して、新しいストリーム アイテムのための領域を作ります。この上限を取得するには、コンテンツ URI android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI にクエリを発行します。コンテンツ URI を除くすべての引数は、
null
のままでかまいません。このクエリは、列が android.provider.ContactsContract.StreamItems#MAX_ITEMS だけの 1 行を含む Cursor を返します。
クラス android.provider.ContactsContract.StreamItems.StreamItemPhotos は、1 つのストリーム アイテムの写真行を含む android.provider.ContactsContract.StreamItemPhotos のサブテーブルを定義します。
ソーシャル ストリーム操作
連絡先プロバイダによって端末の連絡先アプリと連携して管理されるソーシャル ストリーム データは、ソーシャル ネットワーキング システムと既存の連絡先を接続するためのとても便利な手段を用意しています。利用できる機能は次のとおりです。
- 同期アダプタを使用してソーシャル ネットワーキング サービスを連絡先プロバイダに同期することで、ユーザーの連絡先の最近のアクティビティを取得し、それを android.provider.ContactsContract.StreamItems テーブルや android.provider.ContactsContract.StreamItemPhotos テーブルに格納して後で使えるようにできます。
- ユーザーが連絡先を選択して表示したときには、定期的な同期とは別に同期アダプタを起動して追加データを取得できます。この機能により、同期アダプタは連絡先の高解像度の写真や直近のストリーム アイテムを取得できます。
- 端末の連絡先アプリと連絡先プロバイダに関する通知を登録すると、連絡先が表示された場合にインテントを受信し、その時点で連絡先のステータスをサービスからアップデートできます。このアプローチは、同期アダプタを使用してフル同期を実施するより、処理が速く、帯域幅が節約されます。
- ユーザーが端末の連絡先アプリで連絡先を見ている間に、その連絡先をソーシャル ネットワーキング サービスに追加できます。この機能は「連絡先の招待」機能を使用して実現できます。連絡先の招待は、既存の連絡先をネットワークに追加するアクティビティと、端末の連絡先アプリと連絡先プロバイダにアプリの詳細を提供する XML ファイルを組み合わせて実現できます。
連絡先プロバイダを使用したストリーム アイテムの定期的な同期は、他の同期と同じです。同期について詳しくは、連絡先プロバイダの同期アダプタをご覧ください。通知の登録と連絡先の招待については、次の 2 つのセクションで説明します。
ソーシャル ネットワーキング表示を処理するための登録
同期アダプタを登録し、同期アダプタによって管理されている連絡先をユーザーが表示したときに通知されるようにする方法は、次のとおりです。
-
contacts.xml
という名前のファイルをプロジェクトのres/xml/
ディレクトリに作成します。このファイルが既に存在する場合は、この手順を省略できます。 -
このファイルに要素
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">
を追加します。この要素が既に存在する場合は、この手順を省略できます。 -
ユーザーが端末の連絡先アプリで連絡先の詳細ページを開いたときに通知されるサービスを登録するには、属性
viewContactNotifyService="serviceclass"
を要素に追加します。serviceclass
は、端末の連絡先アプリからインテントを受け取ることになるサービスの完全修飾クラス名です。通知側サービスのために、IntentService
を機能拡張したクラスを使用して、そのサービスがインテントを受け取れるようにします。受け取るインテントに含まれるデータには、ユーザーがクリックした未加工連絡先のコンテンツ URI が格納されます。通知側サービスから、同期アダプタをバインドして呼び出し、未加工連絡先のデータをアップデートできます。
ユーザーがストリーム アイテムかストリーム フォトかその両方をクリックしたときに呼び出されるアクティビティを登録する方法は次のとおりです。
-
contacts.xml
という名前のファイルをプロジェクトのres/xml/
ディレクトリに作成します。このファイルが既に存在する場合は、この手順を省略できます。 -
このファイルに要素
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">
を追加します。この要素が既に存在する場合は、この手順を省略できます。 -
アクティビティのどれかを登録して、ユーザーが端末の連絡先アプリでストリーム アイテムをタップしたことに対処するには、属性
viewStreamItemActivity="activityclass"
を要素に追加します。activityclass
は、端末の連絡先アプリからインテントを受け取ることになるアクティビティの完全修飾クラス名です。 -
アクティビティのどれかを登録して、ユーザーが端末の連絡先アプリでストリーム フォトをタップしたことに対処するには、属性
viewStreamItemPhotoActivity="activityclass"
を要素に追加します。activityclass
は、端末の連絡先アプリからインテントを受け取ることになるアクティビティの完全修飾クラス名です。
<ContactsAccountType>
要素については、<ContactsAccountType> 要素で詳しく説明しています。
受け取るインテントには、ユーザーがクリックしたアイテムまたは写真のコンテンツ URI が格納されます。テキスト アイテムと写真とで異なるアクティビティを使用するには、同じファイルで両方の属性を使用します。
ソーシャル ネットワーキング サービスの操作
ユーザーは、連絡先をソーシャル ネットワーキング サービスに招待するのに、端末の連絡先アプリを離れる必要はありません。その代わりとして、連絡先をアクティビティのどれかに招待するインテントを端末の連絡先アプリケーションに送信させることができます。これをセットアップする方法は次のとおりです。
-
contacts.xml
という名前のファイルをプロジェクトのres/xml/
ディレクトリに作成します。このファイルが既に存在する場合は、この手順を省略できます。 -
このファイルに要素
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">
を追加します。この要素が既に存在する場合は、この手順を省略できます。 -
次の属性を追加します。
inviteContactActivity="activityclass"
-
inviteContactActionLabel="@string/invite_action_label"
activityclass
値は、インテントを受信するアクティビティの完全修飾クラス名です。invite_action_label
値は、端末の連絡先アプリの [Add Connection] メニューに表示されるテキストです。
注: ContactsSource
は廃止されたタグ名で、ContactsAccountType
に置き換わっています。
contacts.xml リファレンス
ファイル contacts.xml
には、同期アダプタやアプリと連絡先アプリや連絡先プロバイダとのやり取りを管理する XML 要素が含まれています。これらの要素について、以降のセクションで説明します。
<ContactsAccountType> 要素
<ContactsAccountType>
要素は、アプリと連絡先アプリとのやり取りを管理します。構文は次のとおりです。
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android" inviteContactActivity="activity_name" inviteContactActionLabel="invite_command_text" viewContactNotifyService="view_notify_service" viewGroupActivity="group_view_activity" viewGroupActionLabel="group_action_text" viewStreamItemActivity="viewstream_activity_name" viewStreamItemPhotoActivity="viewphotostream_activity_name">
含まれているファイル:
res/xml/contacts.xml
含めることのできる要素:
<ContactsDataKind>
説明:
ユーザーが連絡先の 1 人をソーシャル ネットワーキングに招待できるようにしたり、ソーシャル ネットワーキング ストリームのどれかがアップデートされたらユーザーに通知したりするための、Android コンポーネントや UI ラベルを宣言します。
属性プレフィックス android:
は、<ContactsAccountType>
の属性に必須ではないことに注目してください。
属性:
inviteContactActivity
- ユーザーが端末の連絡先アプリで [Add Connection] を選択したときに起動する、アプリ内のアクティビティの完全修飾クラス名。
inviteContactActionLabel
-
[Add Connection] メニューで、
inviteContactActivity
に指定されたアクティビティ用に表示されるテキスト。たとえば、文字列「Follow in my network」を指定できます。このラベルには文字列リソース ID を使用できます。 viewContactNotifyService
- ユーザーが連絡先を表示したときに通知を受け取ることになる、独自アプリ内のサービスの完全修飾クラス名。この通知は端末の連絡先アプリによって送信されます。これを使用することで、アプリはデータ処理の多い操作を必要になるまで延期できます。たとえば、アプリはこの通知への対応として、連絡先の高解像度写真と直近のソーシャル ストリーム アイテムを読み込んで表示できます。この機能について詳しくは、ソーシャル ストリーム操作で説明しています。
viewGroupActivity
- グループ情報を表示できる、独自アプリ内のアクティビティの完全修飾クラス名。ユーザーが端末の連絡先アプリでグループラベルをタップすると、このアクティビティの UI が表示されます。
viewGroupActionLabel
-
ユーザーがアプリでグループを表示できるようにする UI コントロールに対し、連絡先アプリが表示するラベル。
この属性には、文字列リソース ID を使用できます。
viewStreamItemActivity
- ユーザーが未加工連絡先のストリーム アイテムをタップしたときに端末の連絡先アプリが起動する、独自アプリ内のアクティビティの完全修飾クラス名。
viewStreamItemPhotoActivity
- ユーザーが未加工連絡先のストリーム アイテムで写真をタップしたときに端末の連絡先アプリが起動する、独自アプリ内のアクティビティの完全修飾クラス名。
<ContactsDataKind> 要素
<ContactsDataKind>
要素は、連絡先アプリにおける独自アプリ UI のカスタムデータ行の表示を管理します。構文は次のとおりです。
<ContactsDataKind android:mimeType="MIMEtype" android:icon="icon_resources" android:summaryColumn="column_name" android:detailColumn="column_name">
含まれているファイル:
<ContactsAccountType>
説明:
この要素は、カスタムデータ行のコンテンツを未加工連絡先の詳細の一部として連絡先アプリに表示させるために使用します。<ContactsAccountType>
の各 <ContactsDataKind>
子要素は、同期アダプタが ContactsContract.Data
テーブルに追加するカスタムデータ行のタイプを表します。使用するカスタム MIME タイプごとに <ContactsDataKind>
要素を 1 つ追加します。データを表示しないカスタムデータ行がある場合は、この要素を追加する必要はありません。
属性:
android:mimeType
-
ContactsContract.Data
テーブルに含まれるカスタムデータ行のどれかに定義したカスタム MIME タイプ。たとえば、値vnd.android.cursor.item/vnd.example.locationstatus
は、連絡先の最新の場所情報を記録するデータ行のためのカスタム MIME タイプということが考えられます。 android:icon
- 連絡先アプリがデータの隣に表示する Android ドローアブル リソース。そのデータが独自サービスからのものであることを示すのに使用します。
android:summaryColumn
- データ行から取得された 2 つの値で最初の列名。この値は、このデータ行のエントリの先頭行として表示されます。この先頭行は、データの要約として使われることを狙ったものですが、省略可能です。android:detailColumn もご覧ください。
android:detailColumn
-
データ行から取得された 2 つの値で 2 番目の列名。この値は、このデータ行のエントリの 2 行目として表示されます。
android:summaryColumn
もご覧ください。
連絡先プロバイダの追加機能
ここまでのセクションで説明した主な機能の他に、連絡先プロバイダには連絡先データの作業用として次のような便利な機能が用意されています。
- 連絡先グループ
- 写真機能
連絡先グループ
連絡先プロバイダでは、オプションで、関連連絡先のコレクションをグループデータでラベル付けできます。あるユーザー アカウントに関連付けられているサーバーがグループを管理する場合、そのアカウントのアカウント タイプ用の同期アダプタが、連絡先プロバイダとサーバーとの間でグループデータを転送する必要があります。ユーザーが新しい連絡先をサーバーに追加し、その連絡先を新しいグループに入れた場合、同期アダプタはその新しいグループを ContactsContract.Groups
テーブルに追加する必要があります。ある未加工連絡先が属するグループ(複数の場合あり)は、ContactsContract.Data
テーブルに、ContactsContract.CommonDataKinds.GroupMembership
MIME タイプを使用して格納されます。
サーバーからの未加工連絡先データを連絡先プロバイダに追加する同期アダプタを設計している場合、グループを使用しないのであれば、連絡先プロバイダに対してデータが可視であることを通知する必要があります。ユーザーがアカウントを端末に追加したときに実行されるコード内で、連絡先プロバイダがそのアカウントに対して追加する ContactsContract.Settings
行をアップデートします。この行で、Settings.UNGROUPED_VISIBLE
列の値を 1 に設定します。こうすると、連絡先プロバイダは、グループを使用していない場合でも、独自の連絡先データを常に表示します。
連絡先の写真
ContactsContract.Data
テーブルは、写真を MIME タイプ Photo.CONTENT_ITEM_TYPE
の行として格納します。この行の CONTACT_ID
列は、それが属する未加工連絡先の _ID
列にリンクされます。クラス ContactsContract.Contacts.Photo
は、連絡先のプライマリ フォトの写真情報を格納する ContactsContract.Contacts
のサブテーブルを定義します。ここでプライマリ フォトとは、連絡先のプライマリ未加工連絡先のプライマリ フォトのことを示します。同様に、クラス ContactsContract.RawContacts.DisplayPhoto
は、未加工連絡先のプライマリ フォトに関する情報を含む ContactsContract.RawContacts
のサブテーブルを定義します。
ContactsContract.Contacts.Photo
と ContactsContract.RawContacts.DisplayPhoto
の参照ドキュメントには、写真情報の取得例が含まれています。未加工連絡先のプライマリ サムネイルを取得するための便利なクラスはありませんが、ContactsContract.Data
テーブルにクエリを送信して、未加工連絡先の _ID
、Photo.CONTENT_ITEM_TYPE
、IS_PRIMARY
列を選んでその未加工連絡先のプライマリ フォト行を探すことができます。
人のソーシャル ストリーム データにも写真が含まれていることがあります。その場合は android.provider.ContactsContract.StreamItemPhotos テーブルに格納されています。このテーブルについては、ソーシャル ストリーム フォトで詳しく説明しています。