diff --git a/Examples/UIExplorer/android/app/build.gradle b/Examples/UIExplorer/android/app/build.gradle
index 6a24213d1785b7..79cbe0b95cdce5 100644
--- a/Examples/UIExplorer/android/app/build.gradle
+++ b/Examples/UIExplorer/android/app/build.gradle
@@ -89,7 +89,7 @@ android {
defaultConfig {
applicationId "com.facebook.react.uiapp"
minSdkVersion 16
- targetSdkVersion 22
+ targetSdkVersion 23
versionCode 1
versionName "1.0"
ndk {
diff --git a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml
index bd77d77e4f50ed..308604da5383f0 100644
--- a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml
+++ b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml
@@ -11,6 +11,10 @@
+
+
+
+
diff --git a/Examples/UIExplorer/js/PermissionsExampleAndroid.android.js b/Examples/UIExplorer/js/PermissionsExampleAndroid.android.js
index 129955ba4984ea..8a5bc52888d8c7 100644
--- a/Examples/UIExplorer/js/PermissionsExampleAndroid.android.js
+++ b/Examples/UIExplorer/js/PermissionsExampleAndroid.android.js
@@ -27,13 +27,15 @@ const React = require('react');
const ReactNative = require('react-native');
const {
PermissionsAndroid,
+ Picker,
StyleSheet,
Text,
- TextInput,
TouchableWithoutFeedback,
View,
} = ReactNative;
+const Item = Picker.Item;
+
exports.displayName = (undefined: ?string);
exports.framework = 'React';
exports.title = 'PermissionsAndroid';
@@ -41,7 +43,7 @@ exports.description = 'Permissions example for API 23+.';
class PermissionsExample extends React.Component {
state = {
- permission: PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
+ permission: PermissionsAndroid.PERMISSIONS.CAMERA,
hasPermission: 'Not Checked',
};
@@ -49,13 +51,14 @@ class PermissionsExample extends React.Component {
return (
Permission Name:
-
+
+
+
+
+
Check Permission
@@ -71,14 +74,14 @@ class PermissionsExample extends React.Component {
);
}
- _updateText = (event: Object) => {
+ _onSelectPermission = (permission: string) => {
this.setState({
- permission: event.nativeEvent.text,
+ permission: permission,
});
};
_checkPermission = async () => {
- let result = await PermissionsAndroid.checkPermission(this.state.permission);
+ let result = await PermissionsAndroid.check(this.state.permission);
this.setState({
hasPermission: (result ? 'Granted' : 'Revoked') + ' for ' +
this.state.permission,
@@ -86,7 +89,7 @@ class PermissionsExample extends React.Component {
};
_requestPermission = async () => {
- let result = await PermissionsAndroid.requestPermission(
+ let result = await PermissionsAndroid.request(
this.state.permission,
{
title: 'Permission Explanation',
@@ -95,8 +98,9 @@ class PermissionsExample extends React.Component {
' because of reasons. Please approve.'
},
);
+
this.setState({
- hasPermission: (result ? 'Granted' : 'Revoked') + ' for ' +
+ hasPermission: result + ' for ' +
this.state.permission,
});
};
@@ -125,4 +129,7 @@ var styles = StyleSheet.create({
touchable: {
color: '#007AFF',
},
+ picker: {
+ flex: 1,
+ }
});
diff --git a/Libraries/PermissionsAndroid/PermissionsAndroid.js b/Libraries/PermissionsAndroid/PermissionsAndroid.js
index aaa1ea9cfe3b22..524bfffb481f45 100644
--- a/Libraries/PermissionsAndroid/PermissionsAndroid.js
+++ b/Libraries/PermissionsAndroid/PermissionsAndroid.js
@@ -19,6 +19,8 @@ type Rationale = {
message: string;
}
+type PermissionStatus = 'granted' | 'denied' | 'never_ask_again';
+
/**
* `PermissionsAndroid` provides access to Android M's new permissions model.
* Some permissions are granted by default when the application is installed
@@ -47,7 +49,7 @@ type Rationale = {
* 'so you can take awesome pictures.'
* }
* )
- * if (granted) {
+ * if (granted === PermissionsAndroid.RESULTS.GRANTED) {
* console.log("You can use the camera")
* } else {
* console.log("Camera permission denied")
@@ -61,6 +63,7 @@ type Rationale = {
class PermissionsAndroid {
PERMISSIONS: Object;
+ RESULTS: Object;
constructor() {
/**
@@ -92,17 +95,38 @@ class PermissionsAndroid {
READ_EXTERNAL_STORAGE: 'android.permission.READ_EXTERNAL_STORAGE',
WRITE_EXTERNAL_STORAGE: 'android.permission.WRITE_EXTERNAL_STORAGE',
};
+
+ this.RESULTS = {
+ GRANTED: 'granted',
+ DENIED: 'denied',
+ NEVER_ASK_AGAIN: 'never_ask_again',
+ };
}
/**
+ * DEPRECATED - use check
+ *
* Returns a promise resolving to a boolean value as to whether the specified
* permissions has been granted
+ *
+ * @deprecated
*/
checkPermission(permission: string) : Promise {
+ console.warn('"PermissionsAndroid.checkPermission" is deprecated. Use "PermissionsAndroid.check" instead');
return Permissions.checkPermission(permission);
}
/**
+ * Returns a promise resolving to a boolean value as to whether the specified
+ * permissions has been granted
+ */
+ check(permission: string) : Promise {
+ return Permissions.checkPermission(permission);
+ }
+
+ /**
+ * DEPRECATED - use request
+ *
* Prompts the user to enable a permission and returns a promise resolving to a
* boolean value indicating whether the user allowed or denied the request
*
@@ -111,8 +135,26 @@ class PermissionsAndroid {
* necessary to show a dialog explaining why the permission is needed
* (https://developer.android.com/training/permissions/requesting.html#explain)
* and then shows the system permission dialog
+ *
+ * @deprecated
*/
async requestPermission(permission: string, rationale?: Rationale) : Promise {
+ console.warn('"PermissionsAndroid.requestPermission" is deprecated. Use "PermissionsAndroid.request" instead');
+ const response = await this.request(permission, rationale);
+ return (response === this.RESULTS.GRANTED);
+ }
+
+ /**
+ * Prompts the user to enable a permission and returns a promise resolving to a
+ * string value indicating whether the user allowed or denied the request
+ *
+ * If the optional rationale argument is included (which is an object with a
+ * `title` and `message`), this function checks with the OS whether it is
+ * necessary to show a dialog explaining why the permission is needed
+ * (https://developer.android.com/training/permissions/requesting.html#explain)
+ * and then shows the system permission dialog
+ */
+ async request(permission: string, rationale?: Rationale) : Promise {
if (rationale) {
const shouldShowRationale = await Permissions.shouldShowRequestPermissionRationale(permission);
@@ -128,6 +170,15 @@ class PermissionsAndroid {
}
return Permissions.requestPermission(permission);
}
+
+ /**
+ * Prompts the user to enable multiple permissions in the same dialog and
+ * returns an object with the permissions as keys and strings as values
+ * indicating whether the user allowed or denied the request
+ */
+ requestMultiple(permissions: Array) : Promise<{[permission: string]: PermissionStatus}> {
+ return Permissions.requestMultiplePermissions(permissions);
+ }
}
PermissionsAndroid = new PermissionsAndroid();
diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java
index 5afb72203e20f8..2791564f80c657 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java
@@ -2,8 +2,6 @@
package com.facebook.react;
-import javax.annotation.Nullable;
-
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
@@ -17,11 +15,14 @@
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.bridge.Callback;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.devsupport.DoubleTapReloadRecognizer;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionListener;
+import javax.annotation.Nullable;
+
/**
* Delegate class for {@link ReactActivity} and {@link ReactFragmentActivity}. You can subclass this
* to provide custom implementations for e.g. {@link #getReactNativeHost()}, if your Application
@@ -38,6 +39,7 @@ public class ReactActivityDelegate {
private @Nullable ReactRootView mReactRootView;
private @Nullable DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
private @Nullable PermissionListener mPermissionListener;
+ private @Nullable Callback mPermissionsCallback;
public ReactActivityDelegate(Activity activity, @Nullable String mainComponentName) {
mActivity = activity;
@@ -117,6 +119,11 @@ protected void onResume() {
getPlainActivity(),
(DefaultHardwareBackBtnHandler) getPlainActivity());
}
+
+ if (mPermissionsCallback != null) {
+ mPermissionsCallback.invoke();
+ mPermissionsCallback = null;
+ }
}
protected void onDestroy() {
@@ -178,13 +185,18 @@ public void requestPermissions(
}
public void onRequestPermissionsResult(
- int requestCode,
- String[] permissions,
- int[] grantResults) {
- if (mPermissionListener != null &&
- mPermissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
- mPermissionListener = null;
- }
+ final int requestCode,
+ final String[] permissions,
+ final int[] grantResults) {
+ mPermissionsCallback = new Callback() {
+ @Override
+ public void invoke(Object... args) {
+ if (mPermissionListener != null &&
+ mPermissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
+ mPermissionListener = null;
+ }
+ }
+ };
}
private Context getContext() {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/PermissionsModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/PermissionsModule.java
index f00a9d788de545..6054964349d1c9 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/PermissionsModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/PermissionsModule.java
@@ -21,10 +21,15 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;
+import java.util.ArrayList;
+
/**
* Module that exposes the Android M Permission system to JS.
*/
@@ -34,6 +39,9 @@ public class PermissionsModule extends ReactContextBaseJavaModule implements Per
private static final String ERROR_INVALID_ACTIVITY = "E_INVALID_ACTIVITY";
private final SparseArray mCallbacks;
private int mRequestCode = 0;
+ private final String GRANTED = "granted";
+ private final String DENIED = "denied";
+ private final String NEVER_ASK_AGAIN = "never_ask_again";
public PermissionsModule(ReactApplicationContext reactContext) {
super(reactContext);
@@ -96,7 +104,7 @@ public void requestPermission(final String permission, final Promise promise) {
return;
}
if (context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
- promise.resolve(true);
+ promise.resolve(GRANTED);
return;
}
@@ -107,9 +115,20 @@ public void requestPermission(final String permission, final Promise promise) {
mRequestCode, new Callback() {
@Override
public void invoke(Object... args) {
- promise.resolve(args[0].equals(PackageManager.PERMISSION_GRANTED));
+ int[] results = (int[]) args[0];
+ if (results[0] == PackageManager.PERMISSION_GRANTED) {
+ promise.resolve(GRANTED);
+ } else {
+ PermissionAwareActivity activity = (PermissionAwareActivity) args[1];
+ if (activity.shouldShowRequestPermissionRationale(permission)) {
+ promise.resolve(DENIED);
+ } else {
+ promise.resolve(NEVER_ASK_AGAIN);
+ }
+ }
}
- });
+ }
+ );
activity.requestPermissions(new String[]{permission}, mRequestCode, this);
mRequestCode++;
@@ -118,17 +137,76 @@ public void invoke(Object... args) {
}
}
+ @ReactMethod
+ public void requestMultiplePermissions(final ReadableArray permissions, final Promise promise) {
+ final WritableMap grantedPermissions = new WritableNativeMap();
+ final ArrayList permissionsToCheck = new ArrayList();
+ int checkedPermissionsCount = 0;
+
+ Context context = getReactApplicationContext().getBaseContext();
+
+ for (int i = 0; i < permissions.size(); i++) {
+ String perm = permissions.getString(i);
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ grantedPermissions.putString(perm, context.checkPermission(perm, Process.myPid(), Process.myUid()) ==
+ PackageManager.PERMISSION_GRANTED ? GRANTED : DENIED);
+ checkedPermissionsCount++;
+ } else if (context.checkSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
+ grantedPermissions.putString(perm, GRANTED);
+ checkedPermissionsCount++;
+ } else {
+ permissionsToCheck.add(perm);
+ }
+ }
+ if (permissions.size() == checkedPermissionsCount) {
+ promise.resolve(grantedPermissions);
+ return;
+ }
+ try {
+
+ PermissionAwareActivity activity = getPermissionAwareActivity();
+
+ mCallbacks.put(
+ mRequestCode, new Callback() {
+ @Override
+ public void invoke(Object... args) {
+ int[] results = (int[]) args[0];
+ PermissionAwareActivity activity = (PermissionAwareActivity) args[1];
+ for (int j = 0; j < permissionsToCheck.size(); j++) {
+ String permission = permissionsToCheck.get(j);
+ if (results[j] == PackageManager.PERMISSION_GRANTED) {
+ grantedPermissions.putString(permission, GRANTED);
+ } else {
+ if (activity.shouldShowRequestPermissionRationale(permission)) {
+ grantedPermissions.putString(permission, DENIED);
+ } else {
+ grantedPermissions.putString(permission, NEVER_ASK_AGAIN);
+ }
+ }
+ }
+ promise.resolve(grantedPermissions);
+ }
+ });
+
+ activity.requestPermissions(permissionsToCheck.toArray(new String[0]), mRequestCode, this);
+ mRequestCode++;
+ } catch (IllegalStateException e) {
+ promise.reject(ERROR_INVALID_ACTIVITY, e);
+ }
+ }
+
/**
* Method called by the activity with the result of the permission request.
*/
@Override
public boolean onRequestPermissionsResult(
- int requestCode,
- String[] permissions,
- int[] grantResults) {
- mCallbacks.get(requestCode).invoke(grantResults[0]);
- mCallbacks.remove(requestCode);
- return mCallbacks.size() == 0;
+ int requestCode,
+ String[] permissions,
+ int[] grantResults) {
+ mCallbacks.get(requestCode).invoke(grantResults, getPermissionAwareActivity());
+ mCallbacks.remove(requestCode);
+ return mCallbacks.size() == 0;
}
private PermissionAwareActivity getPermissionAwareActivity() {