概要
他のアプリが保存したメディアファイルを、アプリから削除しようとした際にAndroid 9では削除できましたが、Android 10では削除できませんでした。
Android 10(TargetSDK 29)以降では、アプリが保存した画像等のメディアファイル以外は直接削除できないようになっているようなのでその対応についてまとめてみました。
https://developer.android.com/training/data-storage/files/external-scoped?hl=ja
Android 10でアプリからストレージ内の画像を削除する
Android 10でMediaStoreの画像や動画をcontentResolver.delete()で削除しようとするとRecoverableSecurityExceptionが発生する
fun delete() {
contentResolver.delete(fileUri, null, null)
}
実行するとRecoverableSecurityExceptionが発生する。
android.app.RecoverableSecurityException:
sampleApp has no access to content://media/external_primary/images/media/hogehoge
RecoverableSecurityExceptionへの対応
調べてみると、「RecoverableSecurityException」への対応としては、
初回削除時に発生したRecoverableSecurityExceptionをキャッチして、
ユーザーへアプリがメディアファイルへアクセスしても良いか許可を求める必要があるらしい。
private lateinit var activityResultLauncher: ActivityResultLauncher<IntentSenderRequest>
~~
~~
private fun delete() {
try {
contentResolver.delete(fileUri, null, null)
} catch (e: SecurityException) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val recoverableSecurityException =
e as? RecoverableSecurityException
?: throw e
// RecoverableSecurityExceptionからintendSenderを取得
// intentSenderを利用して、ポップアップを出してファイルアクセス許可をユーザに求める
activityResultLauncher.launch(
IntentSenderRequest.Builder(recoverableSecurityException.userAction.actionIntent.intentSender).build()
} else {
throw e
}
}
}
~~
~~
// ユーザーが許可した場合、再度削除処理を行う
activityResultLauncher =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
delete()
}
}
・削除時にRecoverableSecurityExceptionをキャッチ
・RecoverableSecurityExceptionからintentSenderを取得
・取得したintentSenderを実行して、アプリがメディアファイルにアクセスしてもいいか確認する
・ユーザーが許可した場合、アプリに削除権限が与えられるので再度削除処理を行う
上記の流れで削除を実施して、androidのフォトアプリ内を確認すると対象のメディアファイルが消えていた。
しかしながら、androidのストレージ内を確認すると何故かファイルの実体が残っている…。
MediaStoreからは削除できたが、メディアファイルの実体は削除できていなかった。
requestLegacyExternalStorage="true" を使う
ファイルの実体が残り続けてしまう原因を調べてみると、バグなのか仕様なのか分からないが削除したはずのメディアファイルの実体が残ってしまう現象が起きるようだ。
エミュレータでは、上記のコードをテストしたときMediaStoreからアイテムが削除されることを確認しました。 しかし、adb shellで、そのファイルが保存された実際のパスに入ると、ファイルは削除されずに残っています。 この部分がバグなのか、政策による動作かは確かに分かりません。 自分が登録したメディアファイルは、実際のファイルまで削除されていることから見て、実際のファイルも削除されるのが正しい動作のようです。
https://codechacha.com/ja/android-mediastore-remove-media-files/
対応としては、Android 10では「requestLegacyExternalStorage="true"」を使って、対象範囲別ストレージを使わずに、Android 9以前のように削除する方法を使うようです。
Android 10(API レベル 29)をターゲットとしているアプリの場合は、対象範囲別ストレージをオプトアウトし、Android 9 以前の方法を使用してこの操作を行います。
https://developer.android.com/training/data-storage/use-cases?hl=ja
マニフェストに「requestLegacyExternalStorage="true"」を記述
<application
android:requestLegacyExternalStorage="true">
Android 11での対応
試してみてはいませんが、
Android 11では、MediaStoreにメソッドが追加されるようなのでそれを使ってあげれば良さそう?
createTrashRequest()
ユーザーが指定したメディア ファイルをデバイスのゴミ箱に入れるためのリクエスト。ゴミ箱内のアイテムは、システムが規定する期間後に完全に削除されます。
注: デバイスの OEM にプリインストールされるギャラリー アプリの場合、ダイアログを表示せずにゴミ箱にファイルを移動できます。そのためには、IS_TRASHED を 1 に直接設定します。
createDeleteRequest()
前もってゴミ箱に入れずに、ユーザーが指定したメディア ファイルを完全に削除するためのリクエスト。
これらのメソッドのいずれかを呼び出すと、システムは PendingIntent オブジェクトを作成します。アプリがこのインテントを呼び出すと、指定のメディア ファイルをアプリが更新または削除する同意を求めるダイアログがユーザーに表示されます。
https://developer.android.com/preview/privacy/storage?hl=ja#media-batch-operations