Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you request permissions synchronously in Android Marshmallow (API 23)'s runtime permissions model?

Say you have a method like this:

public boolean saveFile (Url url, String content) {     // save the file, this can be done a lot of different ways, but    // basically the point is...     return save_was_successful; } 

Throughout your app, if you want to save a file to external storage, you do something like...

if (saveFile(external_storage_url, "this is a test")) {    // yay, success! } else { // notify the user something was wrong or handle the error } 

This is a simplified example, so don't get on my case about blocking the UI, handling exceptions properly, etc. If you don't like file saving, you could imagine a getContact() or getPhoneState() or whatever. The point is it's a permission-needing operation that returns some value(s) and it's used throughout the app.

In Android <= Lollipop, if the user had installed and agreed to granting android.permission.WRITE_EXTERNAL_STORAGE or whatever, all would be good.

But in the new Marshmallow (API 23) runtime permission model, before you can save the file to external storage, you're supposed to (1) check if the permission has been granted. If not, possibly (2) show a rationale for the request (if the system thinks it's a good idea) with a toast or whatever and (3) ask the user to grant permission via a dialog, then basically sit back and wait for a callback...

(so your app sits around, waiting...)

(4) When the user finally responds to the dialog, the onRequestPermissionsResult() method fires off, and your code now (5) has to sift through WHICH permission request they are actually responding to, whether the user said yes or no (to my knowledge there's no way to handle "no" vs. "no and don't ask again"), (6) figure out what they were trying to accomplish in the first place that prompted the whole ask-for-permissions process, so that the program can finally (7) go ahead and do that thing.

To know what the user was trying to do in step (6) involves having previously passed a special code (the "permissions request response") which is described in the docs as an identifier of the type of permission request (camera/contact/etc.) but seems to me more like a "specifically, what you were trying to do when you realized you'd need to ask for permissions" code, given that the same permission/group may be used for multiple purposes in your code, so you'd need to use this code to return execution to the appropriate place after getting the permission.

I could be totally misunderstanding how this is supposed to work-- so please let me know if I'm way off-- but the larger point is I'm really not sure how to even think about doing all of the above w/the saveFile() method described previously due to the asynchronous "wait for the user to respond" part. The ideas I've considered are pretty hacky and certainly wrong.

Today's Android Developer Podcast hinted that there may be a synchronous solution around the corner, and there was even talk about a magic, one-step alt-enter type of "add a permissions request" tool in Android Studio. Still, how the runtime permission process might be shoved into a saveFile() or whatever-- I'm thinking something along the lines of :

public boolean saveFile(Url url, String content) {    //   this next line will check for the permission, ask the user    //   for permission if required, maybe even handle the rationale    //   situation    if (!checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,                         R.string.permission_storage_rationale))        {            return false; // or throw an exception or whatever        } else {     // try to save the file     return save_was_successful;     } } 

So the above checkPermission() would fail if the user didn't have and also refused to grant the permission. Maybe one could use a loop around checkPermission() to try asking up to 3 times or something, or better would be if a sane dont-be-annoying-policy was handled by the method.

Is such a thing possible? Desirable? Would any such solution block the UI thread? From the podcast it sounded like Google may have a solution like this coming 'round the corner, but I wanted to get thoughts on whether there was something-- a convenience class, a pattern, something-- that doesn't involve everyone having to refactor all permission-requiring operations, which I must assume could get pretty messy.

Sorry for the long-winded question, but I wanted to be as complete as possible. I'll take my answer off the air. Thanks!


Update: Here is the transcript from the podcast mentioned above.

Listen at about 41:20. In this discussion:

Rough transcript:

Tor Norbye (Tools team): "So it doesn't seem like it should be a lot of work for developers. But I understand part of the problem is that these are not synchronous calls, right?. So what you have to- actually change the way your activity is written to have a callback-- so it's really kinda like a state machine where... for this state, you-"

Poiesz (product manager): "Ah- I thought- there was a- there might be an option for synchronous response--"

Norbye: "Oh. that would make things--"

Poiesz: "I can talk with folks internally. I recall a discussion about synchronous- but we can find out."

Norbye: "Yeah. In fact we should probably make it into the tools. Where you have an easy refactoring..."

Then he talks about using annotations in the tools to determine which APIs require permissions.. (which as of now doesn't work that great IMO) and how he wants the tools someday to actually generate the required code if it finds an unchecked "dangerous" method call:

Norbye: "...then if you're on M as well it'll say, 'hey, are you actually checking for this permission or are you catching security exceptions?', and if you're not, we'll say 'you probably need to do something for requesting the permission here.' What I would like though is for that to have a quick fix where you can go 'CHING!' and it inserts all the right stuff for asking, but the way things were back when I looked, this required restructuring a lot of things-- adding interfaces and callbacks, and changing the flow, and that we couldn't do. But if there's an easy synchronous mode as a temporary thing or a permanent thing, that would [be great]."

like image 800
fattire Avatar asked Aug 21 '15 07:08

fattire


People also ask

How do I request multiple permissions on Android?

In case one or more permissions are not granted, ActivityCompat. requestPermissions() will request permissions and the control goes to onRequestPermissionsResult() callback method. You should check the value of shouldShowRequestPermissionRationale() flag in onRequestPermissionsResult() callback method.

How do I request runtime permission on Android?

checkSelfPermission(String perm); It returns an integer value of PERMISSION_GRANTED or PERMISSION_DENIED. Note: If a user declines a permission that is critical in the app, then shouldShowRequestPermissionRationale(String permission); is used to describe the user the need for the permission.

What are runtime permissions in Android?

Runtime permissions prevent apps from gaining access to private data without a user's consent, and provide them with additional context and visibility into the types of permissions that applications are either seeking, or have been granted.

How do you ask permission runtime again if the user deny for the first time?

To ask permission, we need to use ActivityCompat. requestPermission() method with the permission name and the request code.


2 Answers

As of Marshmallow, my understanding is that you can't.

I had to solve the same issue in my app. Here's how I did it:

  1. Refactoring: Move every chunk of code that depends on permissions of some kind into a method of its own.
  2. More refactoring: Identify the trigger for each method (such as starting an activity, tapping a control etc.) and group methods together if they have the same trigger. (If two methods end up having the same trigger AND requiring the same set of permissions, consider merging them.)
  3. Even more refactoring: Find out what depends on the new methods having been called before, and make sure it is flexible about when these methods are called: Either move it into the method itself, or at least make sure that whatever you do doesn't throw exceptions if your method hasn't been called before, and that it starts behaving as expected as soon as the method is called.
  4. Request codes: For each of these methods (or groups thereof), define an integer constant to be used as a request code.
  5. Checking and asking for permissions: Wrap each of these methods/groups of methods into the following code:

.

if (ContextCompat.checkSelfPermission(this, Manifest.permission.SOME_PERMISSION) == PackageManager.PERMISSION_GRANTED)     doStuffThatRequiresPermission(); else     ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SOME_PERMISSION}, Const.PERM_REQUEST_DO_STUFF_THAT_REQUIRES_PERMISSION); 
  1. Handling responses: Write an implementation for onRequestPermissionsResult() in every Activity that asks for permissions. Check if the requested permissions were granted, and use the request code to determine what method needs to be called.

Be aware that the API requires an Activity for runtime permission requests. If you have non-interactive components (such as a Service), take a look at How to request permissions from a service in Android Marshmallow for advice on how to tackle this. Basically, the easiest way is to display a notification which will then bring up an Activity which does nothing but present the runtime permissions dialog. Here is how I tackled this in my app.

like image 76
user149408 Avatar answered Sep 24 '22 04:09

user149408


Short answer: no, there isn't any sync operation today. You have to check if you have the right permission before to complete the operation or as last option, you could put a try/catch block for the security exception. In the catch block you can inform the user that the operation failed due to permission problems. In addition, there is another point: when a permission is revoked the app doesn't restart from main activity, so you have to check for permission even in your onResume().

like image 34
greywolf82 Avatar answered Sep 23 '22 04:09

greywolf82