CheckSelfPermissionの注意事項
Android 6.0(マシュマロ)でランタイムパミッションが導入され、ランタイムパミッション周りのメソッドが新規追加されました。主に以下のメソッドに対して、アンドキュメント動作やサポートライブラリを含めると色々と注意事項があるので、個々に解説したいと思っております。
- checkSelfPermission()
- shouldShowRequestPermissionRationale()
- requestPermissions()
CheckSelfPermission
int checkSelfPermission (String permission)
アプリケーションが指定したパミッションを持っているかを判断するメソッド
- パーミッションが取得済の場合は、PERMISSION_GRANTED
- パーミッションが未取得の場合は、PERMISSION_DENIEDを返す。
- 互換モードの時(設定→拒否)の場合は、PERMISSION_DENIED_APP_OPを返す。(PermissionChecker.checkSelfPermissionのみ)
(※ 互換モードに関しては、ランタイムパミッションの互換性(1)を参照してください)
またcheckSelfPermissionはクラス違いで3つ存在しそれぞれ使い分けをします。
- Content.checkSelfPermission (API Level23)
- ContextCompat.checkSelfPermission (サポートライブラリ)
- PermissionChecker.checkSelfPermission (サポートライブラリ)
// Here, thisActivity is the current activity if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { // Should we show an explanation? if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity, Manifest.permission.READ_CONTACTS)) { // Show an expanation to the user *asynchronously* -- don't block // this thread waiting for the user's response! After the user // sees the explanation, try again to request the permission. } else { // No explanation needed, we can request the permission. ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, MY_PERMISSIONS_REQUEST_READ_CONTACTS); // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an // app-defined int constant. The callback method gets the // result of the request. } }
プロテクションレベルNormalの動き
通常はプロテクションレベルDangerousのパミッションを指定して、アプリケーションが既に権限を取得しているかを確認します。プロテクションレベルNormalのパミッションを入れる事はありませんが、確認をしてみました(想定通りの動作をしました)。
- ManifestにプロテクションレベルNormalのパーミッションが利用宣言されている場合、アプリインストール時にそのパーミッションは取得しているので、PERMISSION_GRANTEDを返す。
- Manifestに利用宣言されていない、プロテクションレベルNormalのパーミッションを指定した場合、権限は持っていないので、PERMISSION_DENIEDを返す。
注)プロテクションレベルノーマルのパーミッションは、AppOpsで権限はく奪できない。
※)AppOpsといっていいかは微妙ですが、端末の設定からアプリケーション単位でパミッションをはく奪したり、許可する機能(呼びやすい名前があるといいのですが)
checkSelfPermissionの注意事項
chekSelfPermissionはパーミッションを引数に指定して、そのパーミッションを取得しているかを判断するシンプルなメソッドであるが、互換モードの時は注意が必要です。
※互換モードとは、targetSdkVersionが23未満に設定されており、AndroidOS6.0以上のデバイスでアプリが動いており、AppOpで権限をはく奪された時を表す。
互換モードの時、
Compat.checkSelfPermissionはPERMISSION_GRANTEDを返します。
PermissionCheker.checkSelfPermissionはPERMISSION_DENIED_APP_OPを返します。
つまりAppOpsにより権限がはく奪され権限がないのにも関わらずCompatは、権限を持っていると返します。
また、Contextクラスには、Android6.0で追加されたcheckSelfPermission以外にもcheckPermission等多くのパーミッションチェックメソッドが存在しますが、これはContext.checkSelfPermissionと同じく、権限がはく奪されているのにも関わらず権限を取得していると判断します。
もちろん互換モードではなく、targetSdkVesionを23以上にした時はAppOpsの値を反映して、PERMISSION_DENIEDを返します。(PERMISSION_DENIED_APP_OPは返さない)
この仕様は以下の思想なのではないかと思います。
- サポートライブラリを使って、ランタイムパーミッションに対応したアプリを作るのであれば、targetSdkは23以上にする事が当たり前。ランタイムパミッション使うなら23以上にしろ(23以上にしないとそもそもランタイムパーミッションモデルで動作する事はないので)
- 互換モードでテストしたら、不具合が発生した。でもランタイムパーミッションにフル対応にするには時間もなく取りあえずハングアップしないように対応したいときは、checkPermissionクラスを使ってチェックをする。
PermissionChecker.checkSelPermissionメソッドを使用すれば、取りあえず問題はありませんので、Compat.checkSelfPermissionは取りあえず使わないと覚えてもいいかもしれません。
どちらにせよ、API Level 1から存在するcheckPermison系を使っているアプリケーション(多分サービスとか、色々複雑なアプリでしょうが)は、API Level23にしないとうまく動作しない事が考えられますので、早々にランタイムパーミッションに対応する事をお勧めします。
感想
この互換モードの時注意しなければいけないのって、バグなのではないかとおもいつつ、仕様としたらどんな思想なんだろうと推測してみました。
丁度、先日サポートライブラリ―が更新され、もしかしたら、動きが代わっているのでは?と思い再度確認してみましたが、以前と結果は同じで、残念ながら仕様推測は正しいようです。
まぁ、checkSelfPermissionって結局checkPermissionメソッドを使っていて、沢山あるcheckPermission系全て互換モードでちゃんと動かすのめんどくさかったのかなーとも思ってます。
RiskFinderでの検知
RiskFinderのマシュマロ対応バージョン(まだ出ていません)では、もちろん今回の注意事項に関しては検出を行います。
- 検知条件
- targetSdkVersionが23未満でCompat.checSelfPermissionを使用
- タイトル
- 互換モードでのCompat.checkSelfPermissionの使用
- 検知理由:
- 「互換モード(targetSdkVersionの値が23未満のアプリが、Android6.0以上の端末で動作しており、OSの設定→アプリ→アプリ情報から、特定のパミッションを不許可にした時)にContextCompat.checkSelfPermissionの戻り値は、PERMISSION_GRANTED (許可)を返します。」
- 解決方法
- PermissionChecker.checkSelfPermissionを使用してください(PERMISSION_DENIED_APP_OPを返します)。または、targetSdkVersionを23以上にしてください。
といった感じです。