Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
Managing runtime permissions
@hotchemi
December 01, 2015
Programming
1
4k
Managing runtime permissions
droidcon.pl 2015
@hotchemi
December 01, 2015
Tweet
Share
More Decks by @hotchemi
See All by @hotchemi
kompile-testing internal
hotchemi
0
210
The things we’ve learned from iOS×React Native hybrid development
hotchemi
2
4.3k
React Nativeを活用したアプリ開発体制/sapuri meetup
hotchemi
3
6.9k
Type-Safe i18n on RN
hotchemi
2
850
Navigation in a hybrid app
hotchemi
3
990
PermissionsDispatcher × Kotlin
hotchemi
0
1.9k
kotlin compiler plugin
hotchemi
1
530
Rx and Preferences
hotchemi
2
130
Introducing PermissionsDispatcher
hotchemi
1
110
Other Decks in Programming
See All in Programming
ECサイトの脆弱性診断をいい感じにやりたい/OWASPKansaiNight_LT1_220727
owaspkansai
0
280
Recap CDN, Edge, WebAssembly | ワインと鍋.js#1
sadnessojisan
2
1.2k
料理の注文メニューの3D化への挑戦
hideg
0
280
kintoneでランダム取得を作ってみた(imoniCamp 2022-07-27)
shokun1108
0
140
読みやすいコード クラスメソッド 2022 年度新卒研修
januswel
0
2.9k
Better Angular Architectures: Architectures with Standalone Components @DWX2022
manfredsteyer
PRO
1
380
ストア評価「2.4」だったCOCOARアプリを1年で「4.4」になんとかした方法@Cloud CIRCUS Meetup #2
1901drama
0
180
SRE NEXT 2022に学ぶこれからのSREキャリア
fukubaka0825
2
390
Google IO 2022 社内LT会 / What's new in Android development tools
shingo_kobayashi
0
390
Git操作編
smt7174
1
220
Enzyme から React Native Testing Library に移行した経緯 / 2022-07-20
tamago3keran
1
160
Pythonで鉄道指向プログラミング
usabarashi
0
110
Featured
See All Featured
Mobile First: as difficult as doing things right
swwweet
213
7.5k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
15
980
Building an army of robots
kneath
299
40k
Java REST API Framework Comparison - PWX 2021
mraible
PRO
11
4.8k
Large-scale JavaScript Application Architecture
addyosmani
499
110k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
14
3.8k
The Invisible Side of Design
smashingmag
290
48k
What's new in Ruby 2.0
geeforr
336
30k
Happy Clients
brianwarren
89
5.6k
What’s in a name? Adding method to the madness
productmarketing
11
1.6k
Optimizing for Happiness
mojombo
365
63k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
212
20k
Transcript
+ShintaroKatafuchi Managing Runtime Permissions @hotchemi
• +ShintaroKatafuchi • @hotchemi • Working at Recruit group •
Member of DroidKaigi
• The biggest android conf in Japan • 2016.02.18 -
2016.02.19 • #droidkaigi • https://droidkaigi.github.io/2016/en/
Runtime Permissions
• Before Marshmallow • Grant permissions at install time •
Check additional permission when the app is updated • “Hate it. One star!!!”
• After Marshmallow • Grant permissions at runtime • User
can revoke permissions from settings screen • Process is restarted
• After Marshmallow • Grant permissions at runtime • User
can revoke permissions from settings screen • Process is restarted
• Never ask again • Once user check and denied,
checkSelfPermission always return PERMISSION_DENIED • targetSdkVersion >= 23 • Lead user to settings screen
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivity(intent); • Never ask again
• Protection level • PROTECTION_NORMAL • PROTECTION_DANGEROUS • PROTECTION_SIGNATURE •
PROTECTION_SIGNATURE_OR_SYSTEM (Deprecated) • PROTECTION_FLAG_XXX etc…
• Normal permissions • ACCESS_NETWORK_STATE • BLUETOOTH • INTERNET •
NFC • VIBRATE • WAKE_LOCK • SET_ALARM etc…
Permission groups Permissions CALENDAR READ_CALENDAR WRITE_CALENDAR CAMERA CAMERA CONTACTS READ_CONTACTS
WRITE_CONTACTS GET_ACCOUNTS LOCATION ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION MICROPHONE RECORD_AUDIO PHONE READ_PHONE_STATE CALL_PHONE READ_CALL_LOG WRITE_CALL_LOG ADD_VOICEMAIL USE_SIP PROCESS_OUTGOING_CALLS SENSORS BODY_SENSORS SMS SEND_SMS RECEIVE_SMS READ_SMS RECEIVE_WAP_PUSH RECEIVE_MMS STORAGE READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE • Dangerous permissions
• Dangerous permissions • Includes custom permission
• Permission groups • Same group permissions are granted at
once ex) READ_CALENDAR, WRITE_CALENDAR Request read calendar permission Grant calendar group Show permission dialog Press allow button Request write calendar permission Calendar is granted already! Returns GRANTED
• Permission groups • “Never ask again” is the same
ex) READ_CALENDAR, WRITE_CALENDAR Request read calendar permission Deny calendar group Show permission dialog Press deny button with check Request write calendar permission Calendar is denied already! Returns DENIED
• Special permissions • SYSTEM_ALERT_WINDOW • WRITE_SETTINGS @Override protected void
onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (!Settings.canDrawOverlays(this)) { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); startActivityForResult(intent, REQUEST_CODE_PERMISSION_SYSTEM_ALERT_WINDOW); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_PERMISSION_SYSTEM_ALERT_WINDOW) { if (Settings.canDrawOverlays(this)) { // yay! } else { // denied. } } }
• Other tips • Permission setting is shared among same
sharedUserId • Manifest trick • Use checker to find dangerous permissions • https://github.com/hotchemi/m-permissions-checker <uses-‐permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18"/> <uses-‐permission-‐sdk-‐23 android:name="android.permission.ACCESS_FINE_LOCATION"/> $ cd <root your app> $ python permissions_checker.py > Searching file: /Users/hoge/test/data/AndroidManifest.xml > Unfortunately, you have to handle these permissions in MNC. > android.permission.READ_CALENDAR > android.permission.WRITE_CALENDAR > android.permission.CAMERA
How to write code?
targetSdkVersion / OS sdk >= 23 / M sdk <
23 / M sdk >= 23 / L Grant permissions Runtime Install Install Never ask again Yes No No Can revoke from settings Yes Yes No
targetSdkVersion >= 23
public class MainActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); showCamera(); } }
public class MainActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); showCamera(); } } private static final int REQUEST_SHOWCAMERA = 0;
public class MainActivity extends AppCompatActivity { private static final
int REQUEST_SHOWCAMERA = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); showCamera(); } } private static final String[] PERMISSION_SHOWCAMERA = new String[]{"android.permission.CAMERA"};
public class MainActivity extends AppCompatActivity { private static final
int REQUEST_SHOWCAMERA = 0; private static final String[] PERMISSION_SHOWCAMERA = new String[]{"android.permission.CAMERA"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } if (PermissionChecker.checkSelfPermissions(this, PERMISSION_SHOWCAMERA) == PERMISSION_GRANTED) { showCamera(); } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_SHOWCAMERA)) { // show rationale } ActivityCompat.requestPermissions(this, PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA); }
public class MainActivity extends AppCompatActivity { private static final
int REQUEST_SHOWCAMERA = 0; private static final String[] PERMISSION_SHOWCAMERA = new String[]{"android.permission.CAMERA"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (PermissionChecker.checkSelfPermissions(this, PERMISSION_SHOWCAMERA) == PERMISSION_GRANTED) { showCamera(); } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_SHOWCAMERA)) { // show rationale } ActivityCompat.requestPermissions(this, PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA); } } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case REQUEST_SHOWCAMERA: if (PermissionUtils.verifyPermissions(grantResults)) { // check all permissions are granted showCamera(); } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_SHOWCAMERA)) { // show denied action } else { // never ask again } } break; }
targetSdkVersion < 23
None
public class MainActivity extends AppCompatActivity { private static final
int REQUEST_SHOWCAMERA = 0; private static final String[] PERMISSION_SHOWCAMERA = new String[]{"android.permission.CAMERA"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // expect PERMISSION_DENIED if (ContextCompat.checkSelfPermissions(this, PERMISSION_SHOWCAMERA) == PERMISSION_GRANTED) { showCamera(); } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_SHOWCAMERA)) { // show rationale } ActivityCompat.requestPermissions(this, PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA); } } }
public class MainActivity extends AppCompatActivity { private static final
int REQUEST_SHOWCAMERA = 0; private static final String[] PERMISSION_SHOWCAMERA = new String[]{"android.permission.CAMERA"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // expect PERMISSION_DENIED if (ContextCompat.checkSelfPermissions(this, PERMISSION_SHOWCAMERA) == PERMISSION_GRANTED) { showCamera(); } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_SHOWCAMERA)) { // show rationale } ActivityCompat.requestPermissions(this, PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA); } } } // some compete class return PERMISSION_GRANTED! if (ContextCompat.checkSelfPermissions(this, PERMISSION_SHOWCAMERA) == PERMISSION_GRANTED) { }
public class MainActivity extends AppCompatActivity { private static final
int REQUEST_SHOWCAMERA = 0; private static final String[] PERMISSION_SHOWCAMERA = new String[]{"android.permission.CAMERA"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // expect PERMISSION_DENIED if (ContextCompat.checkSelfPermissions(this, PERMISSION_SHOWCAMERA) == PERMISSION_GRANTED) { showCamera(); } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_SHOWCAMERA)) { // show rationale } ActivityCompat.requestPermissions(this, PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA); } } } // with PermissionChecker, returns PERMISSION_DENIED_APP_OP if (PermissionChecker.checkSelfPermissions(this, PERMISSION_SHOWCAMERA) == PERMISSION_GRANTED) { }
@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{ switch (requestCode) { case REQUEST_SHOWCAMERA: if (!PermissionUtils.hasSelfPermissions(this, PERMISSION_SHOWCAMERA)) { // results are always granted… // show denied action return; } if (PermissionUtils.verifyPermissions(grantResults)) { showCamera(); } else { if (Actile(this, PERMISSION_SHOWCAMERA)) { // show denied action } break; } if (!PermissionUtils.hasSelfPermissions(this, PERMISSION_SHOWCAMERA)) { // results are always granted… // show denied action return; }
Design pattern
• Best practices • Consider using an intent • Only
ask for permissions you need • Explain why you need permissions
https://www.google.com/design/spec/patterns/permissions.html
• Educate up-front • If your app has a “welcome,”
use it to explain what your app does and why unexpected permissions will be requested
• Ask up-front • Ask for critical and obvious
permissions on first launch • messaging app requests SMS permissions up-front, that makes sense
• Educate in context • Explaining a permission in
context helps draw user interest and improve comprehension
• Ask in context • Wait until a feature is
invoked to request permission • allow a permission when they want to use the feature
Alright, go ahead!!!
public class MainActivity extends AppCompatActivity { private static final
int REQUEST_SHOWCAMERA = 0; private static final String[] PERMISSION_SHOWCAMERA = new String[]{"android.permission.CAMERA"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (PermissionChecker.checkSelfPermissions(this, PERMISSION_SHOWCAMERA) == PERMISSION_GRANTED) { showCamera(); } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_SHOWCAMERA)) { // show rationale } ActivityCompat.requestPermissions(this, PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case REQUEST_SHOWCAMERA: if (!PermissionUtils.hasSelfPermissions(this, PERMISSION_SHOWCAMERA)) { // show denied action return; } if (PermissionUtils.verifyPermissions(grantResults)) { showCamera(); } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_SHOWCAMERA)) { // show denied action } else { // never ask again } } break; } }
OK, one more…
public class MainActivity extends AppCompatActivity { private static final
int REQUEST_SHOWCAMERA = 0; private static final String[] PERMISSION_SHOWCAMERA = new String[]{“android.permission.CAMERA"}; private static final int REQUEST_SHOWCONTACT = 0; private static final String[] PERMISSION_SHOWCONTACT = new String[]{“android.permission.READ_CONTACT"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (PermissionChecker.checkSelfPermissions(this, PERMISSION_SHOWCAMERA) == PERMISSION_GRANTED) { showCamera(); } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_SHOWCAMERA)) { // show rationale… } ActivityCompat.requestPermissions(this, PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA); } if (PermissionChecker.checkSelfPermissions(this, PERMISSION_SHOWCONTACT) == PERMISSION_GRANTED) { showCamera(); } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_SHOWCONTACT)) { // show rationale… } ActivityCompat.requestPermissions(this, PERMISSION_SHOWCONTACT, REQUEST_SHOWCONTACT); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case REQUEST_SHOWCAMERA: if (!PermissionUtils.hasSelfPermissions(this, PERMISSION_SHOWCAMERA)) { onCameraDenied(); return; } if (PermissionUtils.verifyPermissions(grantResults)) { // show camera } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_SHOWCAMERA)) { // show denied action } else { // never ask again } } break; case REQUEST_SHOWCONTACT: if (!PermissionUtils.hasSelfPermissions(this, PERMISSION_SHOWCONTACT)) { // show denied action return; } if (PermissionUtils.verifyPermissions(grantResults)) { // show camera } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_SHOWCONTACT)) { // show denied action } else { // never ask again } } break; } }
Just tired…
No worries.
• PermissionsDispatcher • Generate codes with annotation processing • supported
on API levels 4 and up • Processor module is written with Kotlin • Developed by I and @marcelschnelle • https://github.com/hotchemi/PermissionsDispatcher
• Simple annotation based API • @RuntimePermissions ✔ • @NeedsPermission
✔ • @OnDeniedPermission • @OnShowRationale • @NeverAskAgain
apply plugin: 'android-‐apt' dependencies { compile
“com.github.hotchemi:permissionsdispatcher:2.0.1” apt “com.github.hotchemi:permissionsdispatcher-‐processor:2.0.1” }
• @RuntimePermissions • Activity • Fragment • Fragment(support v4) public
class MainActivity extends AppCompatActivity{ } @RuntimePermissions
• @NeedsPermission • Single permission • Multiple permissions void showCamera()
{ getSupportFragmentManager().beginTransaction() .replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance()) .addToBackStack("camera") .commitAllowingStateLoss(); } @NeedsPermission(Manifest.permission.CAMERA)
• @OnPermissionDenied • Single permission • Multiple permissions void
onCameraDenied() { Toast.makeText(this, R.string.permission_camera_denied, Toast.LENGTH_LONG).show(); } @OnPermissionDenied(Manifest.permission.CAMERA)
• @OnShowRationale • Asynchronous support @OnShowRationale(Manifest.permission.CAMERA) void
showRationaleForCamera(final PermissionRequest request) { new AlertDialog.Builder(this) .setPositiveButton(R.string.button_allow, new DialogInterface.OnClickListener() { @Override public void onClick(@NonNull DialogInterface dialog, int which) { request.proceed(); } }) .setNegativeButton(R.string.button_deny, new DialogInterface.OnClickListener() { @Override public void onClick(@NonNull DialogInterface dialog, int which) { request.cancel(); } }) .setMessage(R.string.permission_camera_rationale) .show(); }
• @NeverAskAgain • Single permission • Multiple permissions • Coming
soon… void onNeverAskAgain() { Toast.makeText(this, R.string.permission_camera_neveraskagain, Toast.LENGTH_LONG).show(); } @NeverAskAgain(Manifest.permission.CAMERA)
package permissions.dispatcher.sample; @RuntimePermissions public class MainActivity extends AppCompatActivity implements
View.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button_camera).setOnClickListener(this); } @Override public void onClick(@NonNull View v) { switch (v.getId()) { case R.id.button_camera: MainActivityPermissionsDispatcher.showCameraWithCheck(this); break; } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults); } } MainActivityPermissionsDispatcher.showCameraWithCheck(this); MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
package permissions.dispatcher.sample; final class MainActivityPermissionsDispatcher { private static
final int REQUEST_SHOWCAMERA = 0; private static final String[] PERMISSION_SHOWCAMERA = new String[] {"android.permission.CAMERA"}; private MainActivityPermissionsDispatcher() { } static void showCameraWithCheck(MainActivity target) { if (PermissionUtils.hasSelfPermissions(target, PERMISSION_SHOWCAMERA)) { target.showCamera(); } else { if (PermissionUtils.shouldShowRequestPermissionRationale(target, PERMISSION_SHOWCAMERA)) { target.showRationaleForCamera(new ShowCameraPermissionRequest(target)); } else { ActivityCompat.requestPermissions(target, PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA); } } } static void onRequestPermissionsResult(MainActivity target, int requestCode, int[] grantResults) { switch (requestCode) { case REQUEST_SHOWCAMERA: if (PermissionUtils.verifyPermissions(grantResults)) { target.showCamera(); } else { target.onCameraDenied(); } break; default: break; } } private static final class ShowCameraPermissionRequest implements PermissionRequest { private final WeakReference<MainActivity> weakTarget; private ShowCameraPermissionRequest(MainActivity target) { this.weakTarget = new WeakReference<>(target); } @Override public void proceed() { MainActivity target = weakTarget.get(); if (target == null) return; ActivityCompat.requestPermissions(target, PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA); } @Override public void cancel() { MainActivity target = weakTarget.get(); if (target == null) return; target.onCameraDenied(); } } } Generated code
Conclusion
• You can’t run away from runtime permissions • But
runtime permissions is difficult to handle • Follow good practice to provide better user experience • Enjoy PermissionsDispatcher!!!
+ShintaroKatafuchi Thanks, any questions? @hotchemi