Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[expo-permissions] Fix permissions in the headless mode #7962

Merged
merged 4 commits into from Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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