Skip to content

Commit

Permalink
[expo-permissions] Fix permissions in the headless mode (#7962)
Browse files Browse the repository at this point in the history
* [expo-permissions] Fix in headless mode

* [expo-permissions] Fix lateinit

* [expo-permissions] Update changelog

* [expo-permissions] Apply requested changes
  • Loading branch information
lukmccall committed Apr 28, 2020
1 parent dea1c9b commit 4b8fd15
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 36 deletions.
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down
@@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {

Expand Down
Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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;
}
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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]);
}
Expand Down Expand Up @@ -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;
}
Expand Down
Expand Up @@ -55,7 +55,7 @@ public List<NativeModule> 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));
Expand Down
@@ -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<out String>, 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
}

}
1 change: 1 addition & 0 deletions packages/expo-permissions/CHANGELOG.md
Expand Up @@ -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))
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit 4b8fd15

Please sign in to comment.