Androidのパーミッションとは?
Androidのパーミッション(権限)とは、端末の中の色々なデータに対してアクセスを保護してプライバシーを保護するたのものです。昨今、スマホは個人情報の塊のようなものです。
- アドレスブック(特に電話番号)
- SMSのメッセージ
- 何らかのログインID、パスワード
- 等など・・・
これらが自由に参照可能だと、勝手に情報が盗まれて見ず知らずのサイトに送られてしまいます。それらを制限するのがパーミッション(権限)です
パーミッションの種類
パーミッションにどんなものがあるかですが、公式サイトではあまりきちんとまとまったのがないようです。唯一、APIリファレンスに説明があります。(結構、たくさんある)
Manifest.permission
ここにStringの定数として定義されています。例えば、ネットワークにアクセスするパーミッションであれば
Constant Value: "android.permission.INTERNET"
と言ったように定義されています。
パーミッションのプロテクションレベル
たくさんあるパーミッションの中で、その「危険性」に応じて「プロテクションレベル」で分類されています。上のネットワークにアクセスするパーミッションであれば
Protection level: normal
Constant Value: "android.permission.INTERNET"
といった具合に分類されています。この「プロテクションレベル」ですが、これも結構な種類があって、公式ページでは
protectionLevel
に定義されています。この中でも使用頻度が高いものをいくつか紹介すると、
プロテクションレベル | 説明 | 取得タイミング | 許可/拒否の変更 |
---|---|---|---|
normal | 低リスクのアクセス許可 | インストール時 | 不可 |
dangerous | 高リスクのアクセス許可 | 実行時 | 可 |
signature | 要求元/先が同じ証明書で署名の場合許可 | インストール時 | 不可 |
以上のように、低リスクな許可はアプリのインストール時に設定され、ユーザに許可/拒否を聞かれることはありません。インストール時に設定れるので、あとから許可/拒否を変更することもできません。インストール時にアプリ(Manifest)で設定されている通りに設定されます。
それに対して、高リスクな許可は、実行時にユーザに許可/拒否を確認します。ユーザが許可すればアクセスは許可されますが、拒否すればアクセスできません。許可/拒否は後からアプリの設定で変更することができます。
ちょっと、変わっているのがsignature。これはあるアプリで登録したデータを他のアプリで参照/変更するような場合に使います。例えばアドレスブックのメールアドレスをメールアプリで使う場合等がそれに相当します。署名情報が一致していれば許可されます。署名が一致しているかどうかなので、normalと同じ様に、インストール時に設定され、後から許可/拒否の変更は不可です。
パーミッショングループ
dangerousのパーミッションの中でもある程度同時に使うようなことが想定されているものは、パーミッショングループとして定義されています。公式ホームページでは
Manifest.permission_group
例えば、位置情報(GPS)を許可するパーミッショングループLOCATIONであれば、
LOCATION | ACCESS_FINE_LOCATION | 大まかな地理的位置情報の取得 |
ACCESS_COARSE_LOCATION | 正確な地理的位置情報の取得 | |
ACCESS_BACKGROUND_LOCATION | バックグラウンドで位置情報の取得 |
このパーミッショングループの使い方なんですが、パーミッショングループで指定してリクエストするとまとめてリクエストされるのかな?と思ったらそうではなく、公式ホームページでは
Android での権限
権限グループは、アプリが密接に関連する権限をリクエストしたときに、ユーザーに表示されるシステム ダイアログの数を最小限に抑えるために役立ちます。アプリに権限を付与するように求めるプロンプトがユーザーに表示された場合、同じグループに属する権限が同じインターフェースに表示されます。ただし、権限は予告なくグループを変更することがあるため、特定の権限が必ずしも他の権限とグループ化されているわけではありません。
どうやら、パーミッション許可/拒否のダイアログのGUIが、グループが同じであれば同じものが表示される。ただ、見かけ上の問題だけのようです。
パーミッションを必要とするアプリのサンプル
パーミッションを必要とするアプリのサンプルとして、以下の通り、プロテクションレベルがnormalがひとつ、dangerousがひとつ、パーミッショングループがひとつの例を作ってみたいと思います。
グループ | パーミッション | プロテクションレベル |
---|---|---|
- | CAMERA | dangerous |
- | INTERNET | normal |
LOCATION | ACCESS_FINE_LOCATION | dangerous |
ACCESS_COARSE_LOCATION | dangerous | |
ACCESS_BACKGROUND_LOCATION | dangerous |
Manifestにリクエストするパーミッションを定義します。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> -->
・・・
ここでハマったのが、ACCESS_BACKGROUND_LOCATIONを付けると、パーミッションの許可/拒否のダイアログが表示されず、期待通り動かないことに気が付きました。ACCESS_BACKGROUND_LOCATIONについては別途、説明します。
要求するパーミッションを定義します。android.permission.INTERNETはインストール時に既に付与されているパーミッションなので、ここで要求する必要はありません。
companion object {
/** リクエストコード(値はなんでもいい) */
private const val REQUEST_CODE_PERMISSIONS = 10
/** リクエストするパーミッション */
private val REQUIRED_PERMISSIONS =
arrayOf (
Manifest.permission.CAMERA,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
// Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
}
onCreateで、既にパーミッションが全て付与されていれば何もしない。ひとつでも付与されていないものがあれば要求します。要求は配列で複数まとめて1回でできます。
override fun onCreate(savedInstanceState: Bundle?) {
・・・

if (allPermissionsGranted()) {
Toast.makeText(this,"既にパーミッションが許可されています", Toast.LENGTH_LONG).show()
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
)
}
}
全てのパーミッションが付与されているかどうかのチェックはこのように行います。
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(
baseContext, it) == PackageManager.PERMISSION_GRANTED
}
最後に、パーミッションを要求した結果のコールバックを定義します。このコールバックはrequestPermissionsの呼び出し毎なので複数のパーミッションを1回で呼び出せば、このコールバックも1回です。
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
Toast.makeText(this,"パーミッションが許可されました。", Toast.LENGTH_LONG).show()
} else {
Toast.makeText(this,"パーミッションが許可されませんでした。(-_-;)", Toast.LENGTH_LONG).show()
finish()
}
}
}
アプリを実行してみる
アプリを実行すると、このようにパーミッションを許可/拒否するダイアログがパーミッション毎に出ます。
ACCESS_FINE_LOCATIONと、ACCESS_COARSE_LOCATIONはまとめて1回しか表示されません。
全てのパーミッションが許可されました。
アプリの設定で権限を見てみます。
カメラのパーミッション。
位置情報のパーミッション。
次回以降、起動すると既にパーミッションが許可されています。
ACCESS_BACKGROUND_LOCATIONを付けると何故期待通りにならなかったのか?
ACCESS_BACKGROUND_LOCATIONはAndroid Q(10、API29)で追加になっています。
その後、Android R(11、API 30)で更に変更になっています。
Android 位置情報の許諾設定について
おそらくAndroid Q(10、API29)で、ACCESS_FINE_LOCATION と ACCESS_COARSE_LOCATION のどちらかと同時に
ACCESS_BACKGROUND_LOCATION を指定すると、パーミッションの許可/拒否のダイアログに「常に許可」というのが出てくるハズです。この「常に」がバックグランドも許可していることを意味するらしいです。
で、その後Android R(11、API 30)で、更に変更され、ACCESS_FINE_LOCATION と ACCESS_BACKGROUND_LOCATION を同時にリクエストしたら アプリがクラッシュする ように変更されているようです。(何故だ?)
なので、Android R(11、API 30)以上でビルドしていたので期待通りに動いていなかったようです。
最後に
完成形はgitHubに置きました