diff --git a/Examples/UIExplorer/PermissionsExampleAndroid.android.js b/Examples/UIExplorer/PermissionsExampleAndroid.android.js new file mode 100644 index 00000000000000..bde582110ce072 --- /dev/null +++ b/Examples/UIExplorer/PermissionsExampleAndroid.android.js @@ -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 = ''; +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 ( + + Permission Name: + + + + Check Permission + + + Permission Status: {this.state.hasPermission} + + + Request Permission + + + + ); + }, + + _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: () => , + }, +]; + +var styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: 'white', + }, + singleLine: { + fontSize: 16, + padding: 4, + }, + text: { + margin: 10, + }, + touchable: { + color: '#007AFF', + }, +}); + diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index c4c7aecc95d7bc..4fdfd4afb23d27 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -169,6 +169,10 @@ const APIExamples = [ key: 'PanResponderExample', module: require('./PanResponderExample'), }, + { + key: 'PermissionsExampleAndroid', + module: require('./PermissionsExampleAndroid'), + }, { key: 'PointerEventsExample', module: require('./PointerEventsExample'), diff --git a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml index be53ed1b474186..8efcb2ef4ecef6 100644 --- a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml +++ b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml @@ -13,7 +13,7 @@ + android:targetSdkVersion="23" /> mCallbacks; + private int mRequestCode = 0; + + public PermissionsModule(ReactApplicationContext reactContext) { + super(reactContext); + mCallbacks = new SparseArray(); + } + + @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; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK index 432279a923cd81..9882cbdbd18a01 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK @@ -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'), diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index c17cfe8d04d25f..a07429e7ead6dc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -30,6 +30,7 @@ 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 List createNativeModules(ReactApplicationContext reactConte new LocationModule(reactContext), new NetworkingModule(reactContext), new NetInfoModule(reactContext), + new PermissionsModule(reactContext), new StatusBarModule(reactContext), new TimePickerDialogModule(reactContext), new ToastModule(reactContext),