34
Android 6.0 RuntimePermissionの実装と注意点 2015/11/14 GDG Kobe Android6.0 Marshmallow勉強会

Android6.0 RuntimePermissionの実装と注意点

Embed Size (px)

Citation preview

Page 1: Android6.0 RuntimePermissionの実装と注意点

Android 6.0 RuntimePermissionの実装と注意点

2015/11/14 GDG Kobe

Android6.0 Marshmallow勉強会

Page 2: Android6.0 RuntimePermissionの実装と注意点

self-introduction

twitter @KatsukiNakatani

Facebook Katsuki.Nakataniお仕事大阪のSIerでインフラ(サーバーとかルーターとか。。。)のエンジニアをしてます

個人でアプリ作ってますAndroid Windows Store

中谷 克紀名前

Page 3: Android6.0 RuntimePermissionの実装と注意点

8みなさんこの数字をご存知ですか?

そう!今年でAndroidは8歳です

Page 4: Android6.0 RuntimePermissionの実装と注意点

Android6.0 Marshmallowこの8年間、StoreInstalledPermissionだったモデルが変革の時を迎えました。

新しい権限モデルRuntimePermissionです

今日はそのRuntimePermissionの実装と私自身はまったところなどを交えて 今後皆様が実装する際の手助けとなれば幸いです

Page 5: Android6.0 RuntimePermissionの実装と注意点

RuntimePermissionとは?Android 6.0(SDK 23)で実装された、実行時許可ベースの権限モデル

Page 6: Android6.0 RuntimePermissionの実装と注意点

前段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>

端末内の特定のセンサーやデータにアクセスするためにアプリケーションごとに 付与するもの

Page 7: Android6.0 RuntimePermissionの実装と注意点

今までの使い方宣言するのは下記のファイルに記載すればOKAndroidManifest.xml

権限の付与について

ユーザーはGooglePlayからアプリのインストール時に権限を確認し、 インストール=そのアプリに該当の権限をすべて付与する

開発者 ・・・必要な権限をManifestに記載する ユーザー・・・インストール時に確認するだけ

とてもシンプルで両者にとってWIN-WINな関係

だったはずですが。。。

Page 8: Android6.0 RuntimePermissionの実装と注意点

ユーザー目線で見ると2011年ミログが公開したアプリでユーザの同意を得る前にサーバにデータを送信

2012年全国電話帳というアプリで端末内電話帳データをサーバに送信

2013年Simejiが変換文字列を自動的にサーバに送信

?でもシンプルなモデルだからわかりやすいってさっき話してたよね?  なぜこんなことが起こるんだろう

ユーザーはインストール時にそこまでパーミッションをみてない

アプリによる情報流出の被害者が後を絶たない

Page 9: Android6.0 RuntimePermissionの実装と注意点

開発者目線では?

全然バージョンアップされてない

理由・・・Permission追加時は自動更新されず、ユーザの許諾アクションが必要      ユーザが許諾する際、追加の場合には特になぜ追加したか納得しないと更新しない傾向に有る

Manifestに記載するだけでOKやし、権限なかったらExceptionで落ちるからテストでもわかるし、シンプルで楽やん?

また、それら以外に、インストール時にパーミッションを求めることで、どういう機能でPermissionが 必要なのかがわかりにくい側面があり、インストールがためらわれる面もあった

Page 10: Android6.0 RuntimePermissionの実装と注意点

RuntimePermission!そこで登場したのがRuntimePermissionです

インストール・更新時(自動含む)にユーザにPermissionの許諾は求めません

実行時必要なときに必要な分だけ、Permissonを取得して実行します

Page 11: Android6.0 RuntimePermissionの実装と注意点

とまた、その前に

Page 12: Android6.0 RuntimePermissionの実装と注意点

RuntimePermissionの動作は、build.gradleに指定するTargetSDKのバージョンで変わりますTargetSDK/権限 端末のOS 許諾の不要なパーミッション 許諾が必要なパーミッション

236.0

インストール時に付与 付与されない<23 インストール時に付与 インストール時に付与- 6.0未満 インストール時に付与 インストール時に付与

RuntimePermission対応を実施するためにはTargetSDKのバージョンを23にしましょう

Page 13: Android6.0 RuntimePermissionの実装と注意点

許諾の必要なパーミッション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用がもう少しあるようですが、今回は省いています

Page 14: Android6.0 RuntimePermissionの実装と注意点

独自パーミッションも対象となります<permission android:name="com.test.permission" android:label="testPermission" android:description="@string/description" android:protectionLevel="dangerous" /> <uses-permission android:name="com.test.permission"/>

dangerousのレベルを指定した場合に対象となります

Page 15: Android6.0 RuntimePermissionの実装と注意点

RuntimePermissionで許可対象のまとめ

PermissionGroupに所属するパーミッションまたは、 所属しないがProtectionLevelがdangerousなパーミッションが対象

逆に言うと、それ以外のインターネットアクセスなどは ユーザーに意識することなくアプリに権限を付与することが可能

これとても嬉しい!!

Page 16: Android6.0 RuntimePermissionの実装と注意点

Google Playでの動作

TargetSDKが22未満 TargetSDKが23

インストール時にダイアログが表示 インストール時にダイアログが表示されない

Page 17: Android6.0 RuntimePermissionの実装と注意点

