20150909 日本androidの会9月定例講演資料
TRANSCRIPT
これだけは押さえておきたい「新しいパーミッションモデル」の勘所
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_CONTACTSandroid.permission.WRITE_CONTACTSandroid.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_CONTACTSandroid.permission.WRITE_CONTACTSandroid.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である。
ただし、その処理の中では、現在パーミッションが許可されているかは考慮されず、状態に関わらず、常にパーミッション要求のダイアログが表示される。
その要求ダイアログで、パーミッションを拒否すると結果として取り消しされてしまう。
ご清聴ありがとうございました