Everything developers need to know about Android M's new runtime permissions

Everything developers need to know about Android M's new runtime permissions

A translated foreign article.

The name of Android M was officially announced not long ago, and the final official version is coming soon!

Android is constantly evolving, the latest update M is very different, some major changes like runtime permissions will have disruptive impact. Surprisingly, the Android community doesn't talk much about this, even though it's important and may cause serious problems in the near future.

That’s why I’m writing this blog post today. Here’s everything you need to know about Android runtime permissions, including how to implement them in your code. It’s not too late to fix it now.

New runtime permissions

Android's permission system has always been a security-first concept, because these permissions are only asked once during installation. Once installed, the app can access everything within the permission without the user knowing.

No wonder some bad guys exploit this flaw to maliciously collect user data and do bad things!

The Android team knows this, too. After 7 years, the permissions system has finally been redesigned. In Android 6.0 Marshmallow, apps will no longer be granted permissions when they are installed. Instead, apps will have to ask the user for permissions one by one at runtime.

Paste_Image.png

Note that the permission inquiry dialog box will not pop up automatically. The developer has to call it by himself. If some functions that the developer wants to call require certain permissions and the user refuses to grant them, the function will throw an exception and directly cause the program to crash.

Paste_Image.png

In addition, users can also cancel authorized permissions in the settings at any time.

Paste_Image.png

You may have felt a chill in your back... If you are an Android developer, this means you have to completely change your program logic. You can no longer call methods directly like before, you have to check permissions for each place you need, otherwise the app will crash!

Yes. I can't fool you into thinking it's easy. Although it's great for users, it's a nightmare for developers. We have to change the code or there will be potential problems in both the short and long term.

This new runtime permission only works when we set targetSdkVersion to 23 (which means you have tested it on 23), and of course it has to be an M phone. Apps on devices before 6.0 still use the old permission system.

What happens to apps that have already been released?

The new runtime permissions may have made you panic. "Hey, man! What will happen to my app that I released three years ago? If it is installed on Android 6.0, will my app break?!?"

Don't panic, just relax. The Android team is not stupid, they must have considered this situation. If the targetSdkVersion of the app is lower than 23, it will be considered that the app has not been tested with the new permissions of 23, and the old rules will continue to apply: the user has to accept all permissions when installing, and the app will have those permissions after installation!

Paste_Image.png

Then the app runs as before! Note that the user can still cancel the authorization they have agreed to! When the user cancels the authorization, the Android 6.0 system will warn, but this does not prevent the user from canceling the authorization.

Paste_Image.png

The question is, does your app crash at this time?

#p#

The well-meaning owner also told the Android team about this. When we call a function that requires permission in an app with a targetSdkVersion lower than 23, if the permission is revoked by the user, no exception will be thrown. However, it will do nothing, resulting in the function return value being null or 0.

Paste_Image.png

Don’t get too excited. Even though your app won’t crash when calling this function, a return value of null or 0 may still cause a crash later.

The good news (at least for now) is that this kind of revocation of permissions is rare, and I believe that few users would do this. If they do, it is at their own risk.

But in the long run, I believe there will still be a large number of users who will turn off some permissions. It is unacceptable that our app can no longer run perfectly on new devices.

How to make it work perfectly? You'd better modify the code to support the latest permission system, and I suggest you start doing it right away!

If your code has not been successfully modified to support the latest runtime permissions, do not set targetSdkVersion 23 when releasing the app, otherwise you will be in trouble. Only change targetSdkVersion 23 after you have tested it.

Warning: When you create a new project in Android Studio, targetSdkVersion is automatically set to 23. If you don't support the new runtime permissions yet, I recommend you first downgrade your targetSdkVersion to 22.

PROTECTION_NORMAL class permissions

