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]."
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.
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.
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.
To ask permission, we need to use ActivityCompat. requestPermission() method with the permission name and the request code.
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.
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().
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With