diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index c3236a31ec9..89f47e173af 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -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 (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 diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/PermissionsModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/PermissionsModule.java deleted file mode 100644 index 7bd6dc90ed4..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/PermissionsModule.java +++ /dev/null @@ -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 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(); - } - - /** - * 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 permissionsToCheck = new ArrayList(); - 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; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/PermissionsModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/PermissionsModule.kt new file mode 100644 index 00000000000..c759b8af7cf --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/PermissionsModule.kt @@ -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 = SparseArray() + 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() + 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(), 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, + 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" + } +}