[注意!]Android6.0端末&TargetSDK 23未満TargetSDKが23未満のアプリでも、6.0では後から 設定画面でパーミッションを剥奪することが可能です

ただし、外すとPermission Denialで強制終了したり、空のデータが返って来たりします

警告はしてくれます

TargetSDK 23で実装しよう!

Page 18: Android6.0 RuntimePermissionの実装と注意点

Permission WorkflowCameraパーミッションが許可されているかチェック

CameraViewを表示

一度パーミッション要求を拒否しているかチェックYES

NO

パーミッション要求

YES

今後は確認しないがONになっている

例:ボタンをタップしたら、カメラViewが起動します

NO YES

パーミッションが必要な理由を明示するOK

NG

エラーメッセージなどでCameraを起動しない

NO

アプリ設定画面に飛ばす

YES

NO

Page 19: Android6.0 RuntimePermissionの実装と注意点

チェック

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の場合は、カメラを起動します

Page 20: Android6.0 RuntimePermissionの実装と注意点

Permission WorkflowCameraパーミッションが許可されているかチェック

CameraViewを表示

一度パーミッション要求を拒否しているかチェックYES

NO

パーミッション要求

YES

今後は確認しないがONになっている

例:ボタンをタップしたら、カメラViewが起動します

NO YES

パーミッションが必要な理由を明示するOK

NG

エラーメッセージなどでCameraを起動しない

NO

アプリ設定画面に飛ばす

YES

NO

Page 21: Android6.0 RuntimePermissionの実装と注意点

要求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でパーミッションの要求をします

Page 22: Android6.0 RuntimePermissionの実装と注意点

Permission WorkflowCameraパーミッションが許可されているかチェック

CameraViewを表示

一度パーミッション要求を拒否しているかチェックYES

NO

パーミッション要求

YES

今後は確認しないがONになっている

例:ボタンをタップしたら、カメラViewが起動します

NO YES

パーミッションが必要な理由を明示するOK

NG

エラーメッセージなどでCameraを起動しない

NO

アプリ設定画面に飛ばす

YES

NO

Page 23: Android6.0 RuntimePermissionの実装と注意点

結果@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が返却されるため、このメソッド内で 判断するようにしましょう

Page 24: Android6.0 RuntimePermissionの実装と注意点

RuntimePermissionの端末内での管理

Permissionはグループ単位で管理されており、グループ単位で許諾します

例えば、この連絡先という項目には下記が含まれます android.permission-group.CONTACTS

・android.permission.GET_ACCOUNTS ・android.permission.READ_CONTACTS ・android.permission.WRITE_CONTACTS

グループでは管理されますが、Manifestでの指定や RuntimePermissionの指定は、Permission単位です!

Page 25: Android6.0 RuntimePermissionの実装と注意点

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>

同じパーミッショングループのため発信できてしまうけど、こういうのは良くないです

Page 26: Android6.0 RuntimePermissionの実装と注意点

RuntimePermissionの実装のTips①必要なタイミングで必要なパーミッションだけを要求しましょう。

アプリ全体で利用する本質的なパーミッションはアプリ起動時に要求し オプションとして、利用する機能については、その機能を利用する際に要求するのが ベター

Activityで、本質的なパーミッションをチェックして 何かのイベントボタンではそこで使うPermissionのチェックをするってことでOK

Page 27: Android6.0 RuntimePermissionの実装と注意点

RuntimePermissionの実装のTips②Activityの起動時だけや、実行前だけのチェックでは不十分

Permissionが必要なActivityやFragmentの Resume等でPermissionはチェックしたほうがいい

例えばこんな動作  1,アプリを起動する  2,カメラボタンをタップ!(PermissionCheckして許可する)  3,カメラViewを起動する  4,タスク切り替えからパーミッションカメラを剥奪する  5,タスク切り替えで戻ってくる

多分残念な結果になります

Page 28: Android6.0 RuntimePermissionの実装と注意点

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

Page 29: Android6.0 RuntimePermissionの実装と注意点

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には気をつけましょう

Page 30: Android6.0 RuntimePermissionの実装と注意点

その他注意事項や補足RuntimePermissionの実装(TargetSDK23への変更)をストアへアップロードした後に TargetSDK22のAPKへ戻すことは出来ません。 必ずリリース前にテストしましょう

PermissionCheckの実装がめんどくさい?そんな人は、下記ライブラリを見てみるといいかもしれません

https://github.com/tbruyelle/RxPermissionsRxPermissions

PermissionsDispatcher

https://github.com/hotchemi/PermissionsDispatcher

今流行の?RxJavaっぽくPermission処理をかけるライブラリです

アノテーションベースでPermission処理をかけるライブラリです

Page 31: Android6.0 RuntimePermissionの実装と注意点

まとめ自動更新されない問題が解消するのでぜひとも実装しましょう

インストール時のパーミッションダイアログでユーザーが嫌がってインストールしないことも減ります

ただし、TargetSDK23にしてしまったら戻せないので、必ずテストしましょう

Page 32: Android6.0 RuntimePermissionの実装と注意点

おまけ

Page 33: Android6.0 RuntimePermissionの実装と注意点

夢のMinSDK=23のアプリをリリースしました!

ただ単にバッテリー充電中に通知LEDで充電残量をお知らせてくれるだけのアプリです

良かったらダウンロードしてね

Page 34: Android6.0 RuntimePermissionの実装と注意点

ご清聴ありがとうございました