diff --git a/android/expoview/src/main/java/host/exp/exponent/experience/ReactNativeActivity.java b/android/expoview/src/main/java/host/exp/exponent/experience/ReactNativeActivity.java index c9721675cb301..af347f7ff21da 100644 --- a/android/expoview/src/main/java/host/exp/exponent/experience/ReactNativeActivity.java +++ b/android/expoview/src/main/java/host/exp/exponent/experience/ReactNativeActivity.java @@ -6,7 +6,6 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.PermissionInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -691,26 +690,7 @@ public void onRequestPermissionsResult(final int requestCode, final String[] per @Override public int checkPermission(final String permission, final int pid, final int uid) { int globalResult = super.checkPermission(permission, pid, uid); - - // only these permissions, which show a dialog to the user should be scoped. - boolean isDangerousPermission; - try { - PermissionInfo permissionInfo = getPackageManager().getPermissionInfo(permission, PackageManager.GET_META_DATA); - isDangerousPermission = (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) != 0; - } catch (PackageManager.NameNotFoundException e) { - return PackageManager.PERMISSION_DENIED; - } - - if (Constants.isStandaloneApp() || !isDangerousPermission) { - return globalResult; - } - - if (globalResult == PackageManager.PERMISSION_GRANTED && - mExpoKernelServiceRegistry.getPermissionsKernelService().hasGrantedPermissions(permission, mExperienceId)) { - return PackageManager.PERMISSION_GRANTED; - } else { - return PackageManager.PERMISSION_DENIED; - } + return mExpoKernelServiceRegistry.getPermissionsKernelService().getPermissions(globalResult, getPackageManager(), permission, mExperienceId); } public RNObject getDevSupportManager() { diff --git a/android/expoview/src/main/java/host/exp/exponent/kernel/services/PermissionsKernelService.java b/android/expoview/src/main/java/host/exp/exponent/kernel/services/PermissionsKernelService.java index 7d56986d4f1d8..e3eaab385be91 100644 --- a/android/expoview/src/main/java/host/exp/exponent/kernel/services/PermissionsKernelService.java +++ b/android/expoview/src/main/java/host/exp/exponent/kernel/services/PermissionsKernelService.java @@ -1,6 +1,8 @@ package host.exp.exponent.kernel.services; import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; import org.json.JSONException; import org.json.JSONObject; @@ -11,14 +13,14 @@ public class PermissionsKernelService extends BaseKernelService { - ExponentSharedPreferences mExponentSharedPreferences; + private ExponentSharedPreferences mExponentSharedPreferences; public PermissionsKernelService(Context context, ExponentSharedPreferences exponentSharedPreferences) { super(context); mExponentSharedPreferences = exponentSharedPreferences; } - public void grantPermissions(String permission, ExperienceId experienceId) { + public void grantScopedPermissions(String permission, ExperienceId experienceId) { try { JSONObject metadata = mExponentSharedPreferences.getExperienceMetadata(experienceId.get()); if (metadata == null) { @@ -49,7 +51,7 @@ public void grantPermissions(String permission, ExperienceId experienceId) { } } - public void revokePermissions(String permission, ExperienceId experienceId) { + public void revokeScopedPermissions(String permission, ExperienceId experienceId) { try { JSONObject metadata = mExponentSharedPreferences.getExperienceMetadata(experienceId.get()); if (metadata == null) { @@ -84,7 +86,7 @@ public boolean hasGrantedPermissions(String permission, ExperienceId experienceI if (permissions.has(permission)) { JSONObject permissionsObject = permissions.getJSONObject(permission); return permissionsObject.has("status") && - permissionsObject.getString("status").equals("granted"); + permissionsObject.getString("status").equals("granted"); } } } catch (JSONException e) { @@ -93,6 +95,32 @@ public boolean hasGrantedPermissions(String permission, ExperienceId experienceI return false; } + public int getPermissions(int globalPermissionStatus, PackageManager packageManager, String permission, ExperienceId experienceId) { + // only these permissions, which show a dialog to the user should be scoped. + boolean isDangerousPermission; + try { + isDangerousPermission = isDangerousPermission(permission, packageManager); + } catch (PackageManager.NameNotFoundException e) { + return PackageManager.PERMISSION_DENIED; + } + + if (Constants.isStandaloneApp() || !isDangerousPermission) { + return globalPermissionStatus; + } + + if (globalPermissionStatus == PackageManager.PERMISSION_GRANTED && + hasGrantedPermissions(permission, experienceId)) { + return PackageManager.PERMISSION_GRANTED; + } else { + return PackageManager.PERMISSION_DENIED; + } + } + + private boolean isDangerousPermission(String permission, PackageManager packageManager) throws PackageManager.NameNotFoundException { + PermissionInfo permissionInfo = packageManager.getPermissionInfo(permission, PackageManager.GET_META_DATA); + return (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) != 0; + } + @Override public void onExperienceForegrounded(ExperienceId experienceId) { diff --git a/android/expoview/src/main/java/host/exp/exponent/utils/PermissionsHelper.java b/android/expoview/src/main/java/host/exp/exponent/utils/PermissionsHelper.java index 6a518340a0ed8..b75f6228a8c31 100644 --- a/android/expoview/src/main/java/host/exp/exponent/utils/PermissionsHelper.java +++ b/android/expoview/src/main/java/host/exp/exponent/utils/PermissionsHelper.java @@ -113,7 +113,7 @@ public void onRequestPermissionsResult(int requestCode, String permissions[], in isGranted = false; break; } else if (mExperienceId != null) { - mExpoKernelServiceRegistry.getPermissionsKernelService().grantPermissions(permissions[i], mExperienceId); + mExpoKernelServiceRegistry.getPermissionsKernelService().grantScopedPermissions(permissions[i], mExperienceId); } } } @@ -188,11 +188,11 @@ public void onClick(DialogInterface dialog, int which) { mPermissionsAskedCount -= 1; switch (which) { case DialogInterface.BUTTON_POSITIVE: - mExpoKernelServiceRegistry.getPermissionsKernelService().grantPermissions(mPermission, mExperienceId); + mExpoKernelServiceRegistry.getPermissionsKernelService().grantScopedPermissions(mPermission, mExperienceId); break; case DialogInterface.BUTTON_NEGATIVE: - mExpoKernelServiceRegistry.getPermissionsKernelService().revokePermissions(mPermission, mExperienceId); + mExpoKernelServiceRegistry.getPermissionsKernelService().revokeScopedPermissions(mPermission, mExperienceId); mExperiencePermissionsGranted = false; break; } diff --git a/android/expoview/src/main/java/host/exp/exponent/utils/ScopedPermissionsRequester.java b/android/expoview/src/main/java/host/exp/exponent/utils/ScopedPermissionsRequester.java index 7df1e85f8e044..0c751836671a0 100644 --- a/android/expoview/src/main/java/host/exp/exponent/utils/ScopedPermissionsRequester.java +++ b/android/expoview/src/main/java/host/exp/exponent/utils/ScopedPermissionsRequester.java @@ -17,7 +17,6 @@ import javax.inject.Inject; import host.exp.exponent.di.NativeModuleDepsProvider; -import host.exp.exponent.experience.BaseExperienceActivity; import host.exp.exponent.experience.ReactNativeActivity; import host.exp.exponent.kernel.ExperienceId; import host.exp.exponent.kernel.services.ExpoKernelServiceRegistry; @@ -91,7 +90,7 @@ public void onRequestPermissionsResult(final String[] permissions, final int[] g if (grantResults.length > 0) { for (int i = 0; i < grantResults.length; i++) { if (grantResults[i] == PackageManager.PERMISSION_GRANTED && mExperienceId != null) { - mExpoKernelServiceRegistry.getPermissionsKernelService().grantPermissions(permissions[i], mExperienceId); + mExpoKernelServiceRegistry.getPermissionsKernelService().grantScopedPermissions(permissions[i], mExperienceId); } mPermissionsResult.put(permissions[i], grantResults[i]); } @@ -165,12 +164,12 @@ public void onClick(DialogInterface dialog, int which) { mPermissionsAskedCount -= 1; switch (which) { case DialogInterface.BUTTON_POSITIVE: - mExpoKernelServiceRegistry.getPermissionsKernelService().grantPermissions(mPermission, mExperienceId); + mExpoKernelServiceRegistry.getPermissionsKernelService().grantScopedPermissions(mPermission, mExperienceId); mPermissionsResult.put(mPermission, PackageManager.PERMISSION_GRANTED); break; case DialogInterface.BUTTON_NEGATIVE: - mExpoKernelServiceRegistry.getPermissionsKernelService().revokePermissions(mPermission, mExperienceId); + mExpoKernelServiceRegistry.getPermissionsKernelService().revokeScopedPermissions(mPermission, mExperienceId); mPermissionsResult.put(mPermission, PackageManager.PERMISSION_DENIED); break; } diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/universal/ExpoModuleRegistryAdapter.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/universal/ExpoModuleRegistryAdapter.java index 466c5bc003cee..79214fb13bde4 100644 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/universal/ExpoModuleRegistryAdapter.java +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/universal/ExpoModuleRegistryAdapter.java @@ -55,7 +55,7 @@ public List createNativeModules(ScopedContext scopedContext, Exper moduleRegistry.registerExportedModule(new ScopedErrorRecoveryModule(scopedContext, manifest, experienceId)); // Overriding expo-permissions ScopedPermissionsService - moduleRegistry.registerInternalModule(new ScopedPermissionsService(scopedContext)); + moduleRegistry.registerInternalModule(new ScopedPermissionsService(scopedContext, experienceId)); // Overriding expo-facebook moduleRegistry.registerExportedModule(new ScopedFacebookModule(scopedContext, manifest)); diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/universal/ScopedPermissionsService.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/universal/ScopedPermissionsService.kt index 5515c24a80a10..a60c7259ed38b 100644 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/universal/ScopedPermissionsService.kt +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/universal/ScopedPermissionsService.kt @@ -1,13 +1,37 @@ package versioned.host.exp.exponent.modules.universal import android.content.Context +import android.content.pm.PackageManager +import androidx.core.content.ContextCompat import expo.modules.permissions.PermissionsService +import host.exp.exponent.di.NativeModuleDepsProvider +import host.exp.exponent.kernel.ExperienceId +import host.exp.exponent.kernel.services.ExpoKernelServiceRegistry +import org.unimodules.core.ModuleRegistry import org.unimodules.interfaces.permissions.PermissionsResponseListener +import javax.inject.Inject + +class ScopedPermissionsService(context: Context, val experienceId: ExperienceId) : PermissionsService(context) { + + // This variable cannot be lateinit, cause the Location module gets permissions before this module is initialized. + @Inject + var mExpoKernelServiceRegistry: ExpoKernelServiceRegistry? = null + + override fun onCreate(moduleRegistry: ModuleRegistry) { + super.onCreate(moduleRegistry) + NativeModuleDepsProvider.getInstance().inject(ScopedPermissionsService::class.java, this) + } -class ScopedPermissionsService(context: Context) : PermissionsService(context) { // We override this to inject scoped permissions even if the device doesn't support the runtime permissions. override fun askForManifestPermissions(permissions: Array, listener: PermissionsResponseListener) { delegateRequestToActivity(permissions, listener) } + // We override this to scoped permissions in the headless mode. + override fun getManifestPermissionFromContext(permission: String): Int { + val globalPermissions = ContextCompat.checkSelfPermission(context, permission) + return mExpoKernelServiceRegistry?.permissionsKernelService?.getPermissions(globalPermissions, context.packageManager, permission, experienceId) + ?: PackageManager.PERMISSION_DENIED + } + } diff --git a/packages/expo-permissions/CHANGELOG.md b/packages/expo-permissions/CHANGELOG.md index 361902e7d4052..f2a1ced093d91 100644 --- a/packages/expo-permissions/CHANGELOG.md +++ b/packages/expo-permissions/CHANGELOG.md @@ -8,4 +8,5 @@ ### 🐛 Bug fixes +- Fix permissions in the headless mode. ([#7962](https://github.com/expo/expo/pull/7962) by [@lukmccall](https://github.com/lukmccall)) - Fixed `permission cannot be null or empty` error when asking for `WRITE_SETTINGS` permission on Android. ([#7276](https://github.com/expo/expo/pull/7276) by [@lukmccall](https://github.com/lukmccall)) diff --git a/packages/expo-permissions/android/src/main/java/expo/modules/permissions/PermissionsService.kt b/packages/expo-permissions/android/src/main/java/expo/modules/permissions/PermissionsService.kt index 18bcae71e7036..0d545af7e4c08 100644 --- a/packages/expo-permissions/android/src/main/java/expo/modules/permissions/PermissionsService.kt +++ b/packages/expo-permissions/android/src/main/java/expo/modules/permissions/PermissionsService.kt @@ -171,7 +171,13 @@ open class PermissionsService(val context: Context) : InternalModule, Permission return ContextCompat.checkSelfPermission(it, permission) } } - return PackageManager.PERMISSION_DENIED + + // We are in the headless mode. So, we ask current context. + return getManifestPermissionFromContext(permission) + } + + protected open fun getManifestPermissionFromContext(permission: String): Int { + return ContextCompat.checkSelfPermission(context, permission) } private fun canAskAgain(permission: String): Boolean { @@ -266,7 +272,7 @@ open class PermissionsService(val context: Context) : InternalModule, Permission private fun hasWriteSettingsPermission(): Boolean { return if (isRuntimePermissionsAvailable()) { - Settings.System.canWrite(mActivityProvider!!.currentActivity.applicationContext) + Settings.System.canWrite(context.applicationContext) } else { true }