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.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
850
Rx and Preferences
hotchemi
2
200
Introducing PermissionsDispatcher
hotchemi
1
210
Other Decks in Programming
See All in Programming
golang-bob.pdf
iwatsukayura
0
150
SFCで実現する病院・医療システムの動的組み立て式UI
koukimiura
1
140
Materialが消えた日 〜真っ白なFlutterキャンバスにRenderObjectで絵を描く〜
umigishiaoi
0
120
月間100万人のサービス刷新で、 技術選定をHonoに振り切った話
yutobara
8
7.1k
contenteditable と日本語入力に向き合う
colorful12
0
230
Vapor Mode でアクセシビリティは 壊れないか検証した話
fuqda
1
280
そのデザイントークン、Flutterでも同じ見た目になりますか?
nakamizu
0
140
[Dev Group Paris Octobre 26] Agentforce, SIforce, Claudeforce, Slackbot… on s'y retrouve ?
nabondance
0
120
조합이폭발하는디자인시스템이걸눈으로도테스트하라고요
giljongyu
0
650
GraphQL の onError と Nullability のこれから
quramy
0
200
[2026-10-09]一枚の布で作らなくてもいい。 信頼性は「パッチワーク」で紡がれる~既存の運用と新しい可能性をつなぎ合わせてきたCREの話~
tosite
0
120
作れる、でも消せるか?──安全に「枯らして消す」機能削除術 / Drain, Then Delete
chobishiba
3
1.3k
Featured
See All Featured
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
750
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
18k
Code Reviewing Like a Champion
maltzj
529
40k
How to Ace a Technical Interview
jacobian
280
24k
Accessibility Awareness
sabderemane
1
270
Stop Working from a Prison Cell
hatefulcrawdad
274
21k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
470
Site-Speed That Sticks
csswizardry
13
1.6k
Speed Design
sergeychernyshev
33
2.2k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
WENDY [Excerpt]
tessaabrams
14
40k
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