mirror of
https://github.com/status-im/react-native.git
synced 2025-01-13 19:15:05 +00:00
Open source permissions
Summary: This moves into open source the PermissionsModule and the activity and listener interfaces necessary to make permissions work. It also moves the PermissionsExample into the UIExplorer. In order to make this work, the device has to be Android M and above, and the target sdk of the app has to be 23+, so I changed the uiexplorer manifest to target that API. This has the unfortunate consequence that people testing on devices with API 23+ will have to enable the `draw over other apps` setting that react needs for RedBoxing. The app will automatically send the user to that screen, so enabling the setting and resuming the app should be trivial. For testing, try requesting permission for a permission that is currently revoked. If a permission is granted, it can be revoked via adb (`adb shell pm revoke com.your.app android.permission.PERMISSION_NAME`), and then requested. Reviewed By: bestander Differential Revision: D3431324 fbshipit-source-id: 8cbaea676d2b5727cb5191cdb77a02e213bf9ba3
This commit is contained in:
parent
4fe7a25d01
commit
b7352b4667
155
Examples/UIExplorer/PermissionsExampleAndroid.android.js
Normal file
155
Examples/UIExplorer/PermissionsExampleAndroid.android.js
Normal file
@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @providesModule PermissionsExampleAndroid
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
const {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} = ReactNative;
|
||||
const DialogManager = require('NativeModules').DialogManagerAndroid;
|
||||
const Permissions = require('NativeModules').AndroidPermissions;
|
||||
|
||||
exports.displayName = (undefined: ?string);
|
||||
exports.framework = 'React';
|
||||
exports.title = '<Permissions>';
|
||||
exports.description = 'Permissions example for API 23+.';
|
||||
|
||||
const PermissionsExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
permission: 'android.permission.WRITE_EXTERNAL_STORAGE',
|
||||
hasPermission: 'Not Checked',
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.text}>Permission Name:</Text>
|
||||
<TextInput
|
||||
autoFocus={true}
|
||||
autoCorrect={false}
|
||||
style={styles.singleLine}
|
||||
onChange={this._updateText}
|
||||
defaultValue={this.state.permission}
|
||||
/>
|
||||
<TouchableWithoutFeedback onPress={this._checkPermission}>
|
||||
<View>
|
||||
<Text style={[styles.touchable, styles.text]}>Check Permission</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
<Text style={styles.text}>Permission Status: {this.state.hasPermission}</Text>
|
||||
<TouchableWithoutFeedback onPress={this._shouldExplainPermission}>
|
||||
<View>
|
||||
<Text style={[styles.touchable, styles.text]}>Request Permission</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_updateText: function(event: Object) {
|
||||
this.setState({
|
||||
permission: event.nativeEvent.text,
|
||||
});
|
||||
},
|
||||
|
||||
_checkPermission: function() {
|
||||
Permissions.checkPermission(
|
||||
this.state.permission,
|
||||
(permission: string, result: boolean) => {
|
||||
this.setState({
|
||||
hasPermission: (result ? 'Granted' : 'Revoked') + ' for ' + permission,
|
||||
});
|
||||
},
|
||||
this._showError);
|
||||
},
|
||||
|
||||
_shouldExplainPermission: function() {
|
||||
Permissions.shouldShowRequestPermissionRationale(
|
||||
this.state.permission,
|
||||
(permission: string, shouldShow: boolean) => {
|
||||
if (shouldShow) {
|
||||
DialogManager.showAlert(
|
||||
{
|
||||
title: 'Permission Explanation',
|
||||
message:
|
||||
'The app needs the following permission ' + this.state.permission +
|
||||
' because of reasons. Please approve.'
|
||||
},
|
||||
this._showError,
|
||||
this._requestPermission);
|
||||
} else {
|
||||
this._requestPermission();
|
||||
}
|
||||
},
|
||||
this._showError);
|
||||
},
|
||||
|
||||
_requestPermission: function() {
|
||||
Permissions.requestPermission(
|
||||
this.state.permission,
|
||||
(permission: string, result: boolean) => {
|
||||
this.setState({
|
||||
hasPermission: (result ? 'Granted' : 'Revoked') + ' for ' + permission,
|
||||
});
|
||||
},
|
||||
this._showError);
|
||||
},
|
||||
|
||||
_showError: function() {
|
||||
DialogManager.showAlert({message: 'Error'}, {}, {});
|
||||
}
|
||||
});
|
||||
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'Permissions Example',
|
||||
description: 'Short example of how to use the runtime permissions API introduced in Android M.',
|
||||
render: () => <PermissionsExample />,
|
||||
},
|
||||
];
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
singleLine: {
|
||||
fontSize: 16,
|
||||
padding: 4,
|
||||
},
|
||||
text: {
|
||||
margin: 10,
|
||||
},
|
||||
touchable: {
|
||||
color: '#007AFF',
|
||||
},
|
||||
});
|
||||
|
@ -169,6 +169,10 @@ const APIExamples = [
|
||||
key: 'PanResponderExample',
|
||||
module: require('./PanResponderExample'),
|
||||
},
|
||||
{
|
||||
key: 'PermissionsExampleAndroid',
|
||||
module: require('./PermissionsExampleAndroid'),
|
||||
},
|
||||
{
|
||||
key: 'PointerEventsExample',
|
||||
module: require('./PointerEventsExample'),
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="16"
|
||||
android:targetSdkVersion="22" />
|
||||
android:targetSdkVersion="23" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
@ -9,33 +9,35 @@
|
||||
|
||||
package com.facebook.react;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.provider.Settings;
|
||||
import android.view.KeyEvent;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.devsupport.DoubleTapReloadRecognizer;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import com.facebook.react.modules.core.PermissionAwareActivity;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
/**
|
||||
* Base Activity for React Native applications.
|
||||
*/
|
||||
public abstract class ReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
|
||||
public abstract class ReactActivity extends Activity
|
||||
implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
|
||||
|
||||
private static final String REDBOX_PERMISSION_MESSAGE =
|
||||
"Overlay permissions needs to be granted in order for react native apps to run in dev mode";
|
||||
|
||||
private @Nullable PermissionListener mPermissionListener;
|
||||
private @Nullable ReactInstanceManager mReactInstanceManager;
|
||||
private @Nullable ReactRootView mReactRootView;
|
||||
private LifecycleState mLifecycleState = LifecycleState.BEFORE_RESUME;
|
||||
@ -233,4 +235,24 @@ public abstract class ReactActivity extends Activity implements DefaultHardwareB
|
||||
super.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPermissions(
|
||||
String[] permissions,
|
||||
int requestCode,
|
||||
PermissionListener listener) {
|
||||
mPermissionListener = listener;
|
||||
this.requestPermissions(permissions, requestCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode,
|
||||
String[] permissions,
|
||||
int[] grantResults) {
|
||||
if (mPermissionListener != null &&
|
||||
mPermissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
|
||||
mPermissionListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.core;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
/**
|
||||
* Interface used to denote activities that can forward permission requests and call
|
||||
* {@link PermissionListener}s with the permission request results.
|
||||
*/
|
||||
public interface PermissionAwareActivity {
|
||||
|
||||
/**
|
||||
* See {@link Activity#checkPermission}.
|
||||
*/
|
||||
int checkPermission(String permission, int pid, int uid);
|
||||
|
||||
/**
|
||||
* See {@link Activity#checkSelfPermission}.
|
||||
*/
|
||||
int checkSelfPermission(String permission);
|
||||
|
||||
/**
|
||||
* See {@link Activity#shouldShowRequestPermissionRationale}.
|
||||
*/
|
||||
boolean shouldShowRequestPermissionRationale(String permission);
|
||||
|
||||
/**
|
||||
* See {@link Activity#requestPermissions}.
|
||||
*/
|
||||
void requestPermissions(String[] permissions, int requestCode, PermissionListener listener);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.core;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
/**
|
||||
* Interface used by activities to delegate permission request results. Classes implementing this
|
||||
* class will be notified whenever there's a result for a permission request.
|
||||
*/
|
||||
public interface PermissionListener {
|
||||
|
||||
/**
|
||||
* Method called whenever there's a result to a permission request. It is forwarded from
|
||||
* {@link Activity#onRequestPermissionsResult}.
|
||||
*
|
||||
* @return boolean Whether the PermissionListener can be removed.
|
||||
*/
|
||||
boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
include_defs('//ReactAndroid/DEFS')
|
||||
|
||||
android_library(
|
||||
name = 'permissions',
|
||||
srcs = glob(['**/*.java']),
|
||||
deps = [
|
||||
react_native_target('java/com/facebook/react/bridge:bridge'),
|
||||
react_native_target('java/com/facebook/react/common:common'),
|
||||
react_native_target('java/com/facebook/react/modules/core:core'),
|
||||
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
],
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
)
|
||||
|
||||
project_config(
|
||||
src_target = ':permissions',
|
||||
)
|
@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.permissions;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.modules.core.PermissionAwareActivity;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
/**
|
||||
* Module that exposes the Android M Permission system to JS.
|
||||
*/
|
||||
public class PermissionsModule extends ReactContextBaseJavaModule implements PermissionListener {
|
||||
|
||||
private final SparseArray<Callback> mCallbacks;
|
||||
private int mRequestCode = 0;
|
||||
|
||||
public PermissionsModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
mCallbacks = new SparseArray<Callback>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "AndroidPermissions";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the app has the permission given. successCallback is called with true if the
|
||||
* permission had been granted, false otherwise. See {@link Activity#checkSelfPermission}.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void checkPermission(
|
||||
final String permission,
|
||||
final Callback successCallback,
|
||||
final Callback errorCallback) {
|
||||
PermissionAwareActivity activity = getPermissionAwareActivity();
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
successCallback.invoke(
|
||||
permission,
|
||||
activity.checkPermission(permission, Process.myPid(), Process.myUid()) ==
|
||||
PackageManager.PERMISSION_GRANTED);
|
||||
return;
|
||||
}
|
||||
successCallback.invoke(
|
||||
permission,
|
||||
activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the app should display a message explaining why a certain permission is needed.
|
||||
* successCallback is called with true if the app should display a message, false otherwise.
|
||||
* This message is only displayed if the user has revoked this permission once before, and if the
|
||||
* permission dialog will be shown to the user (the user can choose to not be shown that dialog
|
||||
* again). For devices before Android M, this always returns false.
|
||||
* See {@link Activity#shouldShowRequestPermissionRationale}.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void shouldShowRequestPermissionRationale(
|
||||
final String permission,
|
||||
final Callback successCallback,
|
||||
final Callback errorCallback) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
successCallback.invoke(permission, false);
|
||||
return;
|
||||
}
|
||||
successCallback.invoke(
|
||||
permission,
|
||||
getPermissionAwareActivity().shouldShowRequestPermissionRationale(permission));
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the given permission. successCallback is called with true if the permission had been
|
||||
* granted, false otherwise. For devices before Android M, this instead checks if the user has
|
||||
* the permission given or not.
|
||||
* See {@link Activity#checkSelfPermission}.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void requestPermission(
|
||||
final String permission,
|
||||
final Callback successCallback,
|
||||
final Callback errorCallback) {
|
||||
PermissionAwareActivity activity = getPermissionAwareActivity();
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
successCallback.invoke(
|
||||
permission,
|
||||
activity.checkPermission(permission, Process.myPid(), Process.myUid()) ==
|
||||
PackageManager.PERMISSION_GRANTED);
|
||||
return;
|
||||
}
|
||||
if (activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
|
||||
successCallback.invoke(permission, true);
|
||||
return;
|
||||
}
|
||||
|
||||
mCallbacks.put(
|
||||
mRequestCode, new Callback() {
|
||||
@Override
|
||||
public void invoke(Object... args) {
|
||||
successCallback.invoke(permission, args[0].equals(PackageManager.PERMISSION_GRANTED));
|
||||
}
|
||||
});
|
||||
|
||||
activity.requestPermissions(new String[]{permission}, mRequestCode, this);
|
||||
mRequestCode++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
private PermissionAwareActivity getPermissionAwareActivity() {
|
||||
Activity activity = getCurrentActivity();
|
||||
if (activity == null) {
|
||||
throw new IllegalStateException("Tried to use permissions API while not attached to an " +
|
||||
"Activity.");
|
||||
} else if (!(activity instanceof PermissionAwareActivity)) {
|
||||
throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't" +
|
||||
" implement PermissionAwareActivity.");
|
||||
}
|
||||
return (PermissionAwareActivity) activity;
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ android_library(
|
||||
react_native_target('java/com/facebook/react/modules/location:location'),
|
||||
react_native_target('java/com/facebook/react/modules/netinfo:netinfo'),
|
||||
react_native_target('java/com/facebook/react/modules/network:network'),
|
||||
react_native_target('java/com/facebook/react/modules/permissions:permissions'),
|
||||
react_native_target('java/com/facebook/react/modules/statusbar:statusbar'),
|
||||
react_native_target('java/com/facebook/react/modules/storage:storage'),
|
||||
react_native_target('java/com/facebook/react/modules/timepicker:timepicker'),
|
||||
|
@ -30,6 +30,7 @@ import com.facebook.react.modules.intent.IntentModule;
|
||||
import com.facebook.react.modules.location.LocationModule;
|
||||
import com.facebook.react.modules.netinfo.NetInfoModule;
|
||||
import com.facebook.react.modules.network.NetworkingModule;
|
||||
import com.facebook.react.modules.permissions.PermissionsModule;
|
||||
import com.facebook.react.modules.statusbar.StatusBarModule;
|
||||
import com.facebook.react.modules.storage.AsyncStorageModule;
|
||||
import com.facebook.react.modules.timepicker.TimePickerDialogModule;
|
||||
@ -83,6 +84,7 @@ public class MainReactPackage implements ReactPackage {
|
||||
new LocationModule(reactContext),
|
||||
new NetworkingModule(reactContext),
|
||||
new NetInfoModule(reactContext),
|
||||
new PermissionsModule(reactContext),
|
||||
new StatusBarModule(reactContext),
|
||||
new TimePickerDialogModule(reactContext),
|
||||
new ToastModule(reactContext),
|
||||
|
Loading…
x
Reference in New Issue
Block a user