From 42d56cea68ca8e0298a88a09d4eb2aec789f3592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kosmaty?= Date: Wed, 22 Apr 2020 18:58:17 +0200 Subject: [PATCH 1/4] [expo-permissions] Fix in headless mode --- .../experience/ReactNativeActivity.java | 22 +----------- .../services/PermissionsKernelService.java | 36 ++++++++++++++++--- .../exp/exponent/utils/PermissionsHelper.java | 6 ++-- .../utils/ScopedPermissionsRequester.java | 7 ++-- .../universal/ExpoModuleRegistryAdapter.java | 2 +- .../universal/ScopedPermissionsService.kt | 23 +++++++++++- .../modules/permissions/PermissionsService.kt | 10 ++++-- 7 files changed, 70 insertions(+), 36 deletions(-) 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..903722bf50850 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().getFinalPermissions(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..531240161ccae 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 getFinalPermissions(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..2aa84fe005e14 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,34 @@ package versioned.host.exp.exponent.modules.universal import android.content.Context +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) { + + @Inject + lateinit var mExpoKernelServiceRegistry: ExpoKernelServiceRegistry + + 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.getFinalPermissions(globalPermissions, context.packageManager, permission, experienceId) + } + } 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 } From a446103203ae3e5971d82987e749e5bb3a10fa0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kosmaty?= Date: Thu, 23 Apr 2020 10:22:05 +0200 Subject: [PATCH 2/4] [expo-permissions] Fix lateinit --- .../exponent/modules/universal/ScopedPermissionsService.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 2aa84fe005e14..b9901e38667b3 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,6 +1,7 @@ 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 @@ -12,8 +13,9 @@ 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 initialized. @Inject - lateinit var mExpoKernelServiceRegistry: ExpoKernelServiceRegistry + var mExpoKernelServiceRegistry: ExpoKernelServiceRegistry? = null override fun onCreate(moduleRegistry: ModuleRegistry) { super.onCreate(moduleRegistry) @@ -28,7 +30,7 @@ class ScopedPermissionsService(context: Context, val experienceId: ExperienceId) // 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.getFinalPermissions(globalPermissions, context.packageManager, permission, experienceId) + return mExpoKernelServiceRegistry?.permissionsKernelService?.getFinalPermissions(globalPermissions, context.packageManager, permission, experienceId) ?: PackageManager.PERMISSION_DENIED } } From 726a6d06886078b25779dfb72d1d9a346804cf6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kosmaty?= Date: Thu, 23 Apr 2020 10:27:17 +0200 Subject: [PATCH 3/4] [expo-permissions] Update changelog --- packages/expo-permissions/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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)) From 644c0fbb39efecc642ed4839983033b822a6e0c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kosmaty?= Date: Thu, 23 Apr 2020 12:41:33 +0200 Subject: [PATCH 4/4] [expo-permissions] Apply requested changes --- .../host/exp/exponent/experience/ReactNativeActivity.java | 2 +- .../exponent/kernel/services/PermissionsKernelService.java | 2 +- .../exponent/modules/universal/ScopedPermissionsService.kt | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) 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 903722bf50850..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 @@ -690,7 +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); - return mExpoKernelServiceRegistry.getPermissionsKernelService().getFinalPermissions(globalResult, getPackageManager(), permission, mExperienceId); + 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 531240161ccae..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 @@ -95,7 +95,7 @@ public boolean hasGrantedPermissions(String permission, ExperienceId experienceI return false; } - public int getFinalPermissions(int globalPermissionStatus, PackageManager packageManager, String permission, ExperienceId experienceId) { + 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 { 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 b9901e38667b3..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 @@ -13,7 +13,7 @@ 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 initialized. + // This variable cannot be lateinit, cause the Location module gets permissions before this module is initialized. @Inject var mExpoKernelServiceRegistry: ExpoKernelServiceRegistry? = null @@ -30,7 +30,8 @@ class ScopedPermissionsService(context: Context, val experienceId: ExperienceId) // 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?.getFinalPermissions(globalPermissions, context.packageManager, permission, experienceId) ?: PackageManager.PERMISSION_DENIED + return mExpoKernelServiceRegistry?.permissionsKernelService?.getPermissions(globalPermissions, context.packageManager, permission, experienceId) + ?: PackageManager.PERMISSION_DENIED } }