Android6.0のPermission対応をtargetSdkVersion23未満で行うときの注意点まとめ

Andorid6.0 (Marshmallow)から追加されたアプリの許可(Runtime Permissions)への対応をtargetSdkVersion23未満（=22以下）で行うときの注意点をまとめます。

![targetSdkVersion23lessThan.png](https://qiita-image-store.s3.amazonaws.com/0/34488/5e669cfe-c0a7-1995-9e42-a17cda3ee04e.png)

#メリット
targetSdkVersionを23にせず、23未満にしたままで対応することのメリットは以下です。
・インストール時にアプリが必要な許可が全て許可されている
　tagetSdkVersionを23にするとアプリが必要な許可が許可されていないため、許可ダイアログを表示してユーザに許可してもらう必要があります。
・targetSdkVersionを上げたことによる不具合発生の阻止

#Permissionsへ対応する際の注意
targetSdkVersionを23未満に設定している場合でもcompileSdkVersionを23に設定することで、Permissionに必要なメソッドを使用可能です。
しかし、以下に挙げる注意点があります。

##①Context.checkSelfPermission(String permission)が常にtrueを返してしまう
各Permissionが許可されているかを確認するためのメソッドcheckSelfPermission(String permission)が、許可されていないときでもtrueを返してきます。
↓
targetSdkVersion23未満の場合は、PermissionChecker.checkSelfPermission(Context context, String permission)を使用すれば適切な結果を返してくれます。

##②onRequestPermissionsResult(..., int[] grantResults)のgrantResults が常にPERMISSION_GRANTEDを返してしまう
許可がないときにユーザーに許可ダイアログを表示して「許可」または「許可しない」を選択した結果を受け取るメソッド onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) のpermissionsで求めた許可とgrantResultsでどちらを選択したかを受け取りますが、
targetSdkVersion23未満だと許可ダイアログで「許可しない」を選択した場合でもgrantResultsがPERMISSION_GRANTEDを返します。
↓
targetSdkVersion23未満の場合は、onRequestPermissionsResult(...)内で①のPermissionChecker.checkSelfPermission(...)を使用して、許可が得られたかを確認してください。
「許可」が選択されていれば、PermissionChecker.checkSelfPermission(...)がtrueを返すように変更されています。
（targetSdkVersion23の場合は適切なgrantResultを返します。）

##③許可ダイアログで「許可」を選択したときにアプリが再起動される
requestPermissions(String[] permissions, int requestCode) を使用して許可ダイアログを表示した後に、許可されていないPermissionに対して「許可」を選択したときにアプリが再起動されます。
許可変更時のシステムログ：
I/ActivityManager: Killing *** : Permission related app op changed

設定アプリ->アプリ->対象のアプリ->アプリの権限で許可状態を変更したときも同様に再起動されます。しかし、許可ダイアログで許可変更したときに再起動されるのはtargetSdkVersion23未満のときのみです。
（targetSdkVersion23の場合は再起動されません。)

↓

許可ダイアログを表示せず、設定アプリのあなたのアプリの画面を表示して、ユーザーに許可を変更してもらう方法もあります。
あなたのアプリの「アプリの権限」画面に遷移させることはできません。

```java:Java

Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:あなたのアプリのパッケージ名"));
startActivity(intent);

```


##④許可ダイアログでストレージの許可変更して再起動された状態ではストレージにアクセスできない
③で述べた再起動を「ストレージ」のPermissionに対する許可ダイアログを表示して発生させると、
PermissionChecker.checkSelfPermission(...)はtrueを返しますが、
実際に「ストレージ」の許可が必要なEnvironment.getExternalStorageDirectory()などで、
File.canWrite()を行うとfalseを返します。
また、実際にアクセスしようとすると以下の例外を吐きます。

W/System.err: java.io.IOException: open failed: EACCES (Permission denied)
W/System.err: Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
W/System.err:     at libcore.io.Posix.open(Native Method)
W/System.err:     at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186)
W/System.err:     at java.io.File.createNewFile(File.java:932)
W/System.err: 	... 3  more

この状態でマルチタスク画面などからアプリをキルして再起動すれば、正常にアクセスできるようになります。
また、設定アプリから許可状態を変更して再起動される場合には、この現象は発生しません。

（targetSdkVersion23の場合は再起動されませんし、許可ダイアログで選択直後でもストレージに正常にアクセスできます。)

#まとめ
targetSdkVersion23未満では一部制限があるようですので、それを踏まえて実装する必要があります。
特に④のケースなどではユーザーに再起動を促すなどの対応が必要になるかもしれません。

#参考
http://stackoverflow.com/questions/33407250/checkselfpermission-method-is-not-working-in-targetsdkversion-22