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
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
@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
290
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
830
Rx and Preferences
hotchemi
2
190
Introducing PermissionsDispatcher
hotchemi
1
200
Other Decks in Programming
See All in Programming
Heart of Swift Concurrency
koher
0
890
LL言語やWebフレームワークのPostgreSQL対応 〜DBの機能がユーザーに届くまで〜
kentaroutakeda
1
150
Snowflakeで業務アプリを作ろう。 Snowflakeのアプリ機能解説&実践ガイド
ayumu_yamaguchi
1
280
[GoCon2026] When Goroutines Are Not Enough: Runtime Locality in High-Throughput Go
takehaya
6
2.3k
XHTMLが残したもの
yosuke_furukawa
PRO
2
810
{ Android | Kotlin } Gradle Plugin in 2026
ryunen344
1
320
大喜利で理解するLLM as a Judge / Understanding LLM-as-a-Judge through Ogiri
rockname
0
120
海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム
atsuki_seo
0
730
Starting & Sustaining Code-Based E2E Testing for Non-Coding QA Teams( #jasstniigata )
teyamagu
PRO
1
380
App Storeの外へ──日本のiOSサイドローディング入門 for iOSDC Japan 2026
yuukiw00w
0
220
AI Agent時代のリアーキテクチャ戦略と実践
hokaccha
9
4.7k
AIは賢い。でも実行環境は? CLIおじさんがAI時代に伝えたいこと ~ CLIおじさんがAI時代に伝えたいこと ~
curekoshimizu
1
250
Featured
See All Featured
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
760
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
490
A Soul's Torment
seathinner
8
3.6k
Side Projects
sachag
456
43k
Raft: Consensus for Rubyists
vanstee
142
7.7k
How to Think Like a Performance Engineer
csswizardry
28
2.8k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.3k
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.4k
KATA
mclloyd
PRO
35
15k
Building a A Zero-Code AI SEO Workflow
portentint
PRO
0
730
The Art of Programming - Codeland 2020
erikaheidi
57
14k
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