これだけは押さえておきたい
「新しいパーミッションモデル」の勘所
2015/9/9
塩田 明弘
はじめに
本日お話させていただくのは、Android M(6.0)から採用される、
新しいapp permissions model ”Runtime Permissions”についてです。
また、その内容は、現在の最新モデルである、
Android M Developer Preview 3に基づいてお話させていただきます。
最終的にリリースされた際には異なる動作となっている可能性があり
ますのでご了承ください。
自己紹介
 塩田 明弘
 株式会社NTTデータ所属
 メインの仕事はセキュリティ(2013/4~)
 2014/4より社内のAndroidアプリ開発者向けの
セキュリティガイドライン作成やサポートに従事
目次
1. 新パーミッションモデルの特徴
2. Permissionが必要なAPIの対応方法
3. Permissionの設定変更に関わる注意点
4. まとめ
5. その他のトピック
1. 新パーミッションモデルの特徴
1-1. 新パーミッションモデルの特徴(1/3)
インストール時ではなく、利用時に許可を求める
チェックや許可要求、結果受け取りの機能は作ってやる必要有り
→後ろのページで説明
許可を与えても後から変更が可能
1-1. 新パーミッションモデルの特徴(1/3)
インストール時ではなく、利用時に許可を求める
チェックや許可要求、結果受け取りの機能は作ってやる必要有り
→後ろのページで説明
許可を与えても後から変更が可能
Androidの抱える課題、
「すべて許可しないとインストールできない」
「許可したパーミッションを後から変更できない」
という状況の解消(ただし、一部のパーミッションのみ)
1-1. 新パーミッションモデルの特徴(2/3)
連絡帳の読取
利用時
 「連絡帳の読取」が必要な機能を使うとき、アプリは
Permissionをチェックして、不許可なら許可要求する
1-1. 新パーミッションモデルの特徴(2/3)
連絡帳の読取
利用時
 「連絡帳の読取」が必要な機能を使うとき、アプリは
Permissionをチェックして、不許可なら許可要求する
 ユーザが、設定画面から与えていた許可を取り消したり
許可を与えることが出来る
1-1. 新パーミッションモデルの特徴(2/3)
連絡帳の読取
利用時
 「連絡帳の読取」が必要な機能を使うとき、アプリは
Permissionをチェックして、不許可なら許可要求する
 ユーザが、設定画面から与えていた許可を取り消したり
許可を与えることが出来る
 許可がないまま機能を使おうとすると、
「Permission Denial」として強制終了する
当たり前!
1-1. 新パーミッションモデルの特徴(3/3)
 sharedUserIdを設定したアプリ間では、許可/不許可の設定も
共有される。(これまでもPermissionは共有されている)
連絡帳の読取
アプリA
 アプリAで許可を行うと、アプリBでも許可される。
 アプリBのManifestファイル中で宣言されていないPermissionでも、
アプリAで宣言されていれば許可されるが、アプリBの設定画面から
は変更できない。
位置情報へのアクセス
アプリB
宣言したPermission
・位置情報へのアクセス
・連絡帳の読取
宣言したPermission
・位置情報へのアクセス
1-2. 旧バージョン向けのアプリのケース(1/3)
連絡帳の読取 位置情報へのアクセス
ショートカットの作成
 インストール時にすべて許可を与えるが、設定画面で後から
取り消せる
 APIに応じた挙動で動作できないようになる→App Ops
(空の値が返る等、「パーミッションがない」とはならない)
連絡帳の読取
インストール時 インストール後
1-2. 旧バージョン向けのアプリのケース(2/3)
App Ops
 Android4.3から搭載されている隠し機能
 Permissionで与えた機能を中心に、制限をかける
 4.4.2以降では要root化
 Android Mでは、Runtime Permissionと密接に関係しつつ
別に存在している
1-2. 旧バージョン向けのアプリのケース(3/3)
 設定画面で許可を変更すると、App Opsの設定に反映される。
packeges.xml中のパーミッション自体は取り消されない。
 M向けアプリでも、許可を与えたうえでApp Opsを設定すれば、
App Opsによって制限される(要Root化)
連絡帳の読取
読み取りさせないための別機能(AppOps)が働く
実際の動き
パーミッションは与えられたまま
1-3. パーミッションの状態が書き込まれるファイル
パッケージまたはsharedUserIdごとに以下の形式で書き込まれる。
<item name=“パーミッション名” granted=“true” flags=“0”>
 targetSdkVersion=23の場
 ファイル:/data/system/users/{user_id}/runtime-permissions.xml
 上記に書き込まれるのはruntime-permissionの対象となるPermissionのみ
