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
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");
}
}
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.
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().
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.
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.