Bug 1396292 - Part 1 - Provide facilities to explicitly run permissions check callbacks on the background thread. r?sebastian
The permissions check itself is synchronous, but if we then decide to prompt the user to acquire the permission, we have to do so asynchronously and eventually continue execution on the UI thread as a result. Therefore we need to provide a counterpart of onUIThread() for operations that want their callback to stay off the UI thread in all situations.
MozReview-Commit-ID: AOCX1v69R1J
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionBlock.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionBlock.java
@@ -15,16 +15,17 @@ import android.support.annotation.NonNul
* Helper class to run code blocks depending on whether a user has granted or denied certain runtime permissions.
*/
public class PermissionBlock {
private final PermissionsHelper helper;
private Context context;
private String[] permissions;
private boolean onUIThread;
+ private boolean onBackgroundThread;
private Runnable onPermissionsGranted;
private Runnable onPermissionsDenied;
private boolean doNotPrompt;
/* package-private */ PermissionBlock(Context context, PermissionsHelper helper) {
this.context = context;
this.helper = helper;
}
@@ -41,17 +42,27 @@ public class PermissionBlock {
* Execute all callbacks on the UI thread.
*/
public PermissionBlock onUIThread() {
this.onUIThread = true;
return this;
}
/**
+ * Execute all callbacks on the background thread.
+ */
+ public PermissionBlock onBackgroundThread() {
+ this.onBackgroundThread = true;
+ return this;
+ }
+
+ /**
* Do not prompt the user to accept the permission if it has not been granted yet.
+ * This also guarantees that the callback will run on the current thread if no callback
+ * thread has been explicitly specified.
*/
public PermissionBlock doNotPrompt() {
doNotPrompt = true;
return this;
}
/**
* If the condition is true then do not prompt the user to accept the permission if it has not
@@ -111,18 +122,24 @@ public class PermissionBlock {
executeRunnable(onPermissionsDenied);
}
private void executeRunnable(Runnable runnable) {
if (runnable == null) {
return;
}
- if (onUIThread) {
+ if (onUIThread && onBackgroundThread) {
+ throw new IllegalStateException("Cannot run callback on more than one thread");
+ }
+
+ if (onUIThread && !ThreadUtils.isOnUiThread()) {
ThreadUtils.postToUiThread(runnable);
+ } else if (onBackgroundThread && !ThreadUtils.isOnBackgroundThread()) {
+ ThreadUtils.postToBackgroundThread(runnable);
} else {
runnable.run();
}
}
/* package-private */ String[] getPermissions() {
return permissions;
}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/Permissions.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/Permissions.java
@@ -29,18 +29,21 @@ import java.util.concurrent.FutureTask;
* Permissions.from(activity)
* .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
* .onUiThread()
* .andFallback(onPermissionDenied())
* .run(onPermissionGranted())
*
* This example will run the runnable returned by onPermissionGranted() if the WRITE_EXTERNAL_STORAGE permission is
* already granted. Otherwise it will prompt the user and run the runnable returned by onPermissionGranted() or
- * onPermissionDenied() depending on whether the user accepted or not. If onUiThread() is specified then all callbacks
- * will be run on the UI thread.
+ * onPermissionDenied() depending on whether the user accepted or not.
+ * <p>
+ * If onUiThread()/onBackgroundThread() is specified, all callbacks will be run on the respective
+ * thread. Otherwise, the callback may run on the current thread, but will switch to the UI thread
+ * if a permissions prompt is required.
*/
public class Permissions {
private static final Queue<PermissionBlock> waiting = new LinkedList<>();
private static final Queue<PermissionBlock> prompt = new LinkedList<>();
private static PermissionsHelper permissionHelper = new PermissionsHelper();
/**
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/permissions/TestPermissions.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/permissions/TestPermissions.java
@@ -218,16 +218,30 @@ public class TestPermissions {
.withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.doNotPromptIf(true)
.andFallback(mock(Runnable.class))
.run(mock(Runnable.class));
verify(helper, never()).prompt(anyActivity(), any(String[].class));
}
+ @Test(expected = IllegalStateException.class)
+ public void testCannotRunCallbackOnMultipleThreads() {
+ PermissionsHelper helper = mockDenyingHelper();
+ Permissions.setPermissionHelper(helper);
+
+ Permissions.from(mockActivity())
+ .onBackgroundThread()
+ .onUIThread()
+ .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ .doNotPromptIf(true)
+ .andFallback(mock(Runnable.class))
+ .run(mock(Runnable.class));
+ }
+
private Activity mockActivity() {
return mock(Activity.class);
}
private PermissionsHelper mockGrantingHelper() {
PermissionsHelper helper = mock(PermissionsHelper.class);
doReturn(true).when(helper).hasPermissions(any(Context.class), anyPermissions());
return helper;