その他のPermissionは/data/system/packages.xmlに書き込まれる。
 許可の場合:granted=“true”でflags=“0”
 拒否の場合:granted=“false”でflags=“1”
 ダイアログを表示しない場合:granted=“false”でflags=“2”
 targetSdkVersion=22の場合
 ファイル:/data/system/packages.xml
 許可の場合:granted=“true”でflags=“0”
 拒否の場合:granted=“true”でflags=“8”
アプリの動作に対する
決定権が大きくなった
・新モデル対応の改修が必要
・Permission要求の見直し
(ユーザに受け入れられるものか)
1-4. 新パーミッションモデルの影響
ユーザー
開発者
2. Permissionが必要なAPIへの対応方法
2-1. Permissionが必要なAPIへの対応方法
 Permissionがない状態でAPIを使用→Permission Denial
 APIを使用する前に、以下の3つのステップを実行する
 この一連のステップがRuntime-permissionを実現するためには必要となる
Step1. チェック
checkSelfPermissionによる状態チェック
Step2. リクエスト
requestPermissionsによるパーミッション要求
Step3. ハンドリング
onRequestPermissionsResultによるリクエスト結果への対応
複数のパーミッションをチェックする場合を想定し、
文字列の配列を受け取り、for文等でチェックするクラスを作ってもよい
2-2. Runtime-permissionモデルの実現(Step.1 チェック)
public void showContacts(View v){
if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
// Explain to the user why we need to read the contacts
}
//ポップアップなどで要求に対する説明を書く
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
}else{
showContactsDetails();
}
}
パーミッションが許可されていると、PERMISSION_GRANTED(=0)
許可されていないとPERMISSION_DENIED(=-1)を返す
 Permissionの有無のチェックは次のAPIを用いる
 context#checkSelfPermission(String permission_name)
 Permission_nameはString型でパーミッションの名称を指定する
2-2. Runtime-permissionモデルの実現(Step.2 リクエスト)
 パーミッションがなければ、次のAPIを用いてリクエストする
 Activity#requestPermissions(String[] permissions, int requestCode)
 requestCodeを設定し、ハンドリング時のケース分けに使う
shouldShowRequestPermissionRationale(String permission)は、
過去に引数に指定したパーミッションがリクエストを拒否したことがあるか確認できる。
public void showContacts(View v){
if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
// パーミッションの必要性を説明するための文章を書く
}
//ポップアップなどで要求に対する説明を書く
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
}else{
showContactsDetails();
}
}
2-2. Runtime-permissionモデルの実現(Step.3 ハンドリング)
 次のコールバックAPIでリクエストの結果を受け取る
 Activity#onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
 リクエスト時に投げたパーミッションに対応する形で、結果が配列grantResultsに格納されている
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showContactsDetails();
} else {
// パーミッションが許可されなかったときの動作を書く
}
return;
}
// その他のリクエストコードの時の動作
}
}
リクエストに対して許可されていたら、
PERMISSION_GRANTED(=0)が格納されている
2-3. Support Libralyを使った旧バージョンOSを考慮した対応
 2-2で紹介したAPIはいずれもAPI Level23以降のもの。
 v4とv13のSupportLibraly 23を用いて、同様の機能を提供するAPIを利用できる。
