【Android/Java】権限の取得方法から取得後の処理

今回は、

- アプリ起動時にカメラ、ストレージ、位置情報権限を取得したい
- 途中で位置情報権限を許可しないに変えることを想定
- ボタン押下時に位置情報権限がない場合は権限取得OSダイアログ表示

をするための方法をまとめる。

## 複数の権限を取得する方法
`requestPermissions`を扱うが、パッと調べた限りだと３つある。
1.[ActivityCompat#requestPermissions](https://developer.android.com/reference/androidx/core/app/ActivityCompat#requestPermissions(android.app.Activity,%20java.lang.String[],%20int))

`public static void requestPermissions (Activity activity, String[] permissions, int requestCode)`  
そもそもActivityCompatクラスって何なの？と気にしたことがなかったのでそのついで公式ドキュメントをみると
>Helper for accessing features in Activity.

とあった。このクラスのメソッドの大半は引数にActivityが必要だったので、必要になったら確認することにする。

2.[Activity#requestPermissions](https://developer.android.com/reference/android/app/Activity#requestPermissions(java.lang.String[],%20int))

`public final void requestPermissions (String[] permissions, int requestCode)`
ActivityクラスはAPI1からなので、Androidの元祖みたいなものだと思うが、この`requestPermissions`は意外にもAPI23から。

3.[Fragment#requestPermissions](https://developer.android.com/reference/android/app/Fragment#requestPermissions(java.lang.String[],%20int))

`public final void requestPermissions (String[] permissions, int requestCode)`
ただこれはAPI23で追加されたもののAPI28で<b>非推奨</b>となっているので注意。この代わりに何を使えば良いかは後述。


以上の３つは使える環境に合わせてどれを使っても変わらない気もする(一語一句公式の説明を読んだわけではないが)。今回は実際に使っていた環境がFragmentクラスだったので(今や非推奨だが)、3の`Fragment#requestPermissions`を扱う

取得したい権限はManifestに記載する必要がある。AndroidManifest.xmlで`application`タグの上に

```
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
```

[ACCESS_MEDIA_LOCATION](https://developer.android.com/reference/android/Manifest.permission#ACCESS_MEDIA_LOCATION)はAPI29からの権限になるが、OSバージョン分岐せずにしていても特に大丈夫そう。この権限は

>Allows an application to access any geographic locations persisted in the user's shared collection.

とあるが、確か画像から位置情報を取得するときにAPI29以上なら必要だった覚えが。。(残課題)

Fragmentクラス内で

```
private static final int REQUEST_PERMISSION = 1000;
private static String[] permissions = {
    Manifest.permission.ACCESS_FINE_LOCATION,
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.WRITE_EXTERNAL_STORAGE,
    Manifest.permission.ACCESS_MEDIA_LOCATION,
    Manifest.permission.CAMERA
};

/*
 * 中略
 */

ActivityCompat.requestPermissions(this.getActivity(), permissions, REQUEST_PERMISSION);

```

これでpermissions配列内の権限全てがチェックされ、不足している権限OSダイアログが表示される。

## 権限を許可・拒否した次の処理
一度でも実装したことがある人には周知のことだが、以下のように書くと権限取得結果関係なしに次の処理が実行されてしまう

```
ActivityCompat.requestPermissions(this.getActivity(), permissions, REQUEST_PERMISSION);

//次の処理
doNextTask();
```

もし権限を取得できたら次の処理を実行したい場合は`onRequestPermissionsResult`をオーバーライドする必要がある。

```
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    if (requestCode == REQUEST_PERMISSION_LOCATION) {
        if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            //許可された場合
            doNextTask();
        }
    }
}
```

第一引数(requestCode)は`requestPermissions`の第三引数になるので、上のサンプルだと`REQUEST_PERMISSION_LOCATION`と一致していたらOK。
第三引数(grantResults)の0番目はユーザーがOSダイアログで許可したのかどうかの結果が返ってくる。
　許可：[PERMISSION_GRANTED](https://developer.android.com/reference/android/content/pm/PackageManager#PERMISSION_GRANTED)
　拒否：[PERMISSION_GRANTED](https://developer.android.com/reference/android/content/pm/PackageManager#PERMISSION_DENIED)
　要素数0：ユーザーがキャンセルした


## onRequestPermissionsResultが呼ばれないことがあった
実際に実装していると、なぜかFragment内で`requestPermissions`を実行しても`onRequestPermissionsResult`が呼ばれずに時間を要したことがあった。ちなみにとある修正をする前までは普通に動いていたのだが、なぜか関係ないと思っている修正をした後だとNGになった。

呼ばれない原因・対策を調べ、[こちら](https://qiita.com/sho5nn/items/6598268cfc3eda051a2a)の記事を読んでみたものの該当する内容はなし。

結果としてはFragmentクラスでも`onRequestPermissionsResult`をオーバーライドしていたが、親Activityクラスでも`onRequestPermissionsResult`をオーバーライドしていていたことが原因だった。
`Fragment#requestPermissions`でも親Activity側の`onRequestPermissionsResult`が呼ばれる様子。なのでその時は全て親Activity側にまとめるように修正した。


## クリックイベント時に権限あるかないかみて処理を分ける
ボタン押下時に位置情報権限がある場合は任意の処理を、権限がない場合は権限取得OS表示するように実装


```
//細かい設定は省略
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            //位置情報権限が既にある場合
        } else {
            //ない場合は再度リエクエスト
            requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_PERMISSION_LOCATION);
        }
    }
});
```

これで位置情報権限は取得されるまで要求がループされます。


## 参考文献（本文中で記載していないもの)
https://docs.kii.com/ja/guides/cloudsdk/android/quickstart/install-sdk/activity-implementation-note/

https://qiita.com/m-coder/items/97a3ce16276334be84aa

## 環境
- Android Studio4.1.2
- Android10