45

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]."

fattire
  • 6,823
  • 3
  • 25
  • 38
  • i not think it will be a problem to UI-thread blocking. even before Marshmallow, we do much asking-dialogs and invoke different codes depends the user decision, all are asynchronized. – Jiang YD Aug 21 '15 at 07:16
  • How do you imagine a synchronous method to work? If can't just click the UI thread. The way things are now is actually extremely simple, just a few lines of code. I'd imagine a synchronous option to actually be more complicated. The result would just be more code bunched together in one place instead of the nice way things are handled now. – Xaver Kapeller Jun 24 '16 at 08:13
  • It just comes down to having a nice clean design and architecture for your app, I find that runtime permissions integrate very well in to this whole system even if I wish that the crucial method would not be part of the `Activity` class. However I can imagine a few very good reasons why that might be the case. – Xaver Kapeller Jun 24 '16 at 08:16
  • And just by the way: There is quick fix in Android Studio to insert the runtime permission checks. And I never had to restructure anything to integrate them. – Xaver Kapeller Jun 24 '16 at 08:18
  • Things are simple if you're lucky enough to already have asynchronous permission-needing code. But I've given a few examples where an app may not have been designed with this in mind, as in a method that returns success/failure that may be called repeatedly in a myriad of contexts throughout an app. It must be completely refactored. Passing numerous "permissions request response" codes, ie. context for every type of permission check, through the result method, then always handling each scenario (per instance, per permission, per result) correctly can easily turn code into spaghetti quickly. – fattire Jun 26 '16 at 03:33
  • Try this it may be work http://stackoverflow.com/a/41221852/5488468 – Bipin Bharti Jan 03 '17 at 11:06
  • Look to my answer [here](http://stackoverflow.com/a/42313408/2441637), hope it be helpful for you. – Hasan A Yousef Feb 18 '17 at 09:29
  • check my answer [here](http://stackoverflow.com/a/42313408/2441637) for a similar question. – Hasan A Yousef Feb 18 '17 at 09:32
  • As Tor Norbye said: restructure your code as a state machine. Lucky for me, my app was already MVC-pattern structured and the Activity/Controller already implemented a FSM, so every time `onRequestPermissionsResult()` is invoked, I know exactly in what state I am and don't have to rely much on the permissions request response code to transition to the other states. – PJ_Finnegan Mar 19 '19 at 13:08

5 Answers5

11

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.

Community
  • 1
  • 1
user149408
  • 5,385
  • 4
  • 33
  • 69
  • I call **`requestPermissions()`** once and they take effect for all Activities in that process, as well as my background service which runs in an entirely separate process that is launched with an intent after the sdcard-write permission is granted (to the "foreground" Activity that started it). – samus Jun 05 '17 at 17:25
  • Indeed, that would probably work: when your app first starts up, request all the permissions it’s ever going to need. That might work for one or maybe two permissions, but may get tedious for the first-time user if they have to acknowledge some five permissions before they first get to touch your app. – user149408 Jun 05 '17 at 23:14
  • As far as I know the only permissions that require this extra level of security are sdcard write access and the camera (although I could still launch the camera without first prompting the user with requestPermissions dialog). – samus Jun 06 '17 at 15:39
  • As far as I know this affects all permissions, though the request is per-category. Contacts and Location also definitely need to be requested. Caveat: if the app targets a lower API level, the old behavior remains in place, with all permissions granted at install time. – user149408 Jun 06 '17 at 20:04
  • I can see your approach being applicable, and formality is always good. I guess for my specific scenario I need write immediately upon startup for config file. But yeah, if a lot of permissions are potentially required, it's probably a good idea to wait until they are actually needed from a UX standpoint (as opposed to annoying user with a bunch of popups... aint nobody got time fodat). However, if there is a way to request all permissions in a single dialog (like when installing from the store), then I guess that wouldn't be so bad. – samus Jun 07 '17 at 15:20
  • I would give my big toe for the equivalent of the C# `await` keyword in Java. – Bassinator Jun 27 '17 at 20:18
2

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().

greywolf82
  • 21,813
  • 18
  • 54
  • 108
1

So I hate to answer my own question specifically with respect to the android.permission.WRITE_EXTERNAL_STORAGE permission used in my example, but what the heck.

For reading and/or writing a file, there is actually a way to completely avoid having to ask for and then check for the permission and thus bypass the whole flow I described above. In this way, the saveFile (Url url, String content) method I gave as an example could continue to work synchronously.

The solution, which I believe works in API 19+, eliminates the need for the WRITE_EXTERNAL_STORAGE permission by having a DocumentsProvider act as a "middleman" to basically ask the user on your app's behalf "please select the specific file to write to" (ie, a "file picker") and then once the user chooses a file (or types in a new file name), the app is now magically granted permission to do so for that Uri, since the user has specifically granted it.

No "official" WRITE_EXTERNAL_STORAGE permission needed.

This way of kind of borrowing permissions is part of the Storage Access Framework, and a discussion about this was made at the Big Android BBQ by Ian Lake. Here's a video called Forget the Storage Permission: Alternatives for sharing and collaborating that goes over the basics and how you specifically use it to bypass the WRITE_EXTERNAL_STORAGE permission requirement entirely.

This doesn't completely solve the sync/async permissions problem for all cases, but for any type of external document, or even one that is offered by a provider (such as gDrive, Box.net, Dropbox, etc.) this may be a solution worth checking out.

fattire
  • 6,823
  • 3
  • 25
  • 38
1

Here's how I solved the "synchronicity" issue without having to explicitly block (and busy-wait), or without requiring a separate "boot-loader" Activity. I refactored splash screen Activity as follows:

update: A more complete example can be found here.


note: Because the requestPermissions() API calls startActivityForResult()

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
}

the main view-creation logic was moved from OnCreate() to OnCreate2(), and OnCreate() now handles the permissions check. If RequestPermissions() needs to be called, then the associated OnRequestPermissionsResult() restarts this activity (forwarding a copy of the original bundle).


[Activity(Label = "MarshmellowActivated",
    MainLauncher = true,      
    Theme = "@style/Theme.Transparent",
    Icon = "@drawable/icon"        
    ////////////////////////////////////////////////////////////////
    // THIS PREVENTS OnRequestPermissionsResult() from being called
    //NoHistory = true
)]
public class MarshmellowActivated : Activity
{
    private const int ANDROID_PERMISSION_REQUEST_CODE__SDCARD = 112;
    private Bundle _savedInstanceState;

    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
    {
        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode)
        {
            case ANDROID_PERMISSION_REQUEST_CODE__SDCARD:
                if (grantResults.Length > 0 && grantResults[0] == Permission.Granted)
                {       
                    Intent restartThisActivityIntent = new Intent(this, this.GetType());
                    if (_savedInstanceState != null)
                    {
                        // *ref1: Forward bundle from one intent to another
                        restartThisActivityIntent.PutExtras(_savedInstanceState);
                    }
                    StartActivity(restartThisActivityIntent);
                }
                else
                {                   
                    throw new Exception("SD Card Write Access: Denied.");
                }
                break;
        }
    }

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Android v6 requires explicit permission granting from user at runtime for extra sweet security goodness
        Permission extStoragePerm = ApplicationContext.CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage);

        //if(extStoragePerm == Permission.Denied)
        if (extStoragePerm != Permission.Granted)
        {
            _savedInstanceState = savedInstanceState;
            // **calls startActivityForResult()**
            RequestPermissions(new[] { Android.Manifest.Permission.WriteExternalStorage }, ANDROID_PERMISSION_REQUEST_CODE__SDCARD);
        }
        else
        {
            OnCreate2(savedInstanceState);
        }
    }

    private void OnCreate2(Bundle savedInstanceState)
    {
        //...
    }
}