・ContextCompat#checkSelfPermission(Context context, String permission)
指定したパーミッションが許可されている場合は「true」を返す。(context#checkPermissionを呼び出してるだけ)
・ActivityCompat#requestPermissions(Activity activity, String[] permissions, int requestCode)
Android 6.0未満の場合は、ActivityCompat#OnRequestPermissionsResultCallbackでコールバックメソッドを呼び出す。アプリが
指定したPermissionを持っている場合は、PERMISSION_GRANTED、持っていない場合はPERMISSION_DENIEDを返す。
・ActivityCompat#shouldShowRequestPermissionRationale(Activity activity, String permission)
Android 6.0未満の場合はfalseを返す
・PermissionChecker#checkSelfPermission(Context context, String permission)
指定したパーミッションに対して、PERMISSION_GRANTED、PERMISSION_DENIEDまたはPERMISSION_DENIED_APP_OPを返
す。 PERMISSION_DENIED_APP_OPは、AppOpsで制限されている状態を表し、targetSdkVersion<=22のアプリで権限が取り消
されている状態等の場合に返される。
・FragmentCompat#requestPermissions(Fragment fragment, String[] permissions, int requestCode)
Android 6.0未満の場合は、FragmentCompat#OnRequestPermissionsResultCallbackでコールバックメソッドを呼び出す。アプリ
が指定したPermissionを持っている場合は、PERMISSION_GRANTED、持っていない場合はPERMISSION_DENIEDを返す。
・FragmentCompat#shouldShowRequestPermissionRationale(Fragment fragment, String permission)
Android 6.0未満の場合はfalseを返す
V4 SupportLibraly
V13 SupportLibraly
3. Permissionの設定変更に関わる注意点
3-1. Permissionの変更点
 PermissionそのものもProtection Levelの変更や廃止が起き
ている。(いつも通り)
 今まであまり意味のなかったPermission Groupは、
runtime permissionで変更単位となるとともに、
dangerousが所属し、グループの廃止・追加が行われている。
パーミッション グループ パーミッション
android.permission-group.CONTACTS android.permission.READ_CONTACTS
android.permission.WRITE_CONTACTS
android.permission.GET_ACCOUNT
グループに所属していないdangerousやnormalのPermission
は変更不可=インストール時に付与される
グループの一例
3-2. グループ単位での設定変更
 パーミッションの設定変更は、AndroidManifestで宣言さ
れているグループ内のパーミッション全てに対して一律に
行われる
 設定画面からの変更でも、requestPermissionsによるアプ
リからの要求でも同じ。要求は引数にパーミッションを一
つ指定すれば、グループ全体に影響が及ぶ
<use-permission android:name=“android.permission. READ_CONTACTS”>
<use-permission android:name=“android.permission. WRITE_CONTACTS”>①AndroidManifestでの宣言
<pkg name="com.sample.sample">
<item name="android.permission. READ_CONTACTS" granted="true" flags="0" />
<item name="android.permission. WRITE_CONTACTS" granted="true" flags="0" />
</pkg>
③パーミッションの許可状況
/data/system/user/{userId}/runtime-permissions.xml
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},
REQUEST_READ_CONTACTS);②アプリ内でのパーミッション要求
3-3. Permissionがグループ単位で設定変更される事の問題点
ユーザーの認識とのズレにより、
次のような問題がとなる生じる可能性がある
1. 許可したつもりのないPermissionが許可済み
2. グループ全体で要求ダイアログが表示されない
3-4. 許可したつもりのないPermissionが許可済み(1/3)
コミュニケーション系アプリで以下の機能を想定
A) 既存ユーザを探すために、連絡帳の情報を利用
要:android.permission.READ_CONTACTS
B) サービス上のユーザを連絡帳に追加
要:android.permission.WRITE_CONTACTS
いずれもandroid.permission-group.CONTACTSに所属
パーミッション グループ パーミッション
android.permission-group.CONTACTS android.permission.READ_CONTACTS
android.permission.WRITE_CONTACTS
android.permission.GET_ACCOUNT
③パーミッショングループへの許可要求
「連絡帳の情報を使ってユーザーを探す
には、「連絡帳へのアクセス」を許可す
る必要があります。」
3-4. 許可したつもりのないPermissionが許可済み(2/3)
ユーザは機能Aだけ許可したつもりでも、機能Bも許可される
①機能A利用の要求
④パーミッショングループへの許可 ⑤パーミッショングループ
への許可の付与
②パーミッションチェック
(READ_CONTACTS)
ユーザ アプリ・端末
⑥機能Aの利用
ここでREAD_CONTACTSだけでなく、
WRITE_CONTACTSにも許可が与えられる
③パーミッショングループへの許可要求
「連絡帳の情報を使ってユーザーを探す
には、「連絡帳へのアクセス」を許可す
る必要があります。」
3-4. 許可したつもりのないPermissionが許可済み(2/3)
①機能A利用の要求
④パーミッショングループへの許可 ⑤パーミッショングループ
への許可の付与
②パーミッションチェック
(READ_CONTACTS)
ユーザ アプリ・端末
⑥機能Aの利用
⑦機能B利用の要求
⑧パーミッションチェック
(WRITE_CONTACTS)
⑨機能Bの利用
ここでREAD_CONTACTSだけでなく、
WRITE_CONTACTSにも許可が与えられる
ユーザは機能Aだけ許可したつもりでも、機能Bも許可される
3-4. 許可したつもりのないPermissionが許可済み(3/3)
 「要求された箇所の機能を使うためにパーミッションが必
要」という説明だけではなく、「パーミッション(グループ)
を許可することによる、アプリ全体への影響」の説明も必要
かも
 公式のベストプラクティスのように、チュートリアルを設け
