Google Playの写真と動画の権限に関するポリシー
Androidアプリ開発者の皆様、Google Playの写真と動画の権限に関するポリシーの詳細の対応は進んでいますか?
写真や動画を頻繁に利用しないアプリでは、 READ_MEDIA_IMAGES
と READ_MEDIA_VIDEO
をアプリから削除する必要があります。
写真や動画のファイルを一度だけ使用する、または頻繁には使用しないとはどういう意味ですか?
写真や動画のファイルを一度だけ使用する、または頻繁には使用しないとは、たとえば、プロフィール写真のアップロード、プレイリスト用の画像のアップロード、銀行取引を目的とした小切手の写真のアップロードなどが該当します。
「頻繁には使用しない」とは、アプリのコア機能として写真や動画のユースケースがないということです。アプリのユースケースが写真や動画のファイルを一度だけ使用するものの場合、または頻繁には使用しないものの場合は、READ_MEDIA_IMAGES 権限や READ_MEDIA_VIDEO 権限を使用できません。代わりに、ユーザーのプライバシーを保護するため、システムの選択ツールを利用することを推奨します。
頻繁には使用しない、または一度だけ使用するユースケースには、ソーシャル、コミュニケーション、写真 / 動画エディタのカテゴリに属するアプリや、幅広いアクセスへのニーズを実証できるアプリ(例: 写真エディタ、ユーザー作成コンテンツ プラットフォーム、画像検索機能、QR コードスキャナ)などがあります。
READ_MEDIA_IMAGES 権限や READ_MEDIA_VIDEO 権限にアクセスできるのは、どのような場合ですか?
コア機能として写真や動画への幅広いアクセスが必要となるアプリでは、上記の権限を使用できます。こうした権限を使用できるアプリとしては、写真や動画を管理するアプリやギャラリー アプリが一般的です。
このポリシーへの対応を絶賛進行中なので、備忘録的に書き残そうと思います。
写真選択ツール
READ_MEDIA_IMAGES
権限や READ_MEDIA_VIDEO
権限を削除しつつ、端末のストレージの写真や動画にアクセスするにはどうしたらいいの?と思うかもしれませんが、もちろん代替案は用意されています。
ポリシーの詳細にも書かれていますが、Androidでは写真選択ツールを使うことができます。
これを使うことで、アプリはピッカーで選択した写真・動画のみにアクセスが許可されるようになります。
早速使ってみましょう。
1. ライブラリの追加・更新
写真選択ツールを使うにはandroidx.activity
のバージョン1.7.0以上が必要です。まずは追加・更新しましょう。
dependencies {
implementation("androidx.activity:activity-ktx:1.7.0")
}
2. ActivityResultLauncherを定義
写真選択ツールを使うためには、ActivityやFragmentでActivityResultLauncherを定義します。具体的にはregisterForActivityResult
を使います。
registerForActivityResult
に引数には、用途によって異なるコントラクトを与えます。1つの写真・動画を取得する場合はPickVisualMedia()
を、複数の写真・動画を取得する場合はPickMultipleVisualMedia()
を引数にします。
Activity の状態が STARTED となる前に定義しないとクラッシュするので注意!
// 1つの写真・動画を取得する場合
private val pickMedia = registerForActivityResult(PickVisualMedia()) { uri ->
if (uri != null) {
// 写真や動画を受け取った後の処理を記述
}
}
// 複数の写真・動画を取得する場合
private val pickMedia = registerForActivityResult(PickMultipleVisualMedia()) { uris ->
if (uris.isNotEmpty()) {
// 写真や動画を受け取った後の処理を記述
}
}
// 選択可能な写真・動画の数を制限することもできる
// 例えば5個に制限したい場合はPickMultipleVisualMediaの引数に5を与えれば良い
private val pickMedia = registerForActivityResult(PickMultipleVisualMedia(5)) { uris ->
if (uris.isNotEmpty() {
// 写真や動画を受け取った後の処理を記述
}
}
3. 写真選択ツールを起動
写真や動画を選択させたい場所でLauncherを起動させましょう。launch
の引数にはPickVisualMediaRequest
を与えますが、取得したいコンテンツの種別(写真 or 動画 or 両方)に応じて、PickVisualMediaRequest
の引数を指定します。具体的には
- 写真のみ -> PickVisualMedia.ImageOnly
- 動画のみ -> PickVisualMedia.VideoOnly
- 写真と動画の両方 -> PickVisualMedia.ImageAndVideo
のように使い分けます。
// 写真のみ
pickMedia.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly))
// 動画のみ
pickMedia.launch(PickVisualMediaRequest(PickVisualMedia.VideoOnly))
// 写真と動画の両方
pickMedia.launch(PickVisualMediaRequest(PickVisualMedia.ImageAndVideo))
これで写真選択ツールが起動して写真や動画を選択することができます。この方法だとREAD_MEDIA_IMAGES
と READ_MEDIA_VIDEO
の権限が必要ないのでこれらの権限を削除できます。やったね!
置き換え完了...... ?
以上の方法で簡単に置き換えられる......と良かったんですが、そう簡単にはいかないこともあります。
写真選択ツールで写真や動画を選択した後に返ってくる値はUriなので、既存の実装が写真や動画の選択後の処理をUriで進めているのであればそのまま扱うことができますが、pathを渡して実行しているような場合はUriからpathを取得する必要があります。
getPath()
などでpathを取得しようとすると、取得したpathでは後続の処理が適切に実行されないこともあると思います。
private val pickerMedia =
registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
uri?.let {
Log.d("testComment", "path = ${uri.path}")
}
}
// こんな感じで返ってくる
path = /picker/0/com.google.android.apps.photos.cloudpicker/media/c38e2756-bf62-4f99-8e80-71f706728be7-1_all_1333
既存の処理を書き換えてもいいですが、結構手間になると思います。
そんなときはcontentResolver
を使ってMediaStore
のDATA
を参照しましょう。
private val pickerMedia = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
uri?.let {
val path = getPathFromUri(uri)
Log.d("testComment", "uri = ${path}")
}
}
private fun getPathFromUri(uri: Uri): String {
val projection = arrayOf(MediaStore.MediaColumns.DATA)
val cursor = contentResolver.query(uri, projection, null, null, null)
var path = ""
cursor?.apply {
if (moveToFirst()) {
path = getString(getColumnIndexOrThrow(MediaStore.MediaColumns.DATA))
}
}
cursor?.close()
return path
}
// しっかり拡張子もついてくる
path = /sdcard/.transforms/synthetic/picker/0/com.google.android.apps.photos.cloudpicker/media/c38e2756-bf62-4f99-8e80-71f706728be7-1_all_1333.jpg
動画の長さ(時間)が欲しい場合は、MediaStore.MediaColumns.DURATION
を使うと取得することができます。
private val pickerMedia = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
uri?.let {
val duration = getMovieDuration(uri)
Log.d("testComment", "duration = ${getMovieDuration(uri)}")
}
}
private fun getMovieDuration(uri: Uri): Long {
val projection = arrayOf(MediaStore.MediaColumns.DURATION)
val cursor = contentResolver.query(uri, projection, null, null, null)
var duration = 0L
cursor?.apply {
if (moveToFirst()) {
duration = getLong(getColumnIndexOrThrow(MediaStore.MediaColumns.DURATION))
}
}
cursor?.close()
return duration
}
// 動画の長さがミリ秒で返ってくる
duration = 4592
その他DISPLAY_NAME
などの情報を取ってくることができるので、必要に応じて指定すれば、既存の処理を大きく変えずともポリシー対応ができると思います。