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

前端 未结 5 1467
既然无缘
既然无缘 2021-02-01 13:09

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
   // ba         


        
相关标签:
5条回答
  • 2021-02-01 13:51

    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");
        }
    
    }
    
    0 讨论(0)
  • 2021-02-01 13:53

    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.

    0 讨论(0)
  • 2021-02-01 13:58

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

    0 讨论(0)
  • 2021-02-01 14:06

    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.

    0 讨论(0)
  • 2021-02-01 14:10

    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.

    0 讨论(0)
提交回复
热议问题