summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/Permissions.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/Permissions.java')
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/Permissions.java210
1 files changed, 210 insertions, 0 deletions
diff --git 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
new file mode 100644
index 000000000..c1b38f61c
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/Permissions.java
@@ -0,0 +1,210 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.permissions;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.support.annotation.NonNull;
+
+import org.mozilla.gecko.util.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Convenience class for checking and prompting for runtime permissions.
+ *
+ * Example:
+ *
+ * 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.
+ */
+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();
+
+ /**
+ * Entry point for checking (and optionally prompting for) runtime permissions.
+ *
+ * Note: The provided context needs to be an Activity context in order to prompt. Use doNotPrompt()
+ * for all other contexts.
+ */
+ public static PermissionBlock from(@NonNull Context context) {
+ return new PermissionBlock(context, permissionHelper);
+ }
+
+ /**
+ * This method will block until the specified permissions have been granted or denied by the user.
+ * If needed the user will be prompted.
+ *
+ * @return true if all of the permissions have been granted. False if any of the permissions have been denied.
+ */
+ public static boolean waitFor(@NonNull Activity activity, String... permissions) {
+ ThreadUtils.assertNotOnUiThread(); // We do not want to block the UI thread.
+
+ // This task will block until all of the permissions have been granted
+ final FutureTask<Boolean> blockingTask = new FutureTask<>(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return true;
+ }
+ });
+
+ // This runnable will cancel the task if any of the permissions have been denied
+ Runnable cancelBlockingTask = new Runnable() {
+ @Override
+ public void run() {
+ blockingTask.cancel(true);
+ }
+ };
+
+ Permissions.from(activity)
+ .withPermissions(permissions)
+ .andFallback(cancelBlockingTask)
+ .run(blockingTask);
+
+ try {
+ return blockingTask.get();
+ } catch (InterruptedException | ExecutionException | CancellationException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Determine whether you have been granted particular permissions.
+ */
+ public static boolean has(Context context, String... permissions) {
+ return permissionHelper.hasPermissions(context, permissions);
+ }
+
+ /* package-private */ static void setPermissionHelper(PermissionsHelper permissionHelper) {
+ Permissions.permissionHelper = permissionHelper;
+ }
+
+ /**
+ * Callback for Activity.onRequestPermissionsResult(). All activities that prompt for permissions using this class
+ * should implement onRequestPermissionsResult() and call this method.
+ */
+ public static synchronized void onRequestPermissionsResult(@NonNull Activity activity, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ processGrantResults(permissions, grantResults);
+
+ processQueue(activity, permissions, grantResults);
+ }
+
+ /* package-private */ static synchronized void prompt(Activity activity, PermissionBlock block) {
+ if (prompt.isEmpty()) {
+ prompt.add(block);
+ showPrompt(activity);
+ } else {
+ waiting.add(block);
+ }
+ }
+
+ private static synchronized void processGrantResults(@NonNull String[] permissions, @NonNull int[] grantResults) {
+ final HashSet<String> grantedPermissions = collectGrantedPermissions(permissions, grantResults);
+
+ while (!prompt.isEmpty()) {
+ final PermissionBlock block = prompt.poll();
+
+ if (allPermissionsGranted(block, grantedPermissions)) {
+ block.onPermissionsGranted();
+ } else {
+ block.onPermissionsDenied();
+ }
+ }
+ }
+
+ private static synchronized void processQueue(Activity activity, String[] permissions, int[] grantResults) {
+ final HashSet<String> deniedPermissions = collectDeniedPermissions(permissions, grantResults);
+
+ while (!waiting.isEmpty()) {
+ final PermissionBlock block = waiting.poll();
+
+ if (block.hasPermissions(activity)) {
+ block.onPermissionsGranted();
+ } else {
+ if (atLeastOnePermissionDenied(block, deniedPermissions)) {
+ // We just prompted the user and one of the permissions of this block has been denied:
+ // There's no reason to instantly prompt again; Just reject without prompting.
+ block.onPermissionsDenied();
+ } else {
+ prompt.add(block);
+ }
+ }
+ }
+
+ if (!prompt.isEmpty()) {
+ showPrompt(activity);
+ }
+ }
+
+ private static synchronized void showPrompt(Activity activity) {
+ HashSet<String> permissions = new HashSet<>();
+
+ for (PermissionBlock block : prompt) {
+ Collections.addAll(permissions, block.getPermissions());
+ }
+
+ permissionHelper.prompt(activity, permissions.toArray(new String[permissions.size()]));
+ }
+
+ private static HashSet<String> collectGrantedPermissions(@NonNull String[] permissions, @NonNull int[] grantResults) {
+ return filterPermissionsByResult(permissions, grantResults, PackageManager.PERMISSION_GRANTED);
+ }
+
+ private static HashSet<String> collectDeniedPermissions(@NonNull String[] permissions, @NonNull int[] grantResults) {
+ return filterPermissionsByResult(permissions, grantResults, PackageManager.PERMISSION_DENIED);
+ }
+
+ private static HashSet<String> filterPermissionsByResult(@NonNull String[] permissions, @NonNull int[] grantResults, int result) {
+ HashSet<String> grantedPermissions = new HashSet<>(permissions.length);
+ for (int i = 0; i < permissions.length; i++) {
+ if (grantResults[i] == result) {
+ grantedPermissions.add(permissions[i]);
+ }
+ }
+ return grantedPermissions;
+ }
+
+ private static boolean allPermissionsGranted(PermissionBlock block, HashSet<String> grantedPermissions) {
+ for (String permission : block.getPermissions()) {
+ if (!grantedPermissions.contains(permission)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean atLeastOnePermissionDenied(PermissionBlock block, HashSet<String> deniedPermissions) {
+ for (String permission : block.getPermissions()) {
+ if (deniedPermissions.contains(permission)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}