る手もあるが、全てをその中ではカバーできない。
 現時点で許可したパーミッションによって、アプリのどの機
能が使えるようになっているのかマッピングして表示する機
能があってもよい。
3-5. グループ全体で要求ダイアログが表示されない(1/2)
 requestPermissionsでパーミッション要求された際に、
「今後は確認しない」にチェックして「許可しない」
を選ぶと、次回以降ダイアログが表示されなくなる。
 「今後は確認しない」とした箇所だけではなく、
同一パーミッショングループ全体が同じ設定になる。
 各パーミッションの状態を、「flags」で管理しており、
この値もグループ単位で設定するため(「今後は確認し
ない」はflags=2)
<pkg name="com.sample.sample">
<item name=" android.permission. READ_CONTACTS" granted=“false" flags=“2" />
<item name=" android.permission. WRITE_CONTACTS" granted=“false" flags=“2" />
</pkg>
パーミッションの許可状況
/data/system/user/{userId}/runtime-permissions.xml
3-5. グループ全体で要求ダイアログが表示されない(2/2)
 この回避(正確には軽減策)のためには、表示しないとした
ときのメッセージの作成や、復活させるための手順を示
したヘルプを設ける
 初めて利用する機能ですでに制限されてしまっている場
合には手順を表示、複数回反応がないのにボタンを押し
たらヘルプ表示などのサポートも必要
 このバランスは今後ちょうどよいところを見つけていく
しかない
4. まとめ
4. まとめ
AndroidのパーミッションモデルはAndroid Mで新しいモデルにとなり
ユーザーが決定権を持つようになった
パーミッションが必要なAPIでは、開発者がチェック・リクエスト・ハ
ンドリングを行うコードを書く必要がある。
グループ単位での設定となったことによって、うまく説明しないとユー
ザーに誤解が生じかねない。説明や補助的な機能を作り、誤解を生じな
いようにするしかない。分量のバランスはユーザーからのレスポンスを
よく見てやる必要がある。
5. その他のトピック
5-1. sharedUserIdを設定したアプリ間でパーミッションモデルが混在
 sharedUserIdを設定したアプリ間では、同じパーミッションが与えられるだけで
はなく、許可/不許可の設定も共有される。
 これは、sharedUserIdを設定したアプリに、targetSdkVersion<=22のアプリ(以下ア
プリA)とtargetSdkVersion=23のアプリ(以下アプリB)が混在していても共有される。
 22→23の順でインストールした場合は、/data/system/users/{user_id}/runtime-
permissions.xmlで管理される。
 23→22の順でインストールした場合は、/data/system/packages.xmlで管理される。
 パーミッションモデルが混在していると、共通して管理されるパーミッションに対
して、アプリ毎のバージョンに従ったパラメータ操作が行われる。
 22→23の順では、設定画面の表記などが一致しない現象が生じる(23→22も問題あり)
 アプリAで取り消しするとパラメータは「granted=“true”でflags=“8”」であるが、 アプリ
Bの設定画面から見ると、許可された状態に見える。(実態は異なる)
 アプリBで取り消しすると、アプリAの設定画面からは許可を与えられない。この状態でアプ
リBをアンインストールすると、アプリAからは二度と権限を許可できない。
 sharedUserIdが設定されたアプリをtargetSdkVersion=23としてリリースする際
は、すべてのアプリを同時にtargetSdkVersion=23にするのがお勧め。
5-2. アプリのバージョンアップ時の注意点
 アプリケーションをバージョンアップした際、
それまで与えられていたパーミッションは引き継がれる。
targetSdkVersionを22から23に変更した場合も同様。
 そのため、バージョンアップにおいては、runtime-
permissionのモデルだが、install-timeの設定が継続される。
 バージョンアップ後の初回起動時に、対応したことを知らせ、
確認を促す画面などを作ってもよいかもしれない。
 一度targetSdkVersion=23にバージョンアップすると、ど
うアプリでは、同一のパッケージとして22に戻すことがで
きない。そのため、その他の要因で22に戻してリリースす
ることがある場合、再インストールする必要がある。
5-3. requestPermissionsは常に要求ダイアログを出す
 Activity#requestPermissionsは、パーミッションを要求するAPIで
ある。
 ただし、その処理の中では、現在パーミッションが許可されているか
は考慮されず、状態に関わらず、常にパーミッション要求のダイアロ
グが表示される。
 その要求ダイアログで、パーミッションを拒否すると結果として取り
消しされてしまう。
ご清聴ありがとうございました

20150909 日本androidの会9月定例講演資料