実行時のパーミッション リクエスト(Runtime Permission) 色々まとめ


####〇前置き
現在地取得やカメラ機能など Dangerous パーミッションを利用する場合、Android 6.0（API レベル 23）以降、動的にアプリ内でパーミッションを付与する必要があります。

####〇タイトル
１．パーミッションリクエストに必要な関数
２．基本リクエストサンプル
３．権限が必要な説明をユーザーに表示するサンプル
４．連続でリクエストを出すサンプル
５．端末のアプリ設定画面に飛ばす

####〇参考URL

・[実行時のパーミッション リクエスト](https://developer.android.com/training/permissions/requesting?hl=ja)
・[Permissions Overview ](https://developer.android.com/guide/topics/permissions/overview?hl=ja)
・[Normal パーミッションと Dangerous パーミッション](https://developer.android.com/guide/topics/security/permissions?hl=ja#normal-dangerous)

#１．パーミッションリクエストに必要な関数
[ContextCompat.checkSelfPermission](https://developer.android.com/reference/android/support/v4/content/ContextCompat#checkSelfPermission(android.content.Context,%20java.lang.String))
ユーザーが権限を許可しているのか？の判定
ContextCompat.を付けることで、Android 6.0未満は全部権限ありとみなし、
6.0以降はユーザーの許可状況を教えてくれるので便利。
なので、Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
といった条件分岐は必要なし。

[ActiivtyCompat.requestPermissions](https://developer.android.com/reference/android/support/v4/app/ActivityCompat#requestPermissions(android.app.Activity,%20java.lang.String[],%20int))
ユーザに権限を与える
ポイントとしては権限の配列が引数に設定できます。（同時に複数リクエスト）


[ActiivtyCompat.OnRequestPermissionsResult ](https://developer.android.com/reference/android/support/v4/app/ActivityCompat.OnRequestPermissionsResultCallback)
ActiivtyCompat.requestPermissionsのコールバック
オーバーライドして権限がちゃんと付与されたのか判断することができる。

[ActiivtyCompat.shouldShowRequestPermissionRationale](https://developer.android.com/reference/android/support/v4/app/ActivityCompat#shouldShowRequestPermissionRationale(android.app.Activity,%20java.lang.String))
拒否されたときのリクエストの根拠を示すことができる。
なくてもよいが、なんでこの権限が必要なの？とユーザーが懐疑的に思う可能性があるので、
説明を表示してなるべくユーザーフレンドリーに権限を追加してもらえる。
`（注意）ただし一回目は表示してくれないという特性があるのでもし一回目も表示させるにはひと手間必要。`

#２．基本リクエストサンプル

```java:AndroidManifest.xml
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
```

```xml:activity_main.xml
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />

```


```java:activity_main.java
import android.Manifest;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_CODE = 1000;
    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = (Button)findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                checkPermission();
            }
        });
    }

    public void checkPermission(){
        // ・現在地取得のパーミッションの許可確認
        // 許可されていない
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // パーミッションの許可をリクエスト
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_CODE);
        }
        // パーミッションが許可されている
        else {
            Toast.makeText(this, "権限が許可されています", Toast.LENGTH_SHORT).show();
            // 以下通常処理等に飛ばす・・・
        }
    }

    // requestPermissionsのコールバック
    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "パーミッション追加しました", Toast.LENGTH_SHORT).show();
                    // 以下通常処理等に飛ばす・・・
                } else {
                    Toast.makeText(this, "パーミッション追加できませんでした", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }
}
```

#３．権限が必要な説明をユーザーに表示するサンプル
xmlファイルなどは上と同じ

```java:activity_main.java
import android.Manifest;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_CODE = 1000;
    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = (Button)findViewById(R.id.button);
        
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION,REQUEST_CODE);
            }
        });
    }

    // パーミッションの状態を確認して、各処理に飛ばす
    public void checkPermission(final String permission,final int request_code){
        // ・現在地取得のパーミッションの許可確認
        // パーミッションが許可されていない
        if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
            // ・許可が必要な説明を表示するかの判定
            // ２回目以降「今後は確認しない」にチェックがない限りtrueになる
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
                // 説明をダイアログで表示
                showInformationDialog(permission, request_code);
            }
            // 初回は shouldShowRequestPermissionRationale() は必ずfalse を返すため
            // 必要ならプリファランスなどに権限を許可したかの判定を持っていて、強制的にダイアログを表示する
//          else if ( プリファランス == false) {
//              showInformationDialog(permission, request_code);
//          }
            // 初回、または、「今後は確認しない」にチェックがある
            else {
                // 「今後は確認しない」の場合、パーミッション追加のポップアップは出ないで、コールバックは強制的に失敗[PERMISSION_DENIED]を返してくるので注意
                ActivityCompat.requestPermissions(this, new String[]{permission}, request_code);

            }
        }
        // パーミッションが許可されている
        else {
            Toast.makeText(this, "権限が許可されています", Toast.LENGTH_SHORT).show();
            // 以下通常処理等に飛ばす・・・
        }
    }

    // ダイアログを表示してOKを押したら、パーミッション追加リクエスト
    public void showInformationDialog(final String permission, final int request_code){
        new AlertDialog.Builder(this)
                .setTitle("パーミッションについて")
                .setMessage("最寄りの本屋を検索するのに現在地取得を許可してもらう必要があります。")
                .setPositiveButton(
                        "OK",
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                // 権限を追加
                                ActivityCompat.requestPermissions(MainActivity.this, new String[]{permission}, request_code);
                            }
                        })
                .show();

    }

    // requestPermissionsのコールバック
    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {

            case REQUEST_CODE:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "パーミッション追加しました", Toast.LENGTH_SHORT).show();
                    // 以下通常処理等に飛ばす・・・
                } else {
                    Toast.makeText(this, "パーミッション追加できませんでした。パーミッションがないと機能が使えませんよ！", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }
}
```

#４．連続でリクエストを出すサンプル

```java:AndroidManifest.xml
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
```

```java:activity_main.java
import android.Manifest;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_CODE = 1000;
    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = (Button)findViewById(R.id.button1);

        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                String permissions[] = new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.READ_PHONE_STATE,Manifest.permission.READ_EXTERNAL_STORAGE};
                checkPermission(permissions,REQUEST_CODE);
            }
        });
    }

    public void checkPermission(final String permissions[],final int request_code){
        // 同時に複数渡すが、許可されていないものだけダイアログが表示される
        ActivityCompat.requestPermissions(this, permissions, request_code);
    }

    // requestPermissionsのコールバック
    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {

            case REQUEST_CODE:
                for(int i = 0; i < permissions.length; i++ ){
                    if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                        Log.w( "DEBUG_DATA", "パーミッション追加しました " + permissions[i] + " " + grantResults[i]);
                    } else {
                        Log.w( "DEBUG_DATA", "パーミッション追加できませんでした。 " + permissions[i] + " " + grantResults[i]);
                    }
                }
                break;
            default:
                break;
        }
    }
}
```
#５．端末のアプリ設定画面に飛ばす

「今後は確認しない」にチェックが入っていると、プログラム上で変更はできないので、条件によっては設定画面に飛ばしてあげて、解除してきてもらう

```java:activity_main.java
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", getApplicationContext().getPackageName(), null));
getApplicationContext().startActivity(intent);
```