//xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/permissions:permissionsAndroid (#45742)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/45742

Changelog: [Internal]

Reviewed By: christophpurrer

Differential Revision: D60303780

fbshipit-source-id: ea5fa127762888ff81e9c452cba323399ba45ca2
This commit is contained in:
Andrew Datsenko
2024-07-29 06:37:21 -07:00
committed by Facebook GitHub Bot
parent 7c1740a44c
commit e99e5d8bc0
3 changed files with 190 additions and 205 deletions
@@ -3569,7 +3569,7 @@ public class com/facebook/react/modules/network/TLSSocketFactory : javax/net/ssl
public fun getSupportedCipherSuites ()[Ljava/lang/String;
}
public class com/facebook/react/modules/permissions/PermissionsModule : com/facebook/fbreact/specs/NativePermissionsAndroidSpec, com/facebook/react/modules/core/PermissionListener {
public final class com/facebook/react/modules/permissions/PermissionsModule : com/facebook/fbreact/specs/NativePermissionsAndroidSpec, com/facebook/react/modules/core/PermissionListener {
public fun <init> (Lcom/facebook/react/bridge/ReactApplicationContext;)V
public fun checkPermission (Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V
public fun onRequestPermissionsResult (I[Ljava/lang/String;[I)Z
@@ -1,204 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.modules.permissions;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.SparseArray;
import com.facebook.common.logging.FLog;
import com.facebook.fbreact.specs.NativePermissionsAndroidSpec;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
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 = NativePermissionsAndroidSpec.NAME)
public class PermissionsModule extends NativePermissionsAndroidSpec 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>();
}
/**
* 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}.
*/
@Override
public void checkPermission(final String permission, final Promise promise) {
Context context = getReactApplicationContext().getBaseContext();
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}.
*/
@Override
public void shouldShowRequestPermissionRationale(final String permission, final Promise promise) {
try {
promise.resolve(
getPermissionAwareActivity().shouldShowRequestPermissionRationale(permission));
} catch (IllegalStateException e) {
promise.reject(ERROR_INVALID_ACTIVITY, e);
}
}
/**
* Request the given permission. successCallback is called with GRANTED if the permission had been
* granted, DENIED or NEVER_ASK_AGAIN otherwise. For devices before Android M, this checks if the
* user has the permission given or not and resolves with GRANTED or DENIED. See {@link
* Activity#checkSelfPermission}.
*/
@Override
public void requestPermission(final String permission, final Promise promise) {
Context context = getReactApplicationContext().getBaseContext();
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.length > 0 && 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);
}
}
@Override
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 (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.length > 0 && 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) {
try {
Callback callback = mCallbacks.get(requestCode);
if (callback != null) {
callback.invoke(grantResults, getPermissionAwareActivity());
mCallbacks.remove(requestCode);
} else {
FLog.w("PermissionsModule", "Unable to find callback with requestCode %d", requestCode);
}
return mCallbacks.size() == 0;
} catch (IllegalStateException e) {
FLog.e(
"PermissionsModule",
e,
"Unexpected invocation of `onRequestPermissionsResult` with invalid current activity");
return false;
}
}
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;
}
}
@@ -0,0 +1,189 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.modules.permissions
import android.app.Activity
import android.content.pm.PackageManager
import android.util.SparseArray
import com.facebook.common.logging.FLog
import com.facebook.fbreact.specs.NativePermissionsAndroidSpec
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableArray
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 = NativePermissionsAndroidSpec.NAME)
public class PermissionsModule(reactContext: ReactApplicationContext?) :
NativePermissionsAndroidSpec(reactContext), PermissionListener {
private val callbacks: SparseArray<Callback> = SparseArray<Callback>()
private var requestCode = 0
private val GRANTED = "granted"
private val DENIED = "denied"
private val NEVER_ASK_AGAIN = "never_ask_again"
/**
* Check if the app has the permission given. successCallback is called with true if the
* permission had been granted, false otherwise. See [Activity.checkSelfPermission].
*/
override public fun checkPermission(permission: String, promise: Promise): Unit {
val context = getReactApplicationContext().getBaseContext()
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
* [permissionAwareActivity.shouldShowRequestPermissionRationale].
*/
override public fun shouldShowRequestPermissionRationale(
permission: String?,
promise: Promise
): Unit {
try {
promise.resolve(permissionAwareActivity.shouldShowRequestPermissionRationale(permission))
} catch (e: IllegalStateException) {
promise.reject(ERROR_INVALID_ACTIVITY, e)
}
}
/**
* Request the given permission. successCallback is called with GRANTED if the permission had been
* granted, DENIED or NEVER_ASK_AGAIN otherwise. For devices before Android M, this checks if the
* user has the permission given or not and resolves with GRANTED or DENIED. See
* [Activity.checkSelfPermission].
*/
override public fun requestPermission(permission: String, promise: Promise): Unit {
val context = getReactApplicationContext().getBaseContext()
if (context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
promise.resolve(GRANTED)
return
}
try {
val activity = permissionAwareActivity
callbacks.put(
requestCode,
object : Callback {
override public operator fun invoke(vararg args: Any?) {
val results = args[0] as IntArray
if (results.size > 0 && results[0] == PackageManager.PERMISSION_GRANTED) {
promise.resolve(GRANTED)
} else {
val callbackActivity = args[1] as PermissionAwareActivity
if (callbackActivity.shouldShowRequestPermissionRationale(permission)) {
promise.resolve(DENIED)
} else {
promise.resolve(NEVER_ASK_AGAIN)
}
}
}
})
activity.requestPermissions(arrayOf(permission), requestCode, this)
requestCode++
} catch (e: IllegalStateException) {
promise.reject(ERROR_INVALID_ACTIVITY, e)
}
}
override public fun requestMultiplePermissions(
permissions: ReadableArray,
promise: Promise
): Unit {
val grantedPermissions = WritableNativeMap()
val permissionsToCheck = ArrayList<String>()
var checkedPermissionsCount = 0
val context = getReactApplicationContext().getBaseContext()
for (i in 0 until permissions.size()) {
val perm = permissions.getString(i)
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 {
val activity = permissionAwareActivity
callbacks.put(
requestCode,
object : Callback {
override public operator fun invoke(vararg args: Any?) {
val results = args[0] as IntArray
val callbackActivity = args[1] as PermissionAwareActivity
for (j in permissionsToCheck.indices) {
val permission = permissionsToCheck[j]
if (results.size > 0 && results[j] == PackageManager.PERMISSION_GRANTED) {
grantedPermissions.putString(permission, GRANTED)
} else {
if (callbackActivity.shouldShowRequestPermissionRationale(permission)) {
grantedPermissions.putString(permission, DENIED)
} else {
grantedPermissions.putString(permission, NEVER_ASK_AGAIN)
}
}
}
promise.resolve(grantedPermissions)
}
})
activity.requestPermissions(permissionsToCheck.toTypedArray<String>(), requestCode, this)
requestCode++
} catch (e: IllegalStateException) {
promise.reject(ERROR_INVALID_ACTIVITY, e)
}
}
/** Method called by the activity with the result of the permission request. */
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
): Boolean =
try {
val callback = callbacks[requestCode]
if (callback != null) {
callback.invoke(grantResults, permissionAwareActivity)
callbacks.remove(requestCode)
} else {
FLog.w("PermissionsModule", "Unable to find callback with requestCode %d", requestCode)
}
callbacks.size() == 0
} catch (e: IllegalStateException) {
FLog.e(
"PermissionsModule",
e,
"Unexpected invocation of `onRequestPermissionsResult` with invalid current activity")
false
}
private val permissionAwareActivity: PermissionAwareActivity
get() {
val activity = getCurrentActivity()
checkNotNull(activity) { "Tried to use permissions API while not attached to an Activity." }
check(activity is PermissionAwareActivity) {
("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.")
}
return activity
}
private companion object {
private const val ERROR_INVALID_ACTIVITY = "E_INVALID_ACTIVITY"
}
}