ref1: Forward bundle from one intent to another

note: This can be refactored to handle more permissions generally. It currently handles the sdcard write permission only, which should convey the pertinent logic with sufficient clarity.

samus
  • 6,102
  • 6
  • 31
  • 69
0

You can Add a blocking helper method like this:

@TargetApi(23) 
public static void checkForPermissionsMAndAboveBlocking(Activity act) {
    Log.i(Prefs.TAG, "checkForPermissions() called");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        // Here, thisActivity is the current activity
        if (act.checkSelfPermission(
                Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {


            // No explanation needed, we can request the permission.
            act.requestPermissions(
                    new String[]{
                          Manifest.permission.WRITE_EXTERNAL_STORAGE
                    },
                    0);

            while (true) {
                if (act.checkSelfPermission(
                        Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        == PackageManager.PERMISSION_GRANTED) {

                    Log.i(Prefs.TAG, "Got permissions, exiting block loop");
                    break;
                }
                Log.i(Prefs.TAG, "Sleeping, waiting for permissions");
                try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
            }

        }
        // permission already granted
        else {
            Log.i(Prefs.TAG, "permission already granted");
        }
    }
    else {
        Log.i(Prefs.TAG, "Below M, permissions not via code");
    }

}
Eli
  • 707
  • 8
  • 16
  • 3
    That's insane, you do know that blocking the UI thread is a terrible idea and that there are much better ways to block a thread than to have an infinite loop with Thread.sleep commands in it, right? – Xaver Kapeller Jun 24 '16 at 08:20
  • In most cases I agree, but in this case, the call should be done just when the app is starting, so, its less terrible. Do you have a better solution? – Eli Jun 24 '16 at 08:31
  • In my case its choosing between this, and app crash. Till I have a better solution, this is my solution. – Eli Jun 24 '16 at 08:47
  • 1
    It's still terrible how do you expect anything to work properly if you do this? And why would you do it anyway? What if the benefit of boxing the main thread and crippling your apps performance and user experience? You don't need to do any of that. If you need to request a permission right at the start then what is stopping you from doing that? Nothing. All this does is that the user can't even properly interact with the permission dialog because the UI thread sleeps constantly for no reason. – Xaver Kapeller Jun 24 '16 at 08:47
  • Why would the app crash? There is no reason for that unless you are doing something terribly wrong. – Xaver Kapeller Jun 24 '16 at 08:48
  • The app crashes because it needs sdcard access (which it doesn't have unless this call). I don't think I'm doing nothing terribly wrong. I just have a use case in which I need to get the permissions right at the start of the app, and I have to use blocking permissions. – Eli Jun 24 '16 at 08:54
  • Ofcouse it make more sense to get the permissions asynchronously before the sdcard access is called. But in my specific use case, the sdcard is accessed immediately when the first activity is called, and there is no way around it. – Eli Jun 24 '16 at 08:58
  • 1
    Why did it need to go that immediately? Because it has to read or write data to the SD card? There is no reason you can't start doing that in `onRequestPermissionsResult()` instead of immediately. In fact most of the time you can start of immediately - if `selfPermissionCheck()` returns that the permission has been granted. You just need to add that if and then also start the process in `onRequestPermissionsResult()`. – Xaver Kapeller Jun 24 '16 at 09:00
  • 1
    There is definitely a way around that. Because all you are thing right now is replicating existing functionality with a terrible solution. Instead of waiting for the callback you are blocking the thread and waiting until it has been granted. Obviously nothing needs to happen immediately. – Xaver Kapeller Jun 24 '16 at 09:02
  • I'm aware of the onRequestPermissionsResult() callback, and I agree, your solution is good for most cases. In my case I can't use this callback (for reasons I will not go into now). I need a synchronous call (as the original post question), which Android API don't offer. – Eli Jun 24 '16 at 09:14
  • 1
    because it can't and shouldn't offer that. – Xaver Kapeller Jun 24 '16 at 09:40
  • 1
    BTW, Google acknowledged the need for a synchronous call, hopefully it will be added soon – Eli Jun 24 '16 at 09:42
  • 1
    Show me where they acknowledge that. I would be very interested in reading this. Because it would go against everything they have ever done we've it comes to an api and would make no sense what so ever. A synchronous call like that makes no sense. It just like giving someone a gun so he can shoot himself in the foot. – Xaver Kapeller Jun 24 '16 at 09:45
  • 2
    I can't find any indication that they have or have ever had plans to add that. – Xaver Kapeller Jun 24 '16 at 09:48
  • 2
    So what some engineer says on a podcast more than a year ago is Google acknowledging that this needs to be added? And of course he just talks about this as a workaround until the quick fix could be added to Android Studio. (which it now has). Do whatever you want in your code, I can just give you the advice: fix whatever problem you are having with this. What you are doing is bad. You can't just block the main thread. Find another solution. – Xaver Kapeller Jun 24 '16 at 09:55
  • Blocking the UI thread is bad because the device will not be responsive to the user. Use causes when using the code I posted above, you need to know exactly what you are doing (and when to use it), or else, you will indeed block the UI Thread. – Eli Sep 11 '16 at 08:35