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]."
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:
.
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);
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.