Runtime(Dangerous)Permissionの取得方法についてまとめます。
現在、Runtime Permissoinの取得方法は2つあります。
ここで紹介するのはRuntime Permission(API≧23)が登場した当初から存在している方法です。
この方法は、リクエスト(Permissionの申請)の結果を共用のコールバックAppCompatActivity#onRequestPermissionsResult( )で受け取ります。複数のリクエスト行うと、複数の結果を一つのコールバックで受けることになり、コールバック内でRequestCodeによる分岐が必要になります。
つまり、「開発者がリクエストを管理」しなければなりません。
Permissionの詳細は「Permissionとその一覧」を参照してください。
Permissonの取得
AndroidManifestファイルへ宣言
Nromal Permissionと同様に、AndroidManifest.xmlへ必要なPermissionを列記する必要があります。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.myapp"> <!-- 以下はNormal Permission --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- 以下はDangerous Permission --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <application ...> ... </application> </manifest>
これだけでは、必要なPermissionを宣言しただけで、取得は出来ていません。
取得のワークフロー
Permissionへ紐づいた機能を実行する時点で、アプリ上から取得を行います。
この「アプリ上から取得する」手順が、ドキュメントのワークフローに紹介されています。
ワークフローに従うと次のようになります。
private const val PERMISSION = Manifest.permission.ACCESS_FINE_LOCATION private const val REQUEST_ID0 = 222 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { ... findViewById<Button>(R.id.btnStart).setOnClickListener { val _PermissionState = ActivityCompat.checkSelfPermission(this, PERMISSION) when { _PermissionState == PackageManager.PERMISSION_GRANTED -> { /* Permissionに紐づいた機能を実行 */ } ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION) -> { AlertDialog.Builder(this).apply { setTitle(R.string.permission_dialog_title) setMessage(R.string.permission_dialog_text) setPositiveButton("OK") { dialog, which -> ActivityCompat.requestPermissions( this@MainActivity, arrayOf(PERMISSION), REQUEST_ID0) } }.create().show() } else -> { ActivityCompat.requestPermissions(this, arrayOf(PERMISSION), REQUEST_ID0) } } } } override fun onRequestPermissionsResult( // コールバック requestCode: Int, permissions: Array<String>, grantResults: IntArray ) { if(grantResults.isEmpty()) throw RuntimeException(getString(R.string.permission_exception_mesg)) when (requestCode) { REQUEST_ID0 -> { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { /* Permissionに紐づいた機能を実行 */ } else { if (! ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION)) { showPermissionSnackbar() } } } else -> { super.onRequestPermissionsResult(requestCode, permissions, grantResults) } } } ... }
ポイント(1):Permissionの状態チェック
Permissionに紐づいた機能(関数またはメソッド)を実行する前に、状態(許可・拒否)のチェックが必要です。
上記の関数またはメソッドは@RequiresPermissionアノテーションが付加されています。チェックを行わない場合、構文チェックでエラーになります。
ActivityCompat#checkSelfPermission( )が状態を返します。
ポイント(2):Permissionの取得根拠を説明
Permissionを申請する前に、取得する根拠の説明が推奨されています。
前回が拒否だった場合、Permissionの必要性についての理解が不十分であると考えられるためです。
前回が拒否の時に、ActivityCompat#shouldShowRequestPermissionRationale( )はtrueを返します。
ポイント(3):コールバックに結果が返る
Permissionを申請するとダイアログが開き、ユーザに許可・拒否の判定を求めます。
その結果はコールバックの引数に返ります。
ActivityCompat#requestPermissions( )で申請します。
AppCompatActivity#onRequestPermissionsResult( )がコールバックです。
ポイント(4):設定アプリへ誘導
ユーザが何度も拒否している中で「次回から表示しない」を選択すると、拒否状態が固定化されてダイアログが開かなくなります。
これにより、ユーザとアプリのコミュニケーションが途切れて、ユーザが状況を理解できなくなる可能性があります。
従って、拒否の影響でアプリの機能が制限されていることをユーザに知らせます。
これはユーザの操作をブロックしない方法(例:Snackbar)で行います。
また、固定化された判定が変更できるように、Setteingsアプリへ誘導することが推奨されています。
ポイント(その他)
Permission関連の関数(メソッド)はActivityCompat配下のものを使用します。
API<23世代(全てInstall-time Permission)の端末と後方互換性を保つためです。
許可のフロー
Permissionが許可される動作です。
拒否のフロー
Permissionが拒否される動作です。
設定アプリへ誘導する方法
Permissionの拒否の影響で、アプリの機能が制限されていることを、Snackbarでユーザに知らせます。
そのSnackbarのアクションを使ってSettingアプリを起動しています。
fun showPermissionSnackbar() { Snackbar.make( findViewById(android.R.id.content), R.string.permission_snackbar_text, Snackbar.LENGTH_LONG ).apply { setBackgroundTint(Color.LTGRAY) setTextColor(Color.BLACK) setActionTextColor(Color.RED) setAction(R.string.permission_snackbar_action) { val _intent = Intent( Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", packageName, null) ) startActivity(_intent) } }.show() }
Snackbarは一定時間の経過後に閉じます。よって、ユーザの操作をブロックしません。
Permission情報の所在
アプリが取得したPermission情報は次の場所に格納されています。
※API≧30で変更になっています。
API<30
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?> <runtime-permissions version="7" ...> ... <pkg name="パッケージ名"> <item name="android.permission.ACCESS_FINE_LOCATION" granted="true" flags="0" /> <item name="android.permission.ACCESS_COARSE_LOCATION" granted="true" flags="0" /> </pkg> ... </runtime-permissions>
API≧30
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?> <runtime-permissions version="8" ...> ... <pkg name="パッケージ名"> <permission name="android.permission.ACCESS_FINE_LOCATION" granted="true" flags="301" /> <permission name="android.permission.ACCESS_COARSE_LOCATION" granted="true" flags="301" /> </pkg> ... </runtime-permissions>
Permission情報の出力
アプリが取得したPermission情報はターミナルから adb shell dumpsys コマンドを使って出力できます。
> adb shell dumpsys package パッケージ名
...
Packages:
Package [パッケージ名] (xxxxxxx):
...
requested permissions:
android.permission.ACCESS_COARSE_LOCATION
android.permission.ACCESS_FINE_LOCATION
android.permission.INTERNET
android.permission.FOREGROUND_SERVICE
install permissions:
android.permission.FOREGROUND_SERVICE: granted=true
android.permission.INTERNET: granted=true
User 0: ...
gids=[3002, 3003]
runtime permissions:
android.permission.ACCESS_FINE_LOCATION: granted=true, flags=[...]
android.permission.ACCESS_COARSE_LOCATION: granted=true, flags=[...]
...
注意1:バックグラウンドで処理は進む
マイアプリがPermissionを申請すると、ダイアログが開きます。
このダイアログは、システムがPackageInstallerアプリに依頼して開かせたものです。
マイアプリとPackageInstallerアプリは異なるアプリです。
この結果、マイアプリ画面の上に、PackageInstallerアプリのダイアログが表示されることになります。
[ ダイアログ表示前 ] > adb shell 'dumpsys activity activities | grep -B 1 "Run #[0-9]*:"' TaskRecord{9279bc2 #106 A=マイアプリパッケージ名 U=0 StackId=1 sz=1} Run #0: ActivityRecord{78e2195 u0 マイアプリパッケージ名/.MainActivity t106} [ ダイアログ表示後 ] > adb shell 'dumpsys activity activities | grep -B 1 "Run #[0-9]*:"' TaskRecord{9279bc2 #106 A=マイアプリパッケージ名 U=0 StackId=1 sz=2} Run #1: ActivityRecord{d31e4d4 u0 com.google.android.packageinstaller/com.android.packageinstaller.permission.ui.GrantPermissionsActivity t106} Run #0: ActivityRecord{78e2195 u0 マイアプリパッケージ名/.MainActivity t106}
マイアプリはバックグラウンドへ遷移し、ActivityはonPause( )を処理します。ダイアログが閉じれば、再びフォアグラウンドへ遷移し、ActivityはonResume( )を処理します。
このように、ダイアログが表示されている最中も、マイアプリのActivityはバックグラウンドで処理を続けています。
注意2:Permissionを含むPermission
同一Group内において、Permissionを含むPermissionがあります。
例えば、次のようなPermissonです。
READ_EXTERNAL_STORAGE ⊂ WRITE_EXTERNAL_STORAGE
ACCESS_COARSE_LOCATION ⊂ ACCESS_FINE_LOCATION
右辺のPermissionを取得すると、おのずと左辺のPersmissionも取得されます。
関連記事: