Android 6.0のRuntime Permissionに対応する

Android 6.0ではRuntime Permissionという概念が取り入れられました。
それまでのアプリでは動作に問題が出ることが有ります。

#Runtime Permissionとは#

Androidではカメラやネットワークなど一部の機能を使用するには、ManifestにPermissionを記載して、使用を明示的に宣言しないと使えない機能があります。
これにより、ユーザーはそのアプリがどのような機能を使用しているかを把握することが出来ます。

PermissionはAndroid6.0未満では全てインストール前に確認するという方式が取られており、ユーザーがPermissionを許可しなかった場合アプリをインストールすることが出来ませんでした。
しかし、この方式はいくつかの問題を含んでいます。

- 多くのユーザーはインストール前にそのアプリがどのPermissionをどうやって使うのか把握しづらく、ユーザーはPermissionに何が宣言されていようとそれが妥当であるか判断できない。
- 一度許可したアプリは永続的に許可され続けるのでユーザーによるPermissionチェックが適切に働きにくい。
- Permissionが変更されるとユーザーの手動アップデートが必要となるため、Permissionを追加したバージョンアップでアップデートがなかなか行ってもらえなくなったり、それを防ぐために予め片っ端からPermissionを宣言しておくなどということが発生していた。

それらを解決するためにAndroid6.0ではRuntime Permissionという仕組みが取り入れられました。
Runtime Permissionではカメラやアドレス帳へのアクセスと言ったリスクの高いPermissionについてインストール時には許可を求めず、**アプリ実行中の任意のタイミング**で許可を得ることができるようになります。

必要になったタイミングでPermissionを求めることができるので、ユーザーはより直感的に何故そのPermissionが必要なのか理解しやすくなりますし、開発者はユーザーにPermissionの必要な理由を説明することができるようになります。
また、ユーザーはあとからPermissionを変更することも出来ます。
アップデート時にPermissionを追加しても自動アップデートが行われます。
#Runtime Permissionの動き#
Runtime Permissionが端末上でどのように動くは、TargetAPIの指定により変わってきます。
TargetAPIが22以前の場合は従来のアプリのようにインストール時にPermissionの一覧が表示されユーザーが全てのPermissionを許可することでインストールが可能となります。
これにより、インストール直後は**アプリが使う全てのPermissionが許可状態**となっています。