When a user installs or updates an app, the system grants the app all permissions requested that are PROTECTION_NORMAL (a basic set of permissions granted at install time). These permissions include:

  1. android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
  2. android.permission.ACCESS_NETWORK_STATE
  3. android.permission.ACCESS_NOTIFICATION_POLICY
  4. android.permission.ACCESS_WIFI_STATE
  5. android.permission.ACCESS_WIMAX_STATE
  6. android.permission.BLUETOOTH
  7. android.permission.BLUETOOTH_ADMIN
  8. android.permission.BROADCAST_STICKY
  9. android.permission.CHANGE_NETWORK_STATE
  10. android.permission.CHANGE_WIFI_MULTICAST_STATE
  11. android.permission.CHANGE_WIFI_STATE
  12. android.permission.CHANGE_WIMAX_STATE
  13. android.permission.DISABLE_KEYGUARD
  14. android.permission.EXPAND_STATUS_BAR
  15. android.permission.FLASHLIGHT
  16. android.permission.GET_ACCOUNTS
  17. android.permission.GET_PACKAGE_SIZE
  18. android.permission.INTERNET
  19. android.permission.KILL_BACKGROUND_PROCESSES
  20. android.permission.MODIFY_AUDIO_SETTINGS
  21. android.permission.NFC
  22. android.permission.READ_SYNC_SETTINGS
  23. android.permission.READ_SYNC_STATS
  24. android.permission.RECEIVE_BOOT_COMPLETED
  25. android.permission.REORDER_TASKS
  26. android.permission.REQUEST_INSTALL_PACKAGES
  27. android.permission.SET_TIME_ZONE
  28. android.permission.SET_WALLPAPER
  29. android.permission.SET_WALLPAPER_HINTS
  30. android.permission.SUBSCRIBED_FEEDS_READ
  31. android.permission.TRANSMIT_IR
  32. android.permission.USE_FINGERPRINT
  33. android.permission.VIBRATE
  34. android.permission.WAKE_LOCK
  35. android.permission.WRITE_SYNC_SETTINGS
  36. com.android.alarm.permission.SET_ALARM
  37. com.android.launcher.permission.INSTALL_SHORTCUT
  38. com.android.launcher.permission.UNINSTALL_SHORTCUT

Just declare these permissions in AndroidManifest.xml and authorize them during installation. No need to check permissions every time you use the app, and users cannot revoke the above authorizations.

Make your app support new runtime permissions

It’s time to make our app support the new permission model, starting with setting compileSdkVersion and targetSdkVersion to 23.

  1. android {
  2. compileSdkVersion 23  
  3. ...
  4.  
  5. defaultConfig {
  6. ...
  7. targetSdkVersion 23  
  8. ...
  9. }

For example, I want to add a contact using the following method.

  1. private   static   final String TAG = "Contacts" ;
  2. private   void insertDummyContact() {
  3. // Two operations are needed to insert a new contact.  
  4. ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>( 2 );
  5.  
  6. // First, set up a new raw contact.  
  7. ContentProviderOperation.Builder op =
  8. ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
  9. .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null )
  10. .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null );
  11. operations.add(op.build());
  12.  
  13. // Next, set the name for the contact.  
  14. op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
  15. .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0 )
  16. .withValue(ContactsContract.Data.MIMETYPE,
  17. ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
  18. .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
  19. "__DUMMY CONTACT from runtime permissions sample" );
  20. operations.add(op.build());
  21.  
  22. // Apply the operations.  
  23. ContentResolver resolver = getContentResolver();
  24. try {
  25. resolver.applyBatch(ContactsContract.AUTHORITY, operations);
  26. } catch (RemoteException e) {
  27. Log.d(TAG, "Could not add a new contact: " + e.getMessage());
  28. } catch (OperationApplicationException e) {
  29. Log.d(TAG, "Could not add a new contact: " + e.getMessage());
  30. }
  31. }

The above code requires the WRITE_CONTACTS permission. If you don't ask for permission, the app will crash.

Next, add the permissions declaration in AndroidManifest.xml as before.

  1. <uses-permission android:name= "android.permission.WRITE_CONTACTS" />

Next, you have to write a method to check if there is permission. If not, a dialog box will pop up to ask the user for permission. Then you can create a contact in the next step.

Permissions are grouped as shown in the following table:

Paste_Image.png

