プライバシーとユーザー管理は、Android エクスペリエンスの中心にあります。写真選択ツールによってメディア共有が安全かつ簡単に実装できるようになったのと同様に、連絡先の選択でも、同じレベルのプライバシー、シンプルさ、優れたユーザー エクスペリエンスを実現します。
連絡先のプライバシーに関する新しい基準
これまで、特定のユーザーの連絡先へのアクセスを必要とするアプリは、広範な READ_CONTACTS 権限に依存していました。このアプローチは機能的ではあるものの、アプリに必要以上のデータが提供されることがよくありました。Android 17 で導入された新しい Android 連絡先選択ツールは、連絡先の選択に標準化された安全な検索可能なインターフェースを提供することで、この状況を変えます。
この機能により、ユーザーは選択した特定の連絡先へのアクセスのみをアプリに許可できるようになります。これは、データの透明性と権限フットプリントの最小化に対する Android の取り組みに沿ったものです。
仕組み
デベロッパーは、Intent.ACTION_PICK_CONTACTS インテントを使用して連絡先選択ツールを統合できます。この更新された API には、次のような強力な機能が用意されています。
- きめ細かいデータ リクエスト: アプリは、連絡先レコード全体を受け取るのではなく、電話番号やメールアドレスなど、必要なフィールドを正確に指定できます。
- 複数選択のサポート: 選択ツールは、連絡先の単一選択と複数選択の両方をサポートしているため、デベロッパーはグループ招待などの機能をより柔軟に実装できます。
- 選択数の制限: デベロッパーは、ユーザーが一度に選択できる連絡先の数にカスタムの上限を設定できます。
- 一時アクセス: 選択すると、リクエストされたデータへの一時的な読み取りアクセスを提供するセッション URI が返されます。これにより、アクセスが必要以上に長く維持されることはありません。
- 他のプロファイルへのアクセス: この新しいインテントを使用すると、ユーザーは仕事用プロファイル、クローン プロファイル、プライベート スペースなどの他のユーザー プロファイルからコンテンツを選択できます。
- パフォーマンスの最適化: 連絡先選択ツールは、結果の一括クエリを可能にする単一の Uri を返します。これにより、
ACTION_PICKで必要とされるように、個々の連絡先の Uri を個別にクエリする必要がなくなります。この効率性により、単一のBinderトランザクションを利用することで、システム オーバーヘッドがさらに削減されます。
下位互換性と実装
Android 17 以降を搭載したデバイスでは、連絡先データタイプを指定する以前の ACTION_PICK インテントが、より安全な新しいインターフェースに自動的にアップグレードされます。ただし、複数選択などの高度な機能を最大限に活用するには、実装コードを更新し、ContentResolver を使用して返されたセッション URI をクエリすることをおすすめします。
連絡先選択ツールを統合する連絡先選択ツールを統合するには、ACTION_PICK_CONTACTS インテントを使用します。以下は、選択ツールを起動して、メールアドレスや電話番号などの特定のデータ フィールドをリクエストする方法を示すコード例です。
// State to hold the list of selected contacts var contacts by remember { mutableStateOf<List>(emptyList()) } // Launcher for the Contact Picker intent val pickContact = rememberLauncherForActivityResult(StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) { val resultUri = it.data?.data ?: return@rememberLauncherForActivityResult // Process the result URI in a background thread coroutine.launch { contacts = processContactPickerResultUri(resultUri, context) } } } // Define the specific contact data fields you need val requestedFields = arrayListOf( Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE, ) // Set up the intent for the Contact Picker val pickContactIntent = Intent(ACTION_PICK_CONTACTS).apply { putExtra(EXTRA_PICK_CONTACTS_SELECTION_LIMIT, 5) putStringArrayListExtra( EXTRA_PICK_CONTACTS_REQUESTED_DATA_FIELDS, requestedFields ) putExtra(EXTRA_PICK_CONTACTS_MATCH_ALL_DATA_FIELDS, false) } // Launch the picker pickContact.launch(pickContactIntent)
ユーザーが選択を行うと、アプリは返されたセッション URI をクエリして、リクエストされた連絡先情報を抽出することで、結果を処理します。
// Data class representing a parsed Contact with selected details data class Contact(val id: String, val name: String, val email: String?, val phone: String?) // Helper function to query the content resolver with the URI returned by the Contact Picker. // Parses the cursor to extract contact details such as name, email, and phone number private suspend fun processContactPickerResultUri( sessionUri: Uri, context: Context ): List<Contact> = withContext(Dispatchers.IO) { // Define the columns we want to retrieve from the ContactPicker ContentProvider val projection = arrayOf( ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, ContactsContract.Data.MIMETYPE, // Type of data (e.g., email or phone) ContactsContract.Data.DATA1, // The actual data (Phone number / Email string) ) val results = mutableListOf<Contact>() // Note: The Contact Picker Session Uri doesn't support custom selection & selectionArgs. context.contentResolver.query(sessionUri, projection, null, null, null)?.use { cursor -> // Get the column indices for our requested projection val contactIdIdx = cursor.getColumnIndex(ContactsContract.Contacts._ID) val mimeTypeIdx = cursor.getColumnIndex(ContactsContract.Data.MIMETYPE) val nameIdx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY) val data1Idx = cursor.getColumnIndex(ContactsContract.Data.DATA1) while (cursor.moveToNext()) { val contactId = cursor.getString(contactIdIdx) val mimeType = cursor.getString(mimeTypeIdx) val name = cursor.getString(nameIdx) ?: "" val data1 = cursor.getString(data1Idx) ?: "" // Determine if the current row represents an email or a phone number val email = if (mimeType == Email.CONTENT_ITEM_TYPE) data1 else null val phone = if (mimeType == Phone.CONTENT_ITEM_TYPE) data1 else null // Add the parsed contact to our results list results.add(Contact(contactId, name, email, phone)) } } return@withContext results }
ドキュメントの全文はこちらでご確認ください。
デベロッパー向けのベスト プラクティス
優れたユーザー エクスペリエンスを提供し、高いセキュリティ基準を維持するために、次のことをおすすめします。
- データの最小化: アプリに必要な特定のデータ フィールド(メールアドレスなど)のみをリクエストします。
- 即時永続化: セッション URI アクセスは一時的なものであるため、選択したデータをすぐに永続化します。
-
サービスに関する最新情報次期 Android リリースをターゲットとするアプリでは、WRITE_CONTACTS 権限を使用して連絡先に直接書き込むことができなくなります。
Roxanna Aliabadi Walker • 所要時間: 6 分 -
サービスに関する最新情報埋め込み型写真選択ツール: アプリで写真や動画を非公開でリクエストするための、よりシームレスな方法。
Roxanna Aliabadi Walker, Yacine Rezgui • 所要時間: 8 分 -
サービスに関する最新情報Unity は世界中のモバイルタイトルの半数以上を支えており、第一印象が重要です。ゲーム デベロッパーにとって、スムーズで信頼性の高いプレーヤー オンボーディング エクスペリエンスは、1 日目の維持率と長期的なエンゲージメントを促進するために不可欠です。
Michelle Martin • 所要時間: 1 分
Android 開発に関する最新の分析情報を毎週メールでお届けします。