M Permissions の導入で一日ハマった話

android初心者です。今回M Permissionsを既存のpermissionに導入しようとしたところ、`shouldShowPermissionRationale()` の挙動に惑わされ、最終的に１日費やしてしまいました。
これから使おうと思っている方に見ていただければ幸いです。

##M Permissionsの全容
- M permissionsとはAndroid6.0(Marshmallow)から導入された新しいpermissionモデルです。
- 今まではアプリのインストール時にすべての権限をユーザーが設定していましたが、M permissionsでは機能が利用されようとしている段階で、権限を許可するダイアログを出すという形になっています。
- 下のコードをみると、`checkSelfPermission()`でpermissionが存在しているかどうかを判定し、ない場合には`requestPermission()`でダイアログを表示させるリクエストを出し、`onRequestPermissionsResult()`で許可/不許可の結果を受け取る形になっています。

↓こんな感じのダイアログが表示されます
![Screen Shot 2016-10-17 at 3.14.53 AM.png](https://qiita-image-store.s3.amazonaws.com/0/117537/44368762-52de-de36-962f-0ae5447f48fa.png)


```Java
    public static boolean existPermission(@NonNull Context context, @NonNull String... permissions) {
        // ContextCompat.checkSelfPermission() could be ok, but not adapted to App Op.
        // could be better to user PermissionChecker
        for (String permission : permissions) {
            int result = PermissionChecker.checkSelfPermission(context, permission);
            if (result != PackageManager.PERMISSION_DENIED) {
                return true;
            }
        }
        return false;
    }
```

```Java
        if (existCameraPermission(getActivity())) {
		// permissionが存在し、機能を利用できる状態
        } else {
        // permissionが存在しない状態、ダイアログを表示してユーザーに許可を求める
        //①
        requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, requestCode);
        }
```

```Java
    // dialogでuserがどちらのボタンを押したのか(許可・不許可)の結果を取得している
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//許可の場合には機能を利用するメソッドに遷移する
//不許可の場合にはユーザーに機能を制限して利用するかどうかのダイアログを表示する。
// ②
    }
```

##`shouldShowPermissionRationale()`とは?

さて、`shouldShowPermissionRationale()` メソッドの公式ドキュメントをみてみましょう

- rationaleを含んだUIを表示するかどうか判定している
- あなたがこのpermissionを使うべき明確な理由をユーザーに提示していない場合に使うべきである

つまりはrationaleを含んだDialogを出すべきかどうかを判定しているという形になります。
このボタンがrationaleですね。
![Screen Shot 2016-10-17 at 3.13.54 AM.png](https://qiita-image-store.s3.amazonaws.com/0/117537/ea680876-c763-967d-028f-1a0db2ec5395.png)

流れとしては
rationaleを含んでいないダイアログでユーザーが不許可を押す->
rationaleを含んだダイアログを表示し、ユーザーがrationalボタンを押す->
その後はダイアログは出さない・機能を使えないようにする
という形になります。
そこで私は`shouldShowPermissionRationale()`メソッドは`ユーザーが不許可を押したときのみにfalse`になり、そこでユーザーが不許可を押したことを判定できる...と思っていました。
上に示した①②の部分に`shouldShowPermissionRationale()`メソッドをおいてみると、、、
![IMG_0402.JPG](https://qiita-image-store.s3.amazonaws.com/0/117537/e4a59d60-603b-8d8c-7bb7-d585c7464943.jpeg)


こんな感じになりました
分かりづらくて申し訳ないのですが、
S:初期状態がONかOFFか(1/0)
T:その後設定画面で動かした場合、状態遷移後の状態(ON:1/OFF:0)
B1:①の部分での`shouldShowPermissionRationale()`のTrue/False
D1:ダイアログで押したボタン(True:承認、False:非承認)
B2:②の部分での`shouldShowPermissionRationale()`のTrue/False
D2:2つ目のダイアログでの押したボタン(True:承認、False:非承認、Rationale:今後表示しない)

この表を見てみると、最初に設定画面を動かしたかどうかで(?)B1の部分はTrue/Falseが変化してしまっています。そのため、①の部分に`shouldShowPermissionRationale()`を置いてしまうと、ダイアログが全く出ないまま機能が使えないという状態に陥ってしまいます。

ただこれはB2を見ていただけると分かると思うのですが、ダイアログ1つめにユーザーがTrue/Falseを押した段階で、`shouldShowPermissionRationale()`は一度すべてTrueになります。そして２つ目のダイアログでrationaleを押すと、`onRequestPermissionsResult()`はfalseになり、`onRequestPermissionsResult()`が呼び出されるものの、ダイアログは表示されなくなります。

真ん中の紫の部分はrationaleをダイアログで押すと、その後はダイアログは一切呼び出されないことを表しています。

ここから分かることは、`shouldShowPermissionRationale()`はダイアログでユーザーが選択したものが常に上書きされるという動きをしているということなのです。

私は①の`shouldShowPermissionRationale()`の結果を見て混乱してしまったのですが、単純に考えれば②にメソッドを設置しておけば、ダイアログから来た結果のみでrationaleが押されているかどうかが判定できるわけです。最終的に

```Java
            if (!fragment.shouldShowRequestPermissionRationale(permission)) {
                        new AlertDialog.Builder(context)
                                .setTitle(R.string.storage_permission_title)
                                .setMessage(R.string.storage_permission_message_confirm)
                                //設定画面に行くかどうかの文章を設定
                                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialogInterface, int i) {
                                        FincPermissionUtils.openSettings(context);
                                    }
                                })
                                //okを押した場合は設定画面への遷移
                                .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialogInterface, int i) {
                                    }
                                })
                                //falseを押した場合は何も表示しない(ダイアログを出した方がいいかもしれないです)
                                .create()
                                .show();

                return;
            }
```

このようなコードを②に挿入することで、rationaleが押されたときに確認のダイアログを出力し、許可を押した場合には設定画面に遷移させることに成功しました。
もし同じ部分で挙動が変だなと思っていた方の参考になれば幸いです。