Once any permission in the same group is granted, the other permissions are automatically granted as well. For example, once WRITE_CONTACTS is granted, the app also has READ_CONTACTS and GET_ACCOUNTS.
The methods used to check and request permissions in the source code are checkSelfPermission and requestPermissions of Activity. These methods were introduced in api23.

  1. final   private   int REQUEST_CODE_ASK_PERMISSIONS = 123 ;
  2.  
  3. private   void insertDummyContactWrapper() {
  4. int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
  5. if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
  6. requestPermissions( new String[] {Manifest.permission.WRITE_CONTACTS},
  7. REQUEST_CODE_ASK_PERMISSIONS);
  8. return ;
  9. }
  10. insertDummyContact();
  11. }

If the permission is already granted, insertDummyContact() will be executed. Otherwise, requestPermissions will be executed to pop up the authorization request dialog box, as follows:

Paste_Image.png

#p#

Regardless of whether the user agrees or refuses, the activity's onRequestPermissionsResult will be called back to notify the result (via the third parameter), grantResults, as follows:

  1. @Override  
  2. public   void onRequestPermissionsResult( int requestCode, String[] permissions, int [] grantResults) {
  3. switch (requestCode) {
  4. case REQUEST_CODE_ASK_PERMISSIONS:
  5. if (grantResults[ 0 ] == PackageManager.PERMISSION_GRANTED) {
  6. // Permission Granted  
  7. insertDummyContact();
  8. } else {
  9. // Permission Denied  
  10. Toast.makeText(MainActivity. this , "WRITE_CONTACTS Denied" , Toast.LENGTH_SHORT)
  11. .show();
  12. }
  13. break ;
  14. default :
  15. super .onRequestPermissionsResult(requestCode, permissions, grantResults);
  16. }
  17. }

This is how the new permissions model works. The code is really complicated but you just have to get used to it. . . In order to make your app compatible with the new permissions model, you have to use similar methods to handle all the necessary cases.
If you want to pound on the wall, now's the time. . .

Handling "Don't remind me"

If the user refuses an authorization, the next time a pop-up window appears, the user will have an option to "not remind me again" to prevent the app from requesting authorization in the future.

Paste_Image.png

If this option is checked by the user before denying authorization, the next time you requestPermissions for this permission, the dialog box will not pop up, and the result is that the app will do nothing.

This will be a very bad user experience, as the user performs an operation but does not get a response. This situation needs to be handled properly. Before requesting requestPermissions, we need to check whether we need to display a prompt for requesting permissions through the activity's shouldShowRequestPermissionRationale. The code is as follows:

  1. final   private   int REQUEST_CODE_ASK_PERMISSIONS = 123 ;
  2.  
  3. private   void insertDummyContactWrapper() {
  4. int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
  5. if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
  6. if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) {
  7. showMessageOKCancel( "You need to allow access to Contacts" ,
  8. new DialogInterface.OnClickListener() {
  9. @Override  
  10. public   void onClick(DialogInterface dialog, int which) {
  11. requestPermissions( new String[] {Manifest.permission.WRITE_CONTACTS},
  12. REQUEST_CODE_ASK_PERMISSIONS);
  13. }
  14. });
  15. return ;
  16. }
  17. requestPermissions( new String[] {Manifest.permission.WRITE_CONTACTS},
  18. REQUEST_CODE_ASK_PERMISSIONS);
  19. return ;
  20. }
  21. insertDummyContact();
  22. }
  23.  
  24. private   void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
  25. new AlertDialog.Builder(MainActivity. this )
  26. .setMessage(message)
  27. .setPositiveButton( "OK" , okListener)
  28. .setNegativeButton( "Cancel" , null )
  29. .create()
  30. .show();
  31. }

The dialog we wrote is shown when a permission is first requested and the user has marked not to be asked again.

In the latter case, onRequestPermissionsResult will receive PERMISSION_DENIED and the system query dialog box will not be displayed.

Paste_Image.png

Done!

Requesting multiple permissions at once

