5分で対応するMパーミッション

### はじめに

* 入門向けにとにかく短くまとめました
* 言わずと知れた[PermissionsDispatcher](https://github.com/hotchemi/PermissionsDispatcher)の紹介記事です
* ざっくり理解できたらより詳しい記事やドキュメントを読みましょう

### Mパーミッションに対応するサンプルコード

このコードは、android.permission.CALL_PHONEの権限が取得できていない場合に、ボタンをクリックすると落ちます。(手元で試して見る場合は、[適当なレイアウトXML](https://gist.github.com/kazy1991/579947ddd9cdf9ff2113)を用意して下さい)

```java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // この呼び出しで落ちる
                makePhoneCallToSampleNumber();
            }
        });
    }

    public void makePhoneCallToSampleNumber() {
        Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:0123456789"));
        startActivity(intent);
    }
}
```

<img src="https://i.gyazo.com/623fa0485fb291d2704d9a999e9c1258.png" width=40% />

これから、上記のコードを最も手軽にMパーミッション対応していきましょう。

### Mパーミッション対応のために実装すべきこと

対応すべき処理は４つあります。

* 権限を事前に確認/必要であれば要求する処理
* 「許可しない」を選択したユーザーへの対応 (optional)
* 許可しなかったユーザーが再度ボタンをクリックした時の対応 (optional)
* 「今後は確認しない」を選択したユーザーがボタンをクリックした場合の対応 (optional)

これらを順に対応していきましょう。

### 対応するための準備
冒頭で書いたように本稿では、PermissonDispatcherを利用します。このライブラリは、煩雑な権限周りの手続きをよしなに対応してくれるものです。
初めにPermissionsDispatcherの依存関係とaptの設定を書きます。(aptをご存じない方はこちら[Androidでaptのライブラリを作るときの高速道路](http://qiita.com/rejasupotaro/items/b9b89f88348222b46708))

プロジェクトの`build.gradle`に記述する。

```diff
buildscript {
	 # ...
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'
+        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
```


アプリのモジュールの`build.gradle`に記述する。

```diff
+ apply plugin: 'android-apt'
# ...
dependencies {
+    compile 'com.github.hotchemi:permissionsdispatcher:2.0.1'
+    apt 'com.github.hotchemi:permissionsdispatcher-processor:2.0.1'
}
```

次に、PermissionsDispatcherの機能を有効にするため`@RuntimePermissions`アノテーションを先ほどの`MainActivity`つけます。

```diff
+@RuntimePermissions
public class MainActivity extends AppCompatActivity {
// ...
}
```

これで下準備は完成です。

### 権限の確認/要求の処理を追加する

権限の確認/取得が必要な`makePhoneCallToSampleNumber`メソッドに`@NeedsPermission`をつけます。(権限が複数必要な場合は{}を使って羅列します)

```diff
+   @NeedsPermission(Manifest.permission.CALL_PHONE)
    private void makePhoneCallToSampleNumber() {
```

次に、メソッドの呼び出しを自動生成されたDispatcher経由に変更します。今回の場合Dispatcherのクラス名はMainActivityPermissionsDispatcherとなります。もしこの時点でaptがDispatcherを生成してなかったら一度`Run 'app'`させてみましょう。

```diff
            public void onClick(View v) {
+               MainActivityPermissionsDispatcher.makePhoneCallToSampleNumberWithCheck(MainActivity.this);
-               makePhoneCallToSampleNumber() 
            }
```

最後に、権限関係のコールバックである`onRequestPermissionsResult`をオーバーライドして、Dispacherに処理をデリゲートします。

```diff
public class MainActivity extends AppCompatActivity {
// ...

+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+        MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
+    }
}
    
```

ここまでで正常系の実装は終了です。この段階でアプリを実行してみると次のようなダイアログが出るようになります。許可すると本当に電話が発信されるのでお気をつけください。

<img src="https://i.gyazo.com/8e9eebd9c4759d847c9b91f66cc75db3.gif" width=30%>

権限の確認/要求の実装したので、とりあえず落ちないアプリになりました。(最低限の実装ですね)

----------

### 権限を拒否したユーザーへのフィードバック処理

ここからはユーザーが権限の要求を拒否した場合のフィードバックを追加していきます。

#### 「許可しない」を選択したユーザーへの対応

"許可しない"を押されてすぐ出るフィードバックです。今回はToastを表示させましょう。`OnPermissionDenied`アノテーションがついたメソッドを実装します。 次のコードを追加すると"許可しない"を選択した時にToastが出るようになります。

```diff
public class MainActivity extends AppCompatActivity {
// ...

+    @SuppressWarnings("unused")
+    @OnPermissionDenied(Manifest.permission.CALL_PHONE)
+    void deniedPermission() {
+        if (PermissionUtils.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
+            Toast.makeText(this, "電話をかけるのに失敗しました。", Toast.LENGTH_SHORT).show();
+        }
+    }
}    
```

<img src="https://i.gyazo.com/b2099bd8de3cc5235d395c0c4d8eec71.gif" width=30% />

#### 許可しなかったユーザーが再度ボタンをクリックした時の対応

警戒して権限の許可をしなかったユーザーに、どうして権限を要求しているのか説明しましょう。今回は簡単なダイアログを出すことにします。OnShowRationaleアノテーションがついたメソッドを実装しましょう。
(サンプルコードでは[material-dialogs](https://github.com/afollestad/material-dialogs)というライブラリを使いますが、深い意味は無いのでなんでもいいです。)

```diff
public class MainActivity extends AppCompatActivity {
// ...

+    @SuppressWarnings("unused")
+    @OnShowRationale(Manifest.permission.CALL_PHONE)
+    void showRationalForStorage(final PermissionRequest request) {
+        new MaterialDialog.Builder(this)
+                .theme(Theme.LIGHT)
+                .title("電話をかけるには許可が必要です")
+                .content("通話の発信/管理には、ユーザーの方に権限の許可をいただく必要があります。")
+                .positiveText("許可する")
+                .negativeText("今はしない")
+                .onPositive(new MaterialDialog.SingleButtonCallback() {
+                    @Override
+                    public void onClick(MaterialDialog dialog, +DialogAction action) {
+                        request.proceed();
+                    }
+                })
+                .show();
+    }
}    
```

一度拒否した後に再度ボタンをクリックすると、いま追加したダイアログが表示されるようになりました。"許可する"を選択すると、`request#proceed`が呼ばれて、先ほどのシステムの権限ダイアログが再度出ます。

<img src="https://i.gyazo.com/0454b2e65b811f51e900e981f97fedea.gif" width=30% />


#### 「今後は確認しない」を選択したユーザーがボタンをクリックした場合の対応

一度システムの権限ダイアログを拒否すると下の画面のように「今後は確認しない」のチェックボックスが表示されます。これにチェックして"許可しない"を選択されるとアプリから権限を取得するチャンスがなくなります。

<img width=30% src="https://i.gyazo.com/59c8d79058abbed4169e8f4607ec6660.png">

こうなると僕たち開発者は、ユーザーの方に手動で設定画面から権限をONして貰う必要があります。なので今回はユーザーの方を設定画面に誘導するダイアログを表示しましょう。

```diff
public class MainActivity extends AppCompatActivity {
// ...

    @SuppressWarnings("unused")
    @OnPermissionDenied(Manifest.permission.CALL_PHONE)
    void deniedPermission() {
        if (PermissionUtils.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
            Toast.makeText(this, "電話をかけるのに失敗しました。", Toast.LENGTH_SHORT).show();
+        } else {
+            new MaterialDialog.Builder(this)
+                    .theme(Theme.LIGHT)
+                    .title("電話をかけることが出来ません")
+                    .content("電話をかけるためには、設定画面>許可からご自身で権限を許可して頂く必要があります。")
+                    .positiveText("設定画面へ")
+                    .negativeText("今はしない")
+                    .onPositive(new MaterialDialog.SingleButtonCallback() {
+                        @Override
+                        public void onClick(MaterialDialog dialog, DialogAction action) {
+                            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+                            Uri uri = Uri.fromParts("package", getPackageName(), null);
+                            intent.setData(uri);
+                            startActivity(intent);
+                        }
+                    })
+                    .show();
        }
    }
}
```

「今後は許可しない」を選択するとこのような画面が出るようになったと思います。"設定画面へ"のアクションはアプリ情報のトップ画面への遷移で、"許可"の画面に直接飛ぶ手段がないのはむず痒い所です。

<img src="https://i.gyazo.com/220048c9354ffe49500e7e4b1dd21a28.gif" width=30% />


### おわり

権限周りの知識がなくても実装が出来てしまうPermissionsDispatcherは素晴らしいですね!!。ユーザーに対するフィードバックの方法は、取得する権限やアプリの性質によっても様々だと思うので、色々と工夫してユーザーが不安にならないようにしたい所です。
最後に、私がMパーミッション周りを勉強する際にお世話になった記事を紹介します。PermissionsDispatcherを使ってると簡単に見えますが、実際は結構複雑で、targetSdkVersionによっても挙動が違ったりするので業務で対応する際にはしっかり確認しておきたいですね。 

#### 絶対に読むべきMパーミッションに関する記事(日本語)
* [初心者のためのM PERMISSIONS入門](http://techbooster.org/android/application/17223/)
* [Android 6.0 の Runtime Permissions (M Permissions) に対応するためのアクティビティ図](http://sys1yagi.hatenablog.com/entry/2015/11/07/185539)

[gist](https://gist.github.com/kazy1991/8c46c8155a1bba1d9431)に今回の最終的なコードを置いておきました。