Android 11 のACTION_OPEN_DOCUMENT_TREE制約を確認する

# はじめに

Android 11では、ローカルストレージのアクセス制限が強化されました。その一つとして、Storage Access Framework (以降SAF) のACTION_OPEN_DOCUMENT_TREEで指定できるディレクトリに以下のような制約が発生しています。

[Android 11 でのストレージに関する更新  \|  Android デベロッパー  \|  Android Developers](https://developer.android.com/preview/privacy/storage?hl=ja)

> **ディレクトリへのアクセス**
ACTION_OPEN_DOCUMENT_TREE インテントのアクションを使って以下のディレクトリへのアクセスをリクエストすることはできなくなりました。

> * 内部ストレージ ボリュームのルート ディレクトリ。
* エミューレートされているカードかリムーバブル カードかによらず、デバイス メーカーが信頼できるとみなす各 SD カード ボリュームのルート ディレクトリ。信頼できるボリュームとは、ほとんどの場合にアプリが正常にアクセスできるボリュームです。
* Download ディレクトリ。

この動作を確認しました。

# 実験

開発環境: Android Studio 4.0.1
動作端末: Android 11 エミュレータ端末
言語: Kotlin

以下のようなコードで、SAFのディレクトリのピッカーを呼び出します。

```kotlin
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
    flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
startActivityForResult(intent, READ_REQUEST_CODE)
```

targetSdkVersionが30の場合は、仕様にあるルートディレクトリやDownloadディレクトリは選択できません。

| 内蔵ストレージのルート | SDカードのルート | Downloadディレクトリ |
|:----------:|:------------:|:------------:|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78503/0d54aacf-92fe-7aec-435c-3ac9fce28440.png" width="200" /> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78503/d457a63f-2a76-9e07-dd7e-751bd39215f5.png" width="200" /> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78503/e90eaf36-4725-7f93-5147-cf7d30919aaa.png" width="200" /> |

適当なディレクトリに入ると選択できるようになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78503/5aeff92f-3ff2-12aa-f6aa-30396d886d73.png" width="200" />

targetSdkVersionを29にすると、ルートでも選択できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78503/e79d28e6-55f8-3d5f-71a0-33a2e2d313b5.png" width="200" />

また、USBにアダプタを介してストレージデバイスを接続した場合(SDカードやUSBメモリ等、メディア種別は関係無いと思われます)、ルートを選択することができました(確認: Pixel 3a Android 11 beta2で確認)。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78503/b4fcf69a-f6e1-5420-8f57-ea58ba865b99.png" width="200" />

[Googleの説明](https://developer.android.com/preview/privacy/storage?hl=ja#other-app-specific-dirs)には、ルートが選択できないストレージの条件として

> エミューレートされているカードかリムーバブル カードかによらず、デバイス メーカーが信頼できるとみなす各 SD カード ボリュームのルート ディレクトリ。信頼できるボリュームとは、ほとんどの場合にアプリが正常にアクセスできるボリュームです。

とあるので、外部接続のストレージデバイスはこれに該当しないようです。


余談ですが、Android 7以降ではストレージアクセス権限取得に以下のようなコードが実行できましたが、Android 10で既に使えなくなっています(実行してもダイアログが表示されず、onActivityResultには必ずRESULT_CANCELEDが返ってきます)。短い命でした。

```kotlin
(getSystemService(STORAGE_SERVICE) as? StorageManager)?.storageVolumes?.firstOrNull{ storageVolume -> storageVolume.isRemovable }?.let {
    val intent = it.createAccessIntent(null)
    startActivityForResult(intent, 1)
}
```

# さいごに

というわけで、targetSdkVersionを30にすると仕様通りACTION_OPEN_DOCUMENT_TREEでストレージのルートディレクトリなどが選択できなくなっていることが分かりました。

AndroidではLollipop以降、SDカードへの書き込みができないため、このACTION_OPEN_DOCUMENT_TREEによってSDカードのアクセスアクセス権限を取得する方法がよく用いられてきましたが、今後は不特定多数のファイルを扱うためには、別の手段を検討する必要があるようです。もちろん、特定のディレクトリだけアクセス出来れば良いのであれば変更の必要は無いかと思いますが。

これに代わる手段として、[MANAGE_EXTERNAL_STORAGE 権限](https://developer.android.com/preview/privacy/storage)なるものが新しく追加されており、こちらを用いるように書かれています。ただしこの権限を使う場合は審査が入るため、ファイラ等の機能を持つアプリ以外ではどうなるか分かりません(恐らくアプリ用途が不適切な場合はリジェクトされるものと思われます)。単純にSDカードにファイルをエクスポートするといった用途で用いてた場合は、SAFのACTION_CREATE_DOCUMENTアクションによるピッカーを用いたファイルを書き込みを検討した方が良いかと思います。