Android13の写真選択ツールを導入しようとしてやめた話

Android13で写真選択ツールが登場しました。

https://developer.android.com/training/data-storage/shared/photopicker?hl=ja

現在開発中のアプリにて、写真選択ツール導入を検討してみた過程と、結果的に見送ることになった理由を書いていきます。

## Intentと写真選択ツールと、その体験
写真選択の実装方法する際、従来はIntent.ACTION_PICKなどを投げることで選択画面を開く実装をすることができました。
まずは実装方法によって、ユーザーの体験がどのように変わるか見ていきます。

### 写真選択ツールで実装した場合
写真選択ツールはAndroid13もしくは、Android11以降のR Extensions Version 2のSDK extensionsが入っている端末に対応しています。（SDK extensionsって何？は[この記事](https://qiita.com/takahirom/items/7613a28cabdaae3e3f81)が分かりやすいです）
そのため対応している場合、対応していない場合で体験が異なります。

■ 対応している場合
画像1枚選択の場合、ハーフスクリーン モードで開かれます。
|  写真  |  アルバム  | アルバム/カメラ |
| :----: | :----: | :----: |
|![Screenshot_20230324_002524.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130234/71ecaa55-8482-653a-7767-23c7dc694240.png)|![Screenshot_20230324_002643.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130234/fe232d6a-2bd7-e099-4633-001915b0e2e0.png)|![Screenshot_20230324_002654.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130234/a7f95ba3-ae48-1ce2-ff8c-c23eff952ae6.png)|

これは内部的には次のItentを投げています（[ドキュメント](https://developer.android.com/reference/android/provider/MediaStore#ACTION_PICK_IMAGES)）
```kotlin
Intent(MediaStore.ACTION_PICK_IMAGES).apply {
    type = "image/*"
}
```

■ 対応していない場合
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130234/3add37db-68cf-6cfd-8cd7-a573cf5dd37f.png width=30%>

内部的には次のItentを投げています。
```kotlin
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
    type = "image/*"
}
```

### Intent.ACTION_PICKで実装した場合
```kotlin
Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI).apply {
    type = "image/*"
}
```

|  写真を選択  |  写真を選択/写真  |
| :----: | :----: |
|![Screenshot_20230324_004922.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130234/15953b59-8b14-4ba8-fe71-2f49c9a431d1.png)|![Screenshot_20230324_005213.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130234/66bb85b8-86e6-5049-2145-dd71c193bc65.png)|

写真選択ツールのハーフスクリーンモードは、画面遷移なしで写真を選択することができて体験が良さそうな反面、対応していない端末ではIntent.ACTION_OPEN_DOCUMENTを投げるため、写真を選択する体験としてはあまり良くなさそうです。

そのため、写真選択ツールとIntent.ACTION_PICKを組み合わせてみることにしました。

## URIのアクセス権限と、絶対パス
写真選択ツールに非対応の場合は、Intent.ACTION_PICKを投げる実装は問題なく行うことができ、それぞれの実装で写真を選択して、URIを受け取り、アプリ上に画像を表示する。

というところまでは問題なく進むことができました。
しかし、画像のURIをDBに保持して、アプリを再起動したところ画像が表示されません。
どうやらアクセス権限がないのが問題のようです。

### MediaStore.ACTION_PICK_IMAGES
写真選択ツールに対応している場合はMediaStore.ACTION_PICK_IMAGESが投げられますが、これによって次のようなURIが取得できます。
```
content://media/picker/0/com.android.providers.media.photopicker/media/1000000020
```
このURIには一時的なアクセス権限が与えられているため、パーミッションなしでアクセスすることが可能です。
しかし、あくまでも一時的でありデバイスが再起動されるか、アプリが停止した際にはアクセスできなくなってしまいます。

アクセス権限持続は次のコードで行うことができますが、[ドキュメント](https://developer.android.com/training/data-storage/shared/photopicker?hl=ja#persist-media-file-access)には「延長」と記載されているため永続的にアクセスできるものではなさそうです。
```kotlin
val flag = Intent.FLAG_GRANT_READ_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flag)
```

URIでは永続的にアクセスすることが難しそうなため、画像の絶対パスを取得できるか試してみます。
次のコードによって、MediaStoreにあるDATA列（絶対パス）を参照します。
```kotlin
fun uriToAbsolutePath(uri: Uri, contentResolver: ContentResolver): String? {
    val projection = arrayOf(MediaStore.MediaColumns.DATA)
    val cursor: Cursor = contentResolver.query(uri, projection, null, null, null) ?: return null
    var path: String? = null
    if (cursor.moveToFirst()) {
        path = cursor.getString(0)
    }
    cursor.close()
    return path
}
```
これによって次のパスが取得できましたが、期待した絶対パスとは異なっており画像にアクセスすることはできませんでした。
```
/sdcard/.transforms/synthetic/picker/0/com.android.providers.media.photopicker/media/1000000020.jpg
```

### Intent.ACTION_PICK
続いて、Intent.ACTION_PICKによって取得できるURIについてです。
```
content://com.google.android.apps.photos.contentprovider/-1/1/content%3A%2F%2Fmedia%2Fexternal%2Fimages%2Fmedia%2F222/ORIGINAL/NONE/image%2Fjpeg/1415589116
```
MediaStore.ACTION_PICK_IMAGESと同様に、画像の取得からアプリが終了するまでは一時的なアクセス権が付与されているため、パーミッションなしで画像にアクセスすることができます。

ただし、MediaStore.ACTION_PICK_IMAGESと異なる点として下記コードによってアクセス権限を持続することはできません。
```kotlin
val flag = Intent.FLAG_GRANT_READ_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flag)
```

しかし、絶対パスについては次のように取得することができます。
```
/storage/emulated/0/DCIM/Camera/PXL_20230321_072320323.jpg
```

絶対パスによって画像にアクセスする場合はManifestに次のパーミッションを追加して許可します。
Android13からストレージアクセスのパーミッションに変更が入ったので、Android13用にREAD_MEDIA_IMAGESを要求します。
```
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
```

## まとめ
Android13の写真選択ツールはユーザー体験が良く魅力的でしたが、アクセス権限は一時的なもの。絶対パスは取得できない。
というところを抑えておく必要がありそうです。

私のアプリではDBに画像のパスを保存して、いつでも画像表示できるようにしたかったため写真選択ツールを使うことは諦めて、Intent.ACTION_PICKを使うことにしました。

この記事が誰かの参考になりましたら幸いです。
また、「もしこうしたらいけたよ」など教えていただけると嬉しいです。