Androidアプリ公開後にインストールしたら、消去したSharedPreferencesが復活してSecurityエラーになった話

# Androidアプリ公開後にインストールしたら、消去したSharedPreferencesが復活してSecurityエラーになった話

## はじめに
開発時には、アンインストールしてからadb installを使用してアプリをインストールしていましたが、アプリ公開後は、アンインストールしてGoogle Playからアプリをインストールするようになりました。
その結果、SharedPreferenceの挙動に違いが生じ、セキュリティエラーが発生し、修正が必要になったことを投稿します。

## 公開したAndroidアプリのSecurityエラーの処理
 **Stereogram Eye Exercise** というアプリをGoogle Playで公開しました。
このアプリは、ユーザにスマホ内の人物やペットの画像を選択させます。画像の顔や体を認識して人物やペットを切り取って立体写真(立体視)表示します。
履歴として過去に選択した画像を一覧表示して、二回目以降は画像の選択操作なしで立体写真表示するようにしています。
この履歴の画像を一覧表示する処理で、Google PlayからインストールしたらSecurityエラーが発生しました。
注：立体写真(立体視)を見ることは目の運動になると言われており、 **Stereogram Eye Exercise** というアプリ名になっています。

### スマホに保存された画像ファイルを表示するにはPermissionが必要
スマホに保存された画像ファイルをアプリで表示するには、ファイルへのPermissionが必要になります。
 **ユーザにファイルを選択**してもらう場合、AndroidManifest.xmlで、READ_EXTERNAL_STORAGEを記述する必要はなく、その他のPermissionも特に必要ありません。
次のようにファイル選択Activityを起動して、ユーザの画像ファイルの選択結果を受け取ります。
アプリにユーザが選択した画像ファイルへのPermissionが付与され、アプリで画像を表示することが可能になります。

```Fragment.kt
var activityResultLauncher = registerForActivityResult(
  ActivityResultContracts.OpenDocument()) { url ->
    url?.let {
      viewModel.setImageUri(url)
      // urlをSharedPreferencesに保存する
         .... 省略 ....
    }
  }

activityResultLauncher.launch(arrayOf("image/*"))
```
アプリに付与された画像ファイルのPermissionはアプリが終了すると**消えて**しまうので、takePersistableUriPermission()でアプリのストレージ領域にPermissionを保存するようにします。
これを行うと、アプリを再起動しても画像ファイルのPermissionが付与されており、ユーザにファイルを再選択してもらうことなくアプリで画像を表示することができます。
```Fragment.kt
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION
requireContext().contentResolver.takePersistableUriPermission(url, takeFlags)
```

## 開発時のadb installではScurityエラーは発生しなかった
ユーザに選択した画像ファイルのUrlをSharedPreferencesに保存する。
アプリを起動したときにSharedPreferencesから画像ファイルのUrlを取得して、履歴一覧に画像を表示する。
開発時にアプリをアンインストールしてからadb installでインストールする。この時、アプリを起動してもSecurityエラーは発生しなかった。
アプリをアンインストールした時にSharedPreferencesが削除されており、履歴に表示する画像がないのでSecurityエラーは発生しなかった。

## Google PlayからインストールするとSecurityエラー発生
開発に使用した端末からアプリをアンインストールして、Google Playからアプリをインストールする。アプリを起動するとSecurityエラーが発生した。
アプリをアンインストールしてSharedPreferencesが削除されたと思っていたが、Google PlayからアプリをインストールするとSharedPreferencesが復元された。
SharedPreferencesが復元されたことで、アプリ起動時に画像ファイルを履歴一覧に表示しようとしたが、画像ファイルのPermissionは復元されおらずSecurityエラー(画像ファイルへのPermissionが無い)が発生した。

## 対応策
SharedPreferencesに保存したUrlの画像ファイルを履歴一覧に表示する時に、画像ファイルのPermissionが存在するかチェックして、Permissionが存在する時だけ画像を表示するようにした。
この対策でGoogle playからインストールしても問題なくアプリが起動できた。
```Fragment.kt
var uriPermissionList = context!!.contentResolver.getPersistedUriPermissions()
if (uriPermissionList.isEmpty()) {
  // 画像ファイルのPermissionが存在しない
  return
}
  ...省略...
var isPermissionOk = false;
// 権限が存在するかチェック
run loop@ {
  uriPermissionList.forEach { uriPerm ->
    if (uriPerm.uri.path == null) { return@forEach } // forのcontinue
    // imageUriは履歴一覧に表示する画像ファイルのUri
    if (uriPerm.uri.path.equals(imageUri.path)) {
      isPermissionOk = true
      return@loop // リソースパスが一致 forのbreak
    }
  }
}
if (!isPermissionOk) {
  // 画像ファイルのPermissionが存在しない
  continue
}
```

## 参考
[ファイルを他のアプリで開きたい ▶︎Intentを勉強する](https://zenn.dev/tbsten/articles/1fc1727ad6826b)

## その他
[Stereogram Eye Exercise (本記事のアプリ)](https://play.google.com/store/apps/details?id=com.github.motoshige021.stereovisionexercise)