【Android】SDカードに保存する

# この記事は
AndroidでSDカードに何かを保存したい
# 使うものは
* [SAF（ストレージアクセスフレームワーク）](https://developer.android.com/guide/topics/providers/document-provider?hl=JA)
* [StorageVolume](https://developer.android.com/reference/android/os/storage/StorageVolume)
* [DocumentFile](https://developer.android.com/reference/android/support/v4/provider/DocumentFile)
* [特定のディレクトリへのアクセス](https://developer.android.com/training/articles/scoped-directory-access?hl=ja)

# 使わないものは
* getExternalDirectoryとか

# まずコード
``` :SDカードへのアクセス権限をユーザに求める
    private fun requestSecondaryStorageAccess() {
        val sm = getSystemService(STORAGE_SERVICE) as StorageManager
        sm.storageVolumes.find { storageVolume -> storageVolume.isRemovable }.let {
            val intent = it.createAccessIntent(null)
            startActivityForResult(intent, REQUEST_SDCARD_ACCESS)
        }
    }
```
``` :権限付与されたURIをごにょごにょする
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (requestCode == REQUEST_SDCARD_ACCESS && resultCode == RESULT_OK) {
            data?.data?.let {
                contentResolver.takePersistableUriPermission(
                    it,
                    Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                )

                val outUri = DocumentFile.fromTreeUri(this, it)?.createFile("text/plain", "sample")?.uri
                outUri?.let{
                    contentResolver.openOutputStream(it)?.use{
                        it.write("hoge".toByteArray())
                    }
                }
            }
        }
```

# 細かく
```
    val sm = getSystemService(STORAGE_SERVICE) as StorageManager
    sm.storageVolumes.find { storageVolume -> storageVolume.isRemovable }.let {
        val intent = it.createAccessIntent(null)
        startActivityForResult(intent, REQUEST_SDCARD_ACCESS)
    }
```
* StorageManagerのgetStorageVolumes()で、利用可能な全てのStorageVolumeを取得し、isRemovable()で取り外し可能であることをチェックする（＝SDカードである）
* SDカードであるStorageVolumeに対してcreateAccessIntent(null)でディレクトリアクセス権を要求するIntentを生成し、発行する
    * createAccessIntentの引数にnullを与えるとルート権限、[その他色々指定できる](https://developer.android.com/reference/android/os/storage/StorageVolume.html?hl=ja#createAccessIntent(java.lang.String))
    * **Android Q非対応（後述）**

```
    data?.data?.let {
        contentResolver.takePersistableUriPermission(
            it,
            Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
        )
```
* ユーザがアクセス権限を与えたディレクトリのUriがdataに格納される
* このUriはそのままだと揮発するが、ContentResolverのtakePersistableUriPermissionでシステムにUriを覚えさせておくことが可能
    * 端末を再起動するまで有効。一度覚えると再度ユーザに権限許可を求めずRESULT_OKが返される。

```
    val outUri = DocumentFile.fromTreeUri(this, it)?.createFile("text/plain", "sample")?.uri
    outUri?.let{
        contentResolver.openOutputStream(it)?.use{
            it.write("hoge".toByteArray())
        }
    }
```
* DocumentFileのfromTreeUriで「アクセス許可されたUri」からDocumentFileオブジェクトを生成
    * この場合、SDカードのルートディレクトリを指す
* DocumentFileのcreateFileでmimeTypeとfilenameを指定してDocumentFileを生成し、Uriを取得
* 新ファイルのoutputStreamを生成し、hogeと書き込んだ

# Android Q
createAccessIntentがDeprecatedとなった。~~余計なことしやがって！~~

``` :ディレクトリピッカーを開いてユーザに権限を付けてもらう
    val intent = volume.createOpenDocumentTreeIntent()
    startActivityForResult(intent, REQUEST_SDCARD_ACCESS)
```
* SDカードの権限を付けますよ～という方法が封じられ、ユーザがディレクトリを指定することが必要になった。
* 因みにAndroidQでは見送られたが、来年（＝2020？）にはSAFに完全移行しないとファイル周りは使えなくなるらしい。どこかに書いてあったけどソースは忘れました。ごめんなさい。