summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions')
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionBlock.java133
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/Permissions.java210
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionsHelper.java32
3 files changed, 375 insertions, 0 deletions
diff --git 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
new file mode 100644
index 000000000..a4d72f258
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionBlock.java
@@ -0,0 +1,133 @@
+/* -*- 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 org.mozilla.gecko.util.ThreadUtils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+/**
+ * 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 Runnable onPermissionsGranted;
+ private Runnable onPermissionsDenied;
+ private boolean doNotPrompt;
+
+ /* package-private */ PermissionBlock(Context context, PermissionsHelper helper) {
+ this.context = context;
+ this.helper = helper;
+ }
+
+ /**
+ * Determine whether the app has been granted the specified permissions.
+ */
+ public PermissionBlock withPermissions(@NonNull String... permissions) {
+ this.permissions = permissions;
+ return this;
+ }
+
+ /**
+ * Execute all callbacks on the UI thread.
+ */
+ public PermissionBlock onUIThread() {
+ this.onUIThread = true;
+ return this;
+ }
+
+ /**
+ * Do not prompt the user to accept the permission if it has not been granted yet.
+ */
+ 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
+ * been granted yet.
+ */
+ public PermissionBlock doNotPromptIf(boolean condition) {
+ if (condition) {
+ doNotPrompt();
+ }
+
+ return this;
+ }
+
+ /**
+ * Execute this permission block. Calling this method will prompt the user if needed.
+ */
+ public void run() {
+ run(null);
+ }
+
+ /**
+ * Execute the specified runnable if the app has been granted all permissions. Calling this method will prompt the
+ * user if needed.
+ */
+ public void run(Runnable onPermissionsGranted) {
+ if (!doNotPrompt && !(context instanceof Activity)) {
+ throw new IllegalStateException("You need to either specify doNotPrompt() or pass in an Activity context");
+ }
+
+ this.onPermissionsGranted = onPermissionsGranted;
+
+ if (hasPermissions(context)) {
+ onPermissionsGranted();
+ } else if (doNotPrompt) {
+ onPermissionsDenied();
+ } else {
+ Permissions.prompt((Activity) context, this);
+ }
+
+ // This reference is no longer needed. Let's clear it now to avoid memory leaks.
+ context = null;
+ }
+
+ /**
+ * Execute this fallback if at least one permission has not been granted.
+ */
+ public PermissionBlock andFallback(@NonNull Runnable onPermissionsDenied) {
+ this.onPermissionsDenied = onPermissionsDenied;
+ return this;
+ }
+
+ /* package-private */ void onPermissionsGranted() {
+ executeRunnable(onPermissionsGranted);
+ }
+
+ /* package-private */ void onPermissionsDenied() {
+ executeRunnable(onPermissionsDenied);
+ }
+
+ private void executeRunnable(Runnable runnable) {
+ if (runnable == null) {
+ return;
+ }
+
+ if (onUIThread) {
+ ThreadUtils.postToUiThread(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
+ /* package-private */ String[] getPermissions() {
+ return permissions;
+ }
+
+ /* packacge-private */ boolean hasPermissions(Context context) {
+ return helper.hasPermissions(context, permissions);
+ }
+}
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;
+ }
+}
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionsHelper.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionsHelper.java
new file mode 100644
index 000000000..945a81f43
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionsHelper.java
@@ -0,0 +1,32 @@
+/* -*- 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.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+
+/* package-private */ class PermissionsHelper {
+ private static final int PERMISSIONS_REQUEST_CODE = 212;
+
+ public boolean hasPermissions(Context context, String... permissions) {
+ for (String permission : permissions) {
+ final int permissionCheck = ContextCompat.checkSelfPermission(context, permission);
+
+ if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public void prompt(Activity activity, String[] permissions) {
+ ActivityCompat.requestPermissions(activity, permissions, PERMISSIONS_REQUEST_CODE);
+ }
+}