Of course, sometimes you need multiple permissions, you can use the above method to request multiple permissions at once. Don't forget to check the "Don't remind me again" setting for each permission.
Modified code:

  1. final   private   int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124 ;
  2.  
  3. private   void insertDummyContactWrapper() {
  4. List<String> permissionsNeeded = new ArrayList<String>();
  5.  
  6. final List<String> permissionsList = new ArrayList<String>();
  7. if (!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION))
  8. permissionsNeeded.add( "GPS" );
  9. if (!addPermission(permissionsList, Manifest.permission.READ_CONTACTS))
  10. permissionsNeeded.add( "Read Contacts" );
  11. if (!addPermission(permissionsList, Manifest.permission.WRITE_CONTACTS))
  12. permissionsNeeded.add( "Write Contacts" );
  13.  
  14. if (permissionsList.size() > 0 ) {
  15. if (permissionsNeeded.size() > 0 ) {
  16. // Need Rationale  
  17. String message = "You need to grant access to " + permissionsNeeded.get( 0 );
  18. for ( int i = 1 ; i < permissionsNeeded.size(); i++)
  19. message = message + ", " + permissionsNeeded.get(i);
  20. showMessageOKCancel(message,
  21. new DialogInterface.OnClickListener() {
  22. @Override  
  23. public   void onClick(DialogInterface dialog, int which) {
  24. requestPermissions(permissionsList.toArray( new String[permissionsList.size()]),
  25. REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
  26. }
  27. });
  28. return ;
  29. }
  30. requestPermissions(permissionsList.toArray( new String[permissionsList.size()]),
  31. REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
  32. return ;
  33. }
  34.  
  35. insertDummyContact();
  36. }
  37.  
  38. private   boolean addPermission(List<String> permissionsList, String permission) {
  39. if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
  40. permissionsList.add(permission);
  41. // Check for Rationale Option  
  42. if (!shouldShowRequestPermissionRationale(permission))
  43. return   false ;
  44. }
  45. return   true ;
  46. }

If all permissions are granted, onRequestPermissionsResult is still called back. I use hashmap to make the code neat and easy to read.

  1. @Override  
  2. public   void onRequestPermissionsResult( int requestCode, String[] permissions, int [] grantResults) {
  3. switch (requestCode) {
  4. case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS:
  5. {
  6. Map<String, Integer> perms = new HashMap<String, Integer>();
  7. // Initial  
  8. perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);
  9. perms.put(Manifest.permission.READ_CONTACTS, PackageManager.PERMISSION_GRANTED);
  10. perms.put(Manifest.permission.WRITE_CONTACTS, PackageManager.PERMISSION_GRANTED);
  11. // Fill with results  
  12. for ( int i = 0 ; i < permissions.length; i++)
  13. perms.put(permissions[i], grantResults[i]);
  14. // Check for ACCESS_FINE_LOCATION  
  15. if (perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
  16. && perms.get(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED
  17. && perms.get(Manifest.permission.WRITE_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
  18. // All Permissions Granted  
  19. insertDummyContact();
  20. } else {
  21. // Permission Denied  
  22. Toast.makeText(MainActivity. this , "Some Permission is Denied" , Toast.LENGTH_SHORT)
  23. .show();
  24. }
  25. }
  26. break ;
  27. default :
  28. super .onRequestPermissionsResult(requestCode, permissions, grantResults);
  29. }
  30. }

The conditions are flexible, you can set them yourself. In some cases, if a permission is not granted, it will not be available; but in other cases, it can work, but the performance is limited. I will not comment on this, you can design it yourself.

#p#

Use compatibility libraries to make your code compatible with older versions

The above code runs fine on Android 6.0 and above, but it doesn't work before 23 API because there are no such methods.

The crude way is to check the version

  1. if (Build.VERSION.SDK_INT >= 23 ) {
  2. // Marshmallow+  
  3. } else {
  4. // Pre-Marshmallow  
  5. }

But it is too complicated. I suggest using the v4 compatible library, which has been compatible with this. Use this method instead:

  • ContextCompat.checkSelfPermission()
    The function returns PERMISSION_GRANTED if granted, PERMISSION_DENIED otherwise, in all versions.
  • ActivityCompat.requestPermissions()
    When this method is called in versions prior to M, OnRequestPermissionsResultCallback is called directly with the correct PERMISSION_GRANTED or PERMISSION_DENIED.
  • ActivityCompat.shouldShowRequestPermissionRationale()
    When called in versions before M, it always returns false.
    Use these three methods in the v4 package, which are perfectly compatible with all versions! This method requires an additional parameter, Context or Activity. There is nothing special about it. Here is the code:
  1. private   void insertDummyContactWrapper() {
  2. int hasWriteContactsPermission = ContextCompat.checkSelfPermission( MainActivity.this ,
  3. Manifest.permission.WRITE_CONTACTS);
  4. if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
  5. if (!ActivityCompat.shouldShowRequestPermissionRationale(MainActivity. this ,
  6. Manifest.permission.WRITE_CONTACTS)) {
  7. showMessageOKCancel( "You need to allow access to Contacts" ,
  8. new DialogInterface.OnClickListener() {
  9. @Override  
  10. public   void onClick(DialogInterface dialog, int which) {
  11. ActivityCompat.requestPermissions( MainActivity.this ,
  12. new String[] {Manifest.permission.WRITE_CONTACTS},
  13. REQUEST_CODE_ASK_PERMISSIONS);
  14. }
  15. });
  16. return ;
  17. }
  18. ActivityCompat.requestPermissions( MainActivity.this ,
  19. new String[] {Manifest.permission.WRITE_CONTACTS},
  20. REQUEST_CODE_ASK_PERMISSIONS);
  21. return ;
  22. }
  23. insertDummyContact();
  24. }

