android6.0 runtimepermissionの実装と注意点
TRANSCRIPT
Android 6.0 RuntimePermissionの実装と注意点
2015/11/14 GDG Kobe
Android6.0 Marshmallow勉強会
self-introduction
twitter @KatsukiNakatani
Facebook Katsuki.Nakataniお仕事大阪のSIerでインフラ(サーバーとかルーターとか。。。)のエンジニアをしてます
個人でアプリ作ってますAndroid Windows Store
中谷 克紀名前
8みなさんこの数字をご存知ですか?
そう!今年でAndroidは8歳です
Android6.0 Marshmallowこの8年間、StoreInstalledPermissionだったモデルが変革の時を迎えました。
新しい権限モデルRuntimePermissionです
今日はそのRuntimePermissionの実装と私自身はまったところなどを交えて 今後皆様が実装する際の手助けとなれば幸いです
RuntimePermissionとは?Android 6.0(SDK 23)で実装された、実行時許可ベースの権限モデル
前段Permissionって皆さんご存知ですよね?<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.miruker.mpermission"> <uses-permission android:name="android.permission.CALL_PHONE" android:required="false" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
端末内の特定のセンサーやデータにアクセスするためにアプリケーションごとに 付与するもの
今までの使い方宣言するのは下記のファイルに記載すればOKAndroidManifest.xml
権限の付与について
ユーザーはGooglePlayからアプリのインストール時に権限を確認し、 インストール=そのアプリに該当の権限をすべて付与する
開発者 ・・・必要な権限をManifestに記載する ユーザー・・・インストール時に確認するだけ
とてもシンプルで両者にとってWIN-WINな関係
だったはずですが。。。
ユーザー目線で見ると2011年ミログが公開したアプリでユーザの同意を得る前にサーバにデータを送信
2012年全国電話帳というアプリで端末内電話帳データをサーバに送信
2013年Simejiが変換文字列を自動的にサーバに送信
?でもシンプルなモデルだからわかりやすいってさっき話してたよね? なぜこんなことが起こるんだろう
ユーザーはインストール時にそこまでパーミッションをみてない
アプリによる情報流出の被害者が後を絶たない
開発者目線では?
全然バージョンアップされてない
理由・・・Permission追加時は自動更新されず、ユーザの許諾アクションが必要 ユーザが許諾する際、追加の場合には特になぜ追加したか納得しないと更新しない傾向に有る
Manifestに記載するだけでOKやし、権限なかったらExceptionで落ちるからテストでもわかるし、シンプルで楽やん?
また、それら以外に、インストール時にパーミッションを求めることで、どういう機能でPermissionが 必要なのかがわかりにくい側面があり、インストールがためらわれる面もあった
RuntimePermission!そこで登場したのがRuntimePermissionです
インストール・更新時(自動含む)にユーザにPermissionの許諾は求めません
実行時必要なときに必要な分だけ、Permissonを取得して実行します
とまた、その前に
RuntimePermissionの動作は、build.gradleに指定するTargetSDKのバージョンで変わりますTargetSDK/権限 端末のOS 許諾の不要なパーミッション 許諾が必要なパーミッション
236.0
インストール時に付与 付与されない<23 インストール時に付与 インストール時に付与- 6.0未満 インストール時に付与 インストール時に付与
RuntimePermission対応を実施するためにはTargetSDKのバージョンを23にしましょう
許諾の必要なパーミッションgroup:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS permission:android.permission.GET_ACCOUNTS permission:android.permission.READ_CONTACTS
group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG permission:android.permission.READ_PHONE_STATE permission:android.permission.CALL_PHONE permission:android.permission.WRITE_CALL_LOG permission:android.permission.USE_SIP permission:android.permission.PROCESS_OUTGOING_CALLS permission:com.android.voicemail.permission.ADD_VOICEMAIL
group:android.permission-group.CALENDAR permission:android.permission.READ_CALENDAR permission:android.permission.WRITE_CALENDAR
group:android.permission-group.CAMERA permission:android.permission.CAMERA
group:android.permission-group.SENSORS permission:android.permission.BODY_SENSORS permission:android.permission.USE_FINGERPRINT
group:android.permission-group.LOCATION permission:android.permission.ACCESS_FINE_LOCATION permission:android.permission.ACCESS_COARSE_LOCATION
group:android.permission-group.STORAGE permission:android.permission.READ_EXTERNAL_STORAGE permission:android.permission.WRITE_EXTERNAL_STORAGE
group:android.permission-group.MICROPHONE permission:android.permission.RECORD_AUDIO
group:android.permission-group.SMS
permission:android.permission.READ_SMS permission:android.permission.RECEIVE_WAP_PUSH permission:android.permission.RECEIVE_MMS permission:android.permission.RECEIVE_SMS permission:android.permission.SEND_SMS permission:android.permission.READ_CELL_BROADCASTS
基本的にProtectionLevelがDANGEROUSなパーミッションです ※FingerPrintはNormalなので注意が必要です!
※Auto用がもう少しあるようですが、今回は省いています
独自パーミッションも対象となります<permission android:name="com.test.permission" android:label="testPermission" android:description="@string/description" android:protectionLevel="dangerous" /> <uses-permission android:name="com.test.permission"/>
dangerousのレベルを指定した場合に対象となります
RuntimePermissionで許可対象のまとめ
PermissionGroupに所属するパーミッションまたは、 所属しないがProtectionLevelがdangerousなパーミッションが対象
逆に言うと、それ以外のインターネットアクセスなどは ユーザーに意識することなくアプリに権限を付与することが可能
これとても嬉しい!!
Google Playでの動作
TargetSDKが22未満 TargetSDKが23
インストール時にダイアログが表示 インストール時にダイアログが表示されない
[注意!]Android6.0端末&TargetSDK 23未満TargetSDKが23未満のアプリでも、6.0では後から 設定画面でパーミッションを剥奪することが可能です
ただし、外すとPermission Denialで強制終了したり、空のデータが返って来たりします
警告はしてくれます
TargetSDK 23で実装しよう!
Permission WorkflowCameraパーミッションが許可されているかチェック
CameraViewを表示
一度パーミッション要求を拒否しているかチェックYES
NO
パーミッション要求
YES
今後は確認しないがONになっている
例:ボタンをタップしたら、カメラViewが起動します
NO YES
パーミッションが必要な理由を明示するOK
NG
エラーメッセージなどでCameraを起動しない
NO
アプリ設定画面に飛ばす
YES
NO
チェック
public void showCamera(View view) { if (PermissionChecker.checkSelfPermission(context, Manifest.permission.CAMERA) != PermissionChecker.PERMISSION_GRANTED) { //権限がない場合はパーミッションを要求するメソッドを呼び出し requestCameraPermission(); } else { //権限がある場合はそのまま処理を呼び出し showCameraPreview(); } }
AndroidManifest.xmlに必要なパーミッションを記載<uses-permission android:name="android.permission.CAMERA"/>
PermissionCheckerのcheckSelfPermissionメソッドで、パーミッションがGranted(許可)かどうかを確認します
結果がGrantedの場合は、カメラを起動します
Permission WorkflowCameraパーミッションが許可されているかチェック
CameraViewを表示
一度パーミッション要求を拒否しているかチェックYES
NO
パーミッション要求
YES
今後は確認しないがONになっている
例:ボタンをタップしたら、カメラViewが起動します
NO YES
パーミッションが必要な理由を明示するOK
NG
エラーメッセージなどでCameraを起動しない
NO
アプリ設定画面に飛ばす
YES
NO
要求private void requestCameraPermission() { if (ActivityCompat.shouldShowRequestPermissionRationale(context, Manifest.permission.CAMERA)) { Snackbar.make(mLayout, R.string.permission_camera_rationale, Snackbar.LENGTH_INDEFINITE) .setAction(R.string.ok, new View.OnClickListener() { @Override public void onClick(View view) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA); } }) .show(); } else { ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA); } }
ActivityCompat.shuldShowRequestPermissionRationalメソッドで、 パーミッションが一度拒否され、説明が必要であるかのチェックをします
結果がTrue(一度拒否されている状態)で返って来た場合、このケースだとSnackbarで必要性を表示し、Action処理内で ActivityCompat.RequestPermissionsで、パーミッションの要求を実行しています
一度も実行されたことがない場合は、ActivityCompat.RequestPermissionsでパーミッションの要求をします
Permission WorkflowCameraパーミッションが許可されているかチェック
CameraViewを表示
一度パーミッション要求を拒否しているかチェックYES
NO
パーミッション要求
YES
今後は確認しないがONになっている
例:ボタンをタップしたら、カメラViewが起動します
NO YES
パーミッションが必要な理由を明示するOK
NG
エラーメッセージなどでCameraを起動しない
NO
アプリ設定画面に飛ばす
YES
NO
結果@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == REQUEST_CAMERA) { if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Snackbar.make(mLayout, R.string.permision_available_camera, Snackbar.LENGTH_SHORT).show(); } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) { //今後確認しないがCheckされていない Snackbar.make(mLayout, R.string.permissions_not_granted, Snackbar.LENGTH_SHORT).show(); } else { //今後確認しないがCheckされているため、アプリケーション設定画面へ遷移する Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivity(intent); } } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } }
結果はint配列で帰ってきます(複数のPermissionを要求した場合、要求した順序で返却されます) このケースだと、ひとつのチェックだけですが、必要に応じてループするなりしてチェックしましょう (googleSampleのveryfyPermissionをUtilクラスなどで実装しておくと良いかと) 許可された場合、Snackbarで通知していますが、必要に応じて画面遷移するといいでしょう
onRequestPermissionsResult内で、ActivityCompat,shouldShowRequestPermissionRationaleを呼ぶと、 今後確認しないのCheckが入っていない場合Trueが返却され、入っている場合Falseが返却されるため、このメソッド内で 判断するようにしましょう
RuntimePermissionの端末内での管理
Permissionはグループ単位で管理されており、グループ単位で許諾します
例えば、この連絡先という項目には下記が含まれます android.permission-group.CONTACTS
・android.permission.GET_ACCOUNTS ・android.permission.READ_CONTACTS ・android.permission.WRITE_CONTACTS
グループでは管理されますが、Manifestでの指定や RuntimePermissionの指定は、Permission単位です!
RuntimePermissionの端末内での管理RuntimePermissionはユーザー単位で管理されます(従来のPermissionは違います。あくまでRuntimePermissionのみ)
/data/system/users/{UserID}/runtime-permissions.xml
<pkg name="com.miruker.mpermission"> <item name="android.permission.READ_CALL_LOG" granted="false" flags="1" /> <item name="android.permission.CALL_PHONE" granted="false" flags="1" /> </pkg>
if(PermissionChecker.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CALL_LOG) == PermissionChecker.PERMISSION_GRANTED) { callPhone(); }else{ ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.READ_CALL_LOG}, CALL_PHONE_REQUEST_CODE); }
<pkg name="com.miruker.mpermission"> <item name="android.permission.READ_CALL_LOG" granted="true" flags="0" /> <item name="android.permission.CALL_PHONE" granted="true" flags="0" /> </pkg>
同じパーミッショングループのため発信できてしまうけど、こういうのは良くないです
RuntimePermissionの実装のTips①必要なタイミングで必要なパーミッションだけを要求しましょう。
アプリ全体で利用する本質的なパーミッションはアプリ起動時に要求し オプションとして、利用する機能については、その機能を利用する際に要求するのが ベター
Activityで、本質的なパーミッションをチェックして 何かのイベントボタンではそこで使うPermissionのチェックをするってことでOK
RuntimePermissionの実装のTips②Activityの起動時だけや、実行前だけのチェックでは不十分
Permissionが必要なActivityやFragmentの Resume等でPermissionはチェックしたほうがいい
例えばこんな動作 1,アプリを起動する 2,カメラボタンをタップ!(PermissionCheckして許可する) 3,カメラViewを起動する 4,タスク切り替えからパーミッションカメラを剥奪する 5,タスク切り替えで戻ってくる
多分残念な結果になります
RuntimePermissionの実装のTips③Permissionの要求は、ContextCompatのcheckSelfPermissionではなく PermissionCheckerのcheckSelfPermissionを使いましょう
AppOpsで権限を剥奪した際の考慮もされているようです
public final class PermissionChecker { /** Permission result: The permission is granted. */ public static final int PERMISSION_GRANTED = PackageManager.PERMISSION_GRANTED; /** Permission result: The permission is denied. */ public static final int PERMISSION_DENIED = PackageManager.PERMISSION_DENIED; /** Permission result: The permission is denied because the app op is not allowed. */ public static final int PERMISSION_DENIED_APP_OP = PackageManager.PERMISSION_DENIED - 1; @IntDef({PERMISSION_GRANTED, PERMISSION_DENIED, PERMISSION_DENIED_APP_OP}) @Retention(RetentionPolicy.SOURCE) public @interface PermissionResult {} private PermissionChecker() { /* do nothing */ }
PermissionChecker.java
RuntimePermissionの実装のTips④public void onClick(View view) { if(PermissionChecker.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CALL_LOG) == PermissionChecker.PERMISSION_GRANTED) { fetchCallLog(); }else{ ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_CALL_LOG}, READ_CALL_REQUEST_CODE); } }
何気ないこのコード
Jelly Bean以上では動くけど、ICSでは一向に呼ばれない 要は常にGrantedではない状態
APIレベルで存在しないPermissionについては常にGrantedではない値が返ってくるので 利用するPermissionのAPILevelには気をつけましょう
その他注意事項や補足RuntimePermissionの実装(TargetSDK23への変更)をストアへアップロードした後に TargetSDK22のAPKへ戻すことは出来ません。 必ずリリース前にテストしましょう
PermissionCheckの実装がめんどくさい?そんな人は、下記ライブラリを見てみるといいかもしれません
https://github.com/tbruyelle/RxPermissionsRxPermissions
PermissionsDispatcher
https://github.com/hotchemi/PermissionsDispatcher
今流行の?RxJavaっぽくPermission処理をかけるライブラリです
アノテーションベースでPermission処理をかけるライブラリです
まとめ自動更新されない問題が解消するのでぜひとも実装しましょう
インストール時のパーミッションダイアログでユーザーが嫌がってインストールしないことも減ります
ただし、TargetSDK23にしてしまったら戻せないので、必ずテストしましょう
おまけ
夢のMinSDK=23のアプリをリリースしました!
ただ単にバッテリー充電中に通知LEDで充電残量をお知らせてくれるだけのアプリです
良かったらダウンロードしてね
ご清聴ありがとうございました