Bug 1396292 - Part 1 - Provide facilities to explicitly run permissions check callbacks on the background thread. r?sebastian draft
authorJan Henning <jh+bugzilla@buttercookie.de>
Sat, 02 Sep 2017 21:22:30 +0200
changeset 658743 62a0ee11c329bf081095e8c4a61f93911e358c40
parent 658018 03d235f8b8588660a6bbd0d80891a587d6b57c1e
child 658744 713a1765ba69c7cedeaf5980d8ee9ea9d3176ed9
push id77865
push usermozilla@buttercookie.de
push dateMon, 04 Sep 2017 20:07:24 +0000
reviewerssebastian
bugs1396292
milestone57.0a1
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;