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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
@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.6k
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
200
Introducing PermissionsDispatcher
hotchemi
1
200
Other Decks in Programming
See All in Programming
[2026-09-26]空論ジェネリックプロセス~テスト資産とAIで紡ぐ、再現可能なパフォーマンスチューニングの話~
tosite
0
270
複雑なUIコンポーネントとの ⻑い付き合い⽅:波及するバグからテスト戦略を経て、安全な再設計へ
mixplace
0
410
PumaからFalconに移行したRailsアプリケーションでの実践
falcon8823
0
170
Workers Cache を知る
syumai
0
360
Agents on Rails - Rails at Scale 2026
irinanazarova
0
560
Everything will be SERVERLESS — 信じて運用した10年の経験値 / Everything Will be Serverless — Lessons Learned from 10 Years of Operational Experience
seike460
PRO
1
1.1k
[ハンズオン]AIへの指示だけで「五目並べ」を作ってみよう
satoshi256kbyte
1
380
golang-bob.pdf
iwatsukayura
0
130
コードレビューのボトルネックを"する側"と"される側"の両面から解消する
yub0n
2
1.6k
AmebaブログはKMPから卒業します
kmars0605
0
190
コードで差別化できない時代の ソフトウェアエンジニアリング / Software Engineering in an Era Where Code Doesn’t Differentiate
kishida
2
180
IBM Bob Dojo #1 仕様駆動開発入門
oniak3ibm
PRO
0
390
Featured
See All Featured
Claude Code のすすめ
schroneko
67
230k
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
410
Intergalactic Javascript Robots from Outer Space
tanoku
273
28k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.3k
Side Projects
sachag
456
43k
Producing Creativity
orderedlist
PRO
348
41k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
220
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
3.2k
Darren the Foodie - Storyboard
khoart
PRO
4
4k
Git: the NoSQL Database
bkeepers
PRO
433
67k
Paper Plane (Part 1)
katiecoart
PRO
2
12k
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
520
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