The last two methods can also be used in Fragment, using the v13 compatibility package: FragmentCompat.requestPermissions() and FragmentCompat.shouldShowRequestPermissionRationale(). The effect is the same as activity.

Third-party libraries simplify code

The above code is really complicated. To solve this problem, many third-party libraries have been released, which are really awesome and fast. I tried many and finally found a satisfactory one, hotchemi's PermissionsDispatcher.
It is the same as what I did above, but the code is simplified. It is flexible and easy to expand. Give it a try. If you are not satisfied, you can find something else.

What will happen if my app is still open and the permissions are revoked?

Permissions can be revoked at any time.

Paste_Image.png

What happens when the app is open and is terminated? I tried this and found that the app is terminated. Everything in the app is simply stopped because it is terminated! This makes sense to me because if the system allows it to continue running (without certain permissions), it will summon Freddy into my nightmares. Or worse...

Conclusion and Recommendations

I believe you have a clear understanding of the new permission model. I believe you also realize the seriousness of the problem.

But you have no choice. The new runtime permissions have already been used in Marshmallow. We have no way back. The only thing we can do now is to make sure that the app adapts to the new permission model.

Fortunately, only a few permissions require a runtime permission model. Most commonly used permissions, such as network access, belong to Normal Permission and are automatically granted during installation. Of course, you have to declare them and no need to check them later. Therefore, only a small part of the code you need to modify.

Two suggestions:

1. Take the new permissions model seriously

2. If your code does not support the new permissions, do not set targetSdkVersion 23. Especially when you create a new project in Studio, don't forget to modify it!

Let’s talk about code modification. This is a big deal. If the code structure is not designed well enough, you need some painful refactoring. Every app needs to be corrected. As mentioned above, we have no choice. . .

List all the permissions you need to request, and what happens if A is granted and B is denied. blah, blah.

Good luck with the refactoring. Make it a priority that you need to do, and start working on it now to ensure that there are no issues when M is officially released.

I hope you found this article useful, and happy coding!

<<:  Language is an interface

>>:  Facebook Internal Efficient Work PPT Guide

Recommend

Do all airplanes look the same? Actually, they are very different.

Compared to the 1950s and 1960s, when there were ...

Learn Bazi from scratch, learn Bazi Jiugongge from scratch!

Learn Bazi from scratch, learn Bazi Jiugongge fro...

What are the conditions for activating a 400 telephone number?

The 400 telephone number is a relatively popular ...

Marketing methods that keep users buying

I found that being in love makes people ugly. Tak...

WeChat product analysis report!

one. Product Information 1. Product Name: WeChat ...

How much does it cost to create an automatic food ordering app in Hexian County?

How much is the quotation for automatic ordering ...

Does the Lanzhou Mini Program Mall need to apply for a business license?

Can an e-commerce business license be used to ope...

2020 Zhihu Analysis Report

Recently, the business direction of the docking h...