〈kotlin〉Android studio使い方メモ11:Runtime Permission

金宏和實さんの「作ればわかる!Androidプログラミング kotlin対応」を参考に
Android studioでkotlinを使ってAndroidアプリ作成。

Runtime Permissionとは

情報にアクセスする際にユーザーに許可を求め、同意されるとアプリにアクセス権が与えられる。
Android5.0以前はアプリインストール時に一括して許可を求めていたが、
Android6.0(API23)以降は危険性の高いパーミッションについては
インストール時ではなく実行時に個別に許可を求めることとなった。
これがRuntime Permission。

Runtime Permissionの対象パーミッション

Protection levelが「dangerous」となっているものが対象。

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 電話の状態/コール
READ_CALL_LOG/WRITE_CALL_LOG 通信履歴の取得/書き込み
ADD_VOICEMAIL ボイスメールの追加
USE_SIP SIPの使用
PROCESS_OUTGOING_CALLS 通話発信時のintent補足
BODY_SENSER 身体センサーの利用
SEND_SMS/RECEIVE_SMS/READ_SMS SMSの送信/受信/読み込み
RECEIVE_WAP_PUSH WAPPUSHの受信
RECEIVE_MMS MMSの受信
READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE 外部ストレージの読み込み/書き出し

実装

必要なPermissionを指定

AndroidManifest.xmlに必要なパーミッションを記述する。
今回は詳細な位置情報を取得するACCESS_FINE_LOCATIONを指定。

<manifest>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>
パーミッションの確認

既に指定したパーミッションを持っているかどうかcheckSelfPermission()メソッドで確認する。

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
    private val REQUEST_CODE = 1
    
    private fun checkPermission() {
        if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION)
        == PackageManager.PERMISSION_GRANTED){
            //処理に進む
        }else{
            requestLocationPermission()
        }
    }

checkSelfPermission()では、既に許可されていればPERMISSION_GRANTED、
許可されていなければPERMISSION_DENIEDが返ってくる。
PERMISSION_GRANTEDが返ってこれば実行したい処理に進み、
もしPERMISSION_DENIEDであれば、パーミッションを求める手順に進む。

パーミッションを求める

requestsPermission()メソッドで許可を求めるメッセージを表示する。
これと合わせ、shouldShowRequestPermissionRationale() メソッドを用いて、
ユーザーが既に許可の選択をしたことがあるか(一度拒否されているのか、初めて許可を求めるのか)を確認する。
これがtrue(拒否されたことがある)ならば、
許可の必要性を説明するメッセージを出すなど追加のアクションを設定すると良い。

private fun requestLocationPermission() {
        //一度許可を求めたことがあって拒否されている場合
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                android.Manifest.permission.ACCESS_FINE_LOCATION)){
            ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.
            permission.ACCESS_FINE_LOCATION),  REQUEST_CODE)
        }else{
        //まだ許可を求めていない場合
            ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.
            permission.ACCESS_FINE_LOCATION), REQUEST_CODE)
        }
    }

shouldShowRequestPermissionRationale()メソッドの返り値は、
一度拒否されて「今後表示しない」が選択されてない場合にtrue、
一度拒否されて「今後表示しない」が選択されている場合と、
初めて許可を求める場合にはfalseを返す。
「今後表示しない」が選択されている場合は、
その後のrequestPermissions()メソッドがユーザーにリクエストダイアログを出さずに
PERMISSION_DENIEDを返すみたいだが、公式の記載が見つけられず、
実際のところどうなっているのかは今後検証してみることにする。

パーミッションの結果の確認

onRequestPermissionResult()メソッドでユーザーが選択した結果を受け取る。

override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode){
            REQUEST_CODE ->{
                if (permissions.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                    //処理に進む
                }else{
                    val toast = Toast.makeText(this, "現在位置は表示できません", Toast.LENGTH_LONG)
                    toast.show()
                }
            }
        }

onRequestPermissionResult()メソッドは第一引数にリクエストコードを渡す。
リクエストコードはrequestPermissions()メソッドの第三引数で渡した固定の整数で、
この定数を元にリクエストしたパーミッションの情報を管理している。


本書ではrequestPermissions / onRequestPermissionsResultを使用していたが、
ActivityResultContractsを使うとリクエストコードなしで実装できそうだった。

参考https://developer.android.com/training/permissions/requesting?hl=ja
サンプルコードhttps://github.com/android/permissions-samples