/** * 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.Context; 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.Promise; 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. */ @ReactModule(name = "PermissionsAndroid") public class PermissionsModule extends ReactContextBaseJavaModule implements PermissionListener { private static final String ERROR_INVALID_ACTIVITY = "E_INVALID_ACTIVITY"; private final SparseArray<Callback> 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); mCallbacks = new SparseArray<Callback>(); } @Override public String getName() { return "PermissionsAndroid"; } /** * 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 Promise promise) { Context context = getReactApplicationContext().getBaseContext(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { promise.resolve(context.checkPermission(permission, Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED); return; } promise.resolve(context.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 Promise promise) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { promise.resolve(false); return; } try { promise.resolve(getPermissionAwareActivity().shouldShowRequestPermissionRationale(permission)); } catch (IllegalStateException e) { promise.reject(ERROR_INVALID_ACTIVITY, e); } } /** * 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 Promise promise) { Context context = getReactApplicationContext().getBaseContext(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { promise.resolve(context.checkPermission(permission, Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED); return; } if (context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) { promise.resolve(GRANTED); return; } try { PermissionAwareActivity activity = getPermissionAwareActivity(); mCallbacks.put( mRequestCode, new Callback() { @Override public void invoke(Object... args) { 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++; } catch (IllegalStateException e) { promise.reject(ERROR_INVALID_ACTIVITY, e); } } @ReactMethod public void requestMultiplePermissions(final ReadableArray permissions, final Promise promise) { final WritableMap grantedPermissions = new WritableNativeMap(); final ArrayList<String> permissionsToCheck = new ArrayList<String>(); 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, getPermissionAwareActivity()); 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; } }