Skip to content

Commit

Permalink
Open source permissions
Browse files Browse the repository at this point in the history
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
  • Loading branch information
andreicoman11 authored and Facebook Github Bot 6 committed Jun 15, 2016
1 parent 4fe7a25 commit b7352b4
Show file tree
Hide file tree
Showing 10 changed files with 425 additions and 8 deletions.
155 changes: 155 additions & 0 deletions Examples/UIExplorer/PermissionsExampleAndroid.android.js
Original file line number Diff line number Diff line change
@@ -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',
},
});

4 changes: 4 additions & 0 deletions Examples/UIExplorer/UIExplorerList.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ const APIExamples = [
key: 'PanResponderExample',
module: require('./PanResponderExample'),
},
{
key: 'PermissionsExampleAndroid',
module: require('./PermissionsExampleAndroid'),
},
{
key: 'PointerEventsExample',
module: require('./PointerEventsExample'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="22" />
android:targetSdkVersion="23" />

<application
android:allowBackup="true"
Expand Down
36 changes: 29 additions & 7 deletions ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -233,4 +235,24 @@ public void onNewIntent(Intent intent) {
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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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',
)
Loading

0 comments on commit b7352b4

Please sign in to comment.