Wednesday, October 21, 2015

Tips & Tricks: Dangerous Permissions, Part 1

The biggest new feature of Marshmallow is the permissions model change.  A permission is now either dangerous or normal.  Normal permissions behave just like the old permissions.  They are automatically granted at app download and cannot revoked by the User.

Dangerous permissions are the really interesting ones because the user must explicitly grant the app that permission at run-time.   The user can also revoke the permission anytime via the device settings app.

Even if the permission is normal, it can be classed into a grouping that is revocable.  This doesn’t mean that the user can deny your specific permission, but it will cause your app to recreate if the user revokes it in the background.

Basics

If you don’t have permission for a feature, then attempting to use that feature will cause an exception.  To avoid that you need to do the following each time you want to use the feature protected by the permission:

Permission Model - New Page

Obviously it look a little different in code so here is my step-by-step implementation.

Checking Permissions:

This all done in a custom requestPermissions method of the MainActiviity:

   1: private void requestPermissions() {
   2:  
   3:         //use the static ActivityCompat methods if the minimum app level is less than 23.
   4:         if (ActivityCompat.checkSelfPermission(this, permission.GET_ACCOUNTS) == PackageManager.PERMISSION_DENIED) {
   5:  
   6:             //The permission is denied, so ask the OS if we should show a rationale that explains
   7:             //why the permission is needed.
   8:  
   9:             if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission.GET_ACCOUNTS)) {
  10:                 showRationale();
  11:             }
  12:             else {
  13:                 requestAccountsPermission();
  14:             }
  15:         }
  16:         else {
  17:             Toast.makeText(this, "Activity: Permission Already Granted!", Toast.LENGTH_LONG).show();
  18:         }
  19:     }

This method uses the ActivityCompat static methods to check the permissions.  Using those methods eliminates the pesky logic breaks for handling Marshmallow vs pre-Marshmallow devices. 


Basically,  it calls checkSelfPermission to determine if the desired permission is granted.  If it is then is just shows a toast.  If then permission is denied, then it needs to determine whether to show a rationale for the permission to the user.  This is done via the shouldShowRequestPermissionRationale method.  This will normally return true only if the user has denied the permission before.  You are not obligated to show the rationale, but it is a really good idea to give the user an explanation for why they should grant the permission.


How and what to show in the rationale is completely up to you.  In the example, I use a Snackbar.  When the user clicks on the Snackbar’s “Click Me” I request the OS to display the Permission prompt.  This is done in the requestAccountsPermission method.



   1: private void requestAccountsPermission() {
   2:      //use the static ActivityCompat methods if the minimum app level is less than 23.
   3:      ActivityCompat.requestPermissions(MainActivity.this, PERMISSIONS, PERMISSION_REQUEST_CODE);
   4:  }

This method calls the ActivityCompat’s requestPermissions method.  This method takes a string array of the permissions to request and a code to identify the request.



   1:  
   2:     private static final int PERMISSION_REQUEST_CODE = 1;
   3:  
   4:     private static final String PERMISSIONS[] = {permission.GET_ACCOUNTS};

Since the user prompt for the permissions is handled by the OS, the activity will get the response when its onRequestPermissionsResult method is called.



   1:  
   2:     @Override
   3:     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
   4:         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
   5:  
   6:         if (requestCode == PERMISSION_REQUEST_CODE) {
   7:             if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
   8:                 Toast.makeText(this, "Activity: Permission Granted!", Toast.LENGTH_LONG).show();
   9:             }
  10:             else {
  11:                 Toast.makeText(this, "Activity: Permission Denied!", Toast.LENGTH_LONG).show();
  12:             }
  13:         }
  14:  
  15:     }

From here you can proceed with the feature if the permission is granted or discontinue if the permission is still denied.


Fragment Quirk


Fragments can follow the same process for requesting permissions, but be careful cannot using ActivityCompat methods. If the ActivityCompat requestPermissions method is called, then the Fragments onRequestPermissionsResult will not be called. Ever.   Instead,  use the built-in requestPermissions method only.


Example


The example can be found in my GitHub PermissionsExample repository.

No comments :

Post a Comment