TargetAPIが23以上の場合はネットワークアクセスのような標準的なPermissionについては今までどおりインストール時に行われますが、カメラや位置情報のようなユーザーのプライバシーに重要な影響をおよぼす可能性が高いセンシティブなPermissionについてはインストール時にはチェックされずそのままアプリがインストールされます。
TargetAPIが22以前の場合と違い、この時点ではセンシティブなPermissionは拒否状態となっています。
![kenz1821-targetsdk.jpg](https://qiita-image-store.s3.amazonaws.com/0/17830/1b288c91-c86d-adc7-7452-a6774bc1e732.jpeg)

Target SDKが22以前だったとしてもインストール時に許可を求めることで初期値が許可になるだけという点に注意してください。
target SDKに関係なくAndroid6.0ではインストール後個別にPermissionを拒否することができます。
Target SDKが22未満でPermissionを拒否するときには以下の警告ダイアログが出ます。
![「このアプリはAndroidの以前のバージョンを対象としています。権限を許可しないと、意図したとおりに動作しなくなる可能性があります。」](https://qiita-image-store.s3.amazonaws.com/0/17830/f8434219-70dc-44bd-3388-9395ca973b8e.png)
警告はしてくれますがそれに従うかはユーザーの自由。
Permissionが拒否されている状態でPermissionが必要なAPIにアクセスするとアプリが例外で落ちてしまいます。
そのため、Runtime Permissionの対応が面倒なのでTargetSDKを22のままにしておくのは根本的な対策になりません。

Runtime PermissionはAndroid6.0以降でのみ動作します。
Android5.1以下ではtargetSDKのバージョンによらずすべてのPermissionが従来通りインストール前に確認されます。

#いつ許可を求めるか#

アプリがそのPermissionをどの程度必要としているかによって許可を求めるタイミングを変えることで利便性を改善できます。

##Permissionが無いとアプリが成立しない場合##
アプリを開始時にPermissionの許可取得リクエストを実行します。
インストール後にもPermissionを拒否できるので、実行時のチェックも必要ですが、いざ利用したいという時に個別にパラパラと許可を求めると、ユーザーにとって煩わしく感じるおそれがあるため最初の段階で許可を求めます。
例えば画像編集アプリであればストレージへのアクセスが無いとアプリとして役に立たないので初回起動時にPermissionを求めます。

##Permissionが無くてもアプリが成立する場合##
アプリの付加機能だけでPermissionが必要なAPIを使用している場合など、Permissionがなくてもアプリとして成立するのであれば、起動時にはPermissionの要求を行わずに、Permissionが必要な機能を使用する段階で許可を求めるダイアログを表示します。
例えば画像編集アプリの場合、カメラへのアクセスは、写真を撮って加工したい人には必要ですが、既に撮った写真を加工したい人にとっては不要です。
このような場合はカメラ機能を押したタイミングでPermissionを求めるようにすることで、カメラ機能を使わないユーザーに不要な気を使わせる必要がなくなります。

##ユーザーのアクションを主体とせずにPermissionが必要な場合##
SMSの受信のようにユーザーがアクションしていないタイミングで必要となるPermissionは、たとえPermissionがなくてもアプリが成立したとしても、Permissionを求めるタイミングが他にないため起動時に許可を求めます。

#どうやって許可を求めるか#

そのPermissionを要求する理由が明確かによって許可の求め方を変えます。

##ユーザーがPermissionを必要とする理由が明確である場合##
単にPermissionを要求するダイアログを表示します。
カメラボタンを押した時にカメラのPermissionをリクエストするのはユーザーにとってわかりやすく、単にPermissionを要求するダイアログで事足ります。

##Permissionを必要とする理由がわかりにくい場合##
Permissionが必要な理由を説明します。
例えばユーザー認証を行うためにSMSを使用している場合、SMSへアクセスするPermissionが必要ですが、ユーザーはSMSへのアクセス権が何故必要なのか理解できない可能性があります。
このような場合にはユーザー認証にSMSを使用しており、SMSへのアクセスに許可しないと正しく自動認証が行えない旨を伝えて許可を求めます。

#拒否された時の対応#

ユーザーがPermissionを拒否する可能性があります。
拒否された場合にどのように振る舞うかは、状況により切り替えます。

##Permissionがないとアプリの本質的な機能を使用することができなくなる場合##
Permissionが必要な理由を説明し許可を求める画面を全面に表示し、それ以上先に進めなくします。
これによりユーザーに強くPermissionを求めることが出来ます。
どうしても許可したくないユーザーはアプリを使えなくなりますので、この方式はPermissionがアプリにとって必要不可欠である場合のみに行います。

##Permissionがなくてもアプリとして機能するが、Permissionの拒否がユーザーの一時的な気の迷いだと思える場合##
再度該当の機能を実行するタイミングで、なぜそのPermissionが必要なのかを説明を表示して、再度承諾を求めるダイアログを表示します。
ユーザーはすぐにPermissionを有効にすることが出来ます。

##Permissionがなくてもアプリとして機能し、かつユーザーが明示的にPermissionを拒否し続けると思われる場合##
対象のボタンをグレイアウトするなどしてPermissionを拒否したことで機能が使用できなくなったことをあらわします。
それでも明示的にボタンが押された時のみ再度許諾を求めるダイアログを表示します。
これにより、ユーザーは2度とPermissionを求めるダイアログでイライラしなくて良くなります。
場合によってはボタンそのものを非表示にするべきかもしれません。
この場合、ユーザーは設定から明示的にアプリに許可を付け直さないかぎりPermissionを再度許可できなくなります。

#Runtime Permissionの対象となるPermission#

Runtime Permissionの対象となるPermissionは次のとおりです。

| Permission | 役割 |
|:-----------|:------------|
|READ_CALENDAR|カレンダーの読み込み|
|WRITE_CALENDAR|カレンダーの書き込み|
|CAMERA|カメラ機能
|READ_CONTACTS|連絡先の読み込み
|WRITE_CONTACTS|連絡先の書き込み
|GET_ACCOUNTS|ユーザーが使用しているアカウントの取得
|ACCESS_FINE_LOCATION|詳細な位置
|ACCESS_COARSE_LOCATION|大まかな位置
|RECORD_AUDIO|オーディオの録音（マイク）
|READ_PHONE_STATE|電話の状態を取得する
|CALL_PHONE|電話をかける（直接発信するのではなく通話アプリをIntentで呼び出す場合は不要）
|READ_CALL_LOG|通話履歴を取得する
|WRITE_CALL_LOG|通話履歴を書き込む
|ADD_VOICEMAIL|ボイスメールを追加する
|USE_SIP SIP|(Session Initiation Protocol)を使用する
|PROCESS_OUTGOING_CALLS|通話発信時のIntentを補足する
|BODY_SENSORS|心拍数などの身体センサーを使用する
|SEND_SMS|SMSを送信する
|RECEIVE_SMS|SMSを受信する
|READ_SMS|SMSを読む
|RECEIVE_WAP_PUSH|WAP(Wireless Application Protocol）PUSHを受信する
|RECEIVE_MMS|MMSを受信する
|READ_EXTERNAL_STORAGE|外部ストレージから読み込む
|WRITE_EXTERNAL_STORAGE|外部ストレージに書き出す

上記のPermissionを使っている場合Android6.0以上の端末ではRuntime Permission扱いとなり対応が必須となります。
Target SDKを22以下とすることで、とりあえず動かし続けることは可能です。 しかしながら、その場合ユーザーが明示的にPermissionを拒否すると例外で落ちます。

#Permissionとどう付き合うか#

##Permissionを許可しないユーザー##
今回の変更でPermissionに対するユーザーの考え方が変わってくる可能性があります。
これまではインストール時に漠然と表示していたため多くのユーザーがPermissionを意識せずに許可していました。
一方で今後は明確にユーザーを説得する必要が出てくるので、かなりの割合でPermissionを許可しないユーザーが出てくると思います。

##Permissionを減らす##
まず第一に利用するPermissionを減らす事を考えます。
例えば電話を発信する場合、アプリ内から直接発信する場合はPermissionが必要ですが、番号がセットされた発信画面を表示するまでならPermissionは必要ありません。
ユーザーが明示的に通話ボタンを押す必要が出てきますが、むしろそちらのほうがユーザーフレンドリーといえる場合もあるはずです。
Permissionが必要な電話発信

``` java
Uri phoneNumber = Uri.parse(“tel:0123456789″);
Intent callIntent = new Intent(Intent.ACTION_CALL,phoneNumber);
startActivity(callIntent);
```

Permissionを必要としない電話発信

``` java
Uri phoneNumber = Uri.parse(“tel:0123456789″);
Intent callIntent = new Intent(Intent.ACTION_DIAL,phoneNumber);
startActivity(callIntent);
```
同様に、現在地を表示する場合にはPermissionが必要ですが地図アプリをIntentで開けばPermissionが必要なくなります。

##許可されない状態で動作する##
Permissionを許可しないユーザーもユーザーとして取り込むにはPermissionが許可されていない状態で出来るだけ多くの機能が動作できるような作りにする必要があります。

#対応方法#

対応が必要な手順は3つあります。

1. Permissionが許可されているか確認する。
2. Permissionが許可されていない場合許可を求める。
3. Permissionが許可・拒否された時の処理を追加する。

##Permissionが許可されているか確認する。##
Permissionの許可を求めるには

`ContextCompat#checkSelfPermission()`
を使用します。
第1引数にContext,第2引数に確認したいPermissionを表す文字列を渡します。
Permissionを表す文字列は `android.Manifest.permission`から選択します。存在しないPermissionを指定すると実行時例外が発生するため注意してください。

通常は`Manifest.permission.CAMERA`のようにManifestをimportして指定することが出来ますが、GCMを使っている場合には別のManifestクラスが自動生成されるため明示的にandroid.Manifestをimportするか、`android.Manifest.permission.CAMERA`のように完全修飾子を指定します。

戻り値はint型で、許可されている場合は、`PackageManager.PERMISSION_GRANTED`
拒否されている場合は`android.content.pm.PackageManager.PERMISSION_DENIED`が返ります。

###CAMERAが許可されているかを確認する場合の例###

```java
if (ContextCompat.checkSelfPermission(
  this,android.Manifest.permission.CAMERA)== PackageManager.PERMISSION_GRANTED){
  // 許可されている時の処理
}else{
  // 拒否されている時の処理
}
```

##Permissionが許可されていない場合許可を求める。##
許可されていないことが分かった場合は `ActivityCompat#shouldShowRequestPermissionRationale()`を呼びます。
このメソッドはユーザーがPermissionを明示的に拒否したかどうかを返します。
引数は`ContextCompat#checkSelfPermission()`と同様に、Contextと確認するPermissionを表す文字列です。
ユーザーが明示的に拒否した場合はtrue、ユーザーがまだ判断していないだけの場合はfalseが返ります。
trueが帰ってきた場合、ユーザーにPermissionが何故必要なのか説明して説得して再度許可を求めるか、ボタンを無効にするなどの処理を行います。
falseが帰ってきた場合、ユーザーはまだ判断を行っていないので許可を求めるダイアログを表示します。

許可を求めるには`ActivityCompat#requestPermissions()`を使用します。
引数は
第1引数にContext
第2引数にPermissionを表す文字列の配列
第3引数にコールバックで受け取るint型の数字（任意の数字）
を指定します。

第2引数が`checkSelfPermission()`や`shouldShowRequestPermissionRationale()`と違って配列なのに注意してください。
複数の文字列を渡すことでPermissionの許可を一度に求めることが出来ます。
ここで指定するPermissionは**AndroidManifestに宣言したPermission**でなくてはいけません。
もしAndroidManifestに宣言した以外のPermissionを指定すると
「問題が発生したため、パッケージインストーラーを終了します。」というわかりにくいエラーが表示されアプリが終了します。
![kenz1821-package.jpg](https://qiita-image-store.s3.amazonaws.com/0/17830/e18239dd-248c-c33c-48d8-25d03e890f0c.jpeg)

Cameraが許可されていない場合、ユーザーが明示的に拒否したかどうかで処理を分ける例

```java
if (ContextCompat.checkSelfPermission(
  this,android.Manifest.permission.CAMERA)== PackageManager.PERMISSION_GRANTED){
  // 許可されている時の処理
}else{
  //許可されていない時の処理
   if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CAMERA)) {
      //拒否された時 Permissionが必要な理由を表示して再度許可を求めたり、機能を無効にしたりします。
   } else {
      //まだ許可を求める前の時、許可を求めるダイアログを表示します。
       ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.CAMERA}, 0);

   }
}
```

Permissionがない場合このようなダイアログが表示されます。
![「写真の撮影と動画の記録を許可しますか？」](https://qiita-image-store.s3.amazonaws.com/0/17830/c0cbbbc5-dcf1-485d-8654-17db61a093bb.png)

複数のPermissionを指定していた場合は1/2のように許可するPermissionがいくつあるかも表示されます。

##Permissionが許可・拒否された時の処理を追加する##
ダイアログでPermissionが許可・拒否された時に処理を行うにはActivityあるいはFragmentのonRequestPermissionsResult()をOverrideします。
引数として、
第1引数にActivityCompat#requestPermissions()の第3引数で指定した値
第2引数にActivityCompat#requestPermissions()の第2引数で指定した値
第3引数に第2引数と1対1になるかたちでユーザーがPermmisionを許可したかどうかがintで渡されます。

後はそれに合わせて、許可された場合は後続の処理を行い、拒否された場合はボタンを無効にするとか、説得するためのメッセージを表示するなど必要な処理を行います。

カメラのPermissionが許可されたかどうかを確認する例

```java

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
   switch (requestCode) {
       case 0: { //ActivityCompat#requestPermissions()の第2引数で指定した値
           if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
               //許可された場合の処理
           }else{
               //拒否された場合の処理
           }
           break;
       }
   }
}
```

これによりRuntime Permissionに対応できます。

#Android6.0について#

Runtime PermissionはAndroidのバージョンアップ史上、最も多くの既存APKに影響を与えるバージョンアップであるように思います。
targetAPKを変更しないことで暫定回避はできるものの、例外で落ちるリスクを考えると、できるだけ早めに対応しておくべきでしょう。

この記事は [Firespeedの記事](http://firespeed.org/diary.php?diary=kenz-1821) を元に細部の調整を行ったものです。