Bug 1396292 - Part 1 - Provide facilities to explicitly run permissions check callbacks on the background thread. r=sebastian
authorJan Henning <jh+bugzilla@buttercookie.de>
Sat, 02 Sep 2017 21:22:30 +0200
changeset 428438 6949a7ee45ee4e3f10a5ba437acf643eb75dd52b
parent 428437 9d0a3883066e1111764391a340bfdd109f2be7e2
child 428439 1320c22e21796cb54f02d1712e28b78066fad6c0
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssebastian
bugs1396292
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionBlock.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/Permissions.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/permissions/TestPermissions.java
--- 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;