Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Managing runtime permissions
Search
@hotchemi
December 01, 2015
Programming
4.4k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Managing runtime permissions
droidcon.pl 2015
@hotchemi
December 01, 2015
More Decks by @hotchemi
See All by @hotchemi
kompile-testing internal
hotchemi
0
300
The things we’ve learned from iOS×React Native hybrid development
hotchemi
2
5.5k
React Nativeを活用したアプリ開発体制/sapuri meetup
hotchemi
3
8.3k
Type-Safe i18n on RN
hotchemi
2
1.3k
Navigation in a hybrid app
hotchemi
3
1.4k
PermissionsDispatcher × Kotlin
hotchemi
0
3.4k
kotlin compiler plugin
hotchemi
1
840
Rx and Preferences
hotchemi
2
190
Introducing PermissionsDispatcher
hotchemi
1
200
Other Decks in Programming
See All in Programming
技術的負債の返済は、AI時代の複利で効く投資 — 経営としての意思決定とその遂行
curekoshimizu
1
2.3k
Embedded Swiftで作る自作USBデバイス によるiOSデバイスの自動テスト / iOSDC Japan 2026 glassfiber
glassfiber
0
310
Simple Storage Service(S3) is not simple
iwatsukayura
0
220
[ハンズオン]AIへの指示だけで「五目並べ」を作ってみよう
satoshi256kbyte
1
360
Apple Intelligence を用いた個人情報誤送信防止、及びユーザーリクエスト体験の改善について
yukiny
0
320
20260914 AIエージェント時代のPlatform Engineering LLM基盤とプロダクトの責務境界線
kanfab1
7
2.4k
Starting & Sustaining Code-Based E2E Testing for Non-Coding QA Teams( #jasstniigata )
teyamagu
PRO
1
940
Findy - エンジニア向け会社紹介/Findy Company Deck
findyinc
6
400k
setup-vp GitLab対応の裏側
naokihaba
0
150
Jetpack Compose Mechanisms
skydoves
2
570
難しいけど、読めた。- OSSの入口に立った話。
sts11142
0
140
C#の現在地 進化の歴史と、AI時代の.NET Everywhere
neuecc
5
5k
Featured
See All Featured
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
Being A Developer After 40
akosma
92
590k
Deep Space Network (abreviated)
tonyrice
0
360
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
510
Building Adaptive Systems
keathley
44
3.2k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
250
1.3M
[RailsConf 2023] Rails as a piece of cake
palkan
59
7.1k
Faster Mobile Websites
deanohume
310
32k
Fantastic passwords and where to find them - at NoRuKo
philnash
53
4k
The Limits of Empathy - UXLibs8
cassininazir
2
710
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.9k
How STYLIGHT went responsive
nonsquared
100
6.3k
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