diff options
13 files changed, 292 insertions, 9 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 03ef669c0675..fee8cdb1ce51 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5782,8 +5782,7 @@ public class Activity extends ContextThemeWrapper } if (!getAttributionSource().getRenouncedPermissions().isEmpty()) { - final int permissionCount = permissions.length; - for (int i = 0; i < permissionCount; i++) { + for (int i = 0; i < permissions.length; i++) { if (getAttributionSource().getRenouncedPermissions().contains(permissions[i])) { throw new IllegalArgumentException("Cannot request renounced permission: " + permissions[i]); @@ -5791,13 +5790,55 @@ public class Activity extends ContextThemeWrapper } } - PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager() - : createDeviceContext(deviceId).getPackageManager(); + final Context context = getDeviceId() == deviceId ? this : createDeviceContext(deviceId); + if (Flags.permissionRequestShortCircuitEnabled()) { + int[] permissionsState = getPermissionRequestStates(context, permissions); + boolean hasRequestablePermission = false; + for (int i = 0; i < permissionsState.length; i++) { + if (permissionsState[i] == Context.PERMISSION_REQUEST_STATE_REQUESTABLE) { + hasRequestablePermission = true; + break; + } + } + // If none of the permissions is requestable, finish the request here. + if (!hasRequestablePermission) { + mHasCurrentPermissionsRequest = true; + Log.v(TAG, "No requestable permission in the request."); + int[] results = new int[permissionsState.length]; + for (int i = 0; i < permissionsState.length; i++) { + if (permissionsState[i] == Context.PERMISSION_REQUEST_STATE_GRANTED) { + results[i] = PackageManager.PERMISSION_GRANTED; + } else { + results[i] = PackageManager.PERMISSION_DENIED; + } + } + // Currently permission request result is passed to the client app asynchronously + // in onRequestPermissionsResult, lets keep async behavior here as well. + mHandler.post(() -> { + mHasCurrentPermissionsRequest = false; + onRequestPermissionsResult(requestCode, permissions, results, deviceId); + }); + return; + } + } + + final PackageManager packageManager = context.getPackageManager(); final Intent intent = packageManager.buildRequestPermissionsIntent(permissions); startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null); mHasCurrentPermissionsRequest = true; } + @NonNull + private int[] getPermissionRequestStates(@NonNull Context deviceContext, + @NonNull String[] permissions) { + final int size = permissions.length; + int[] results = new int[size]; + for (int i = 0; i < size; i++) { + results[i] = deviceContext.getPermissionRequestState(permissions[i]); + } + return results; + } + /** * Callback for the result from requesting permissions. This method * is invoked for every call on {@link #requestPermissions} diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index dcbdc2348fbc..d8aa8b3df622 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2366,7 +2366,11 @@ class ContextImpl extends Context { Log.v(TAG, "Treating renounced permission " + permission + " as denied"); return PERMISSION_DENIED; } + int deviceId = resolveDeviceIdForPermissionCheck(permission); + return PermissionManager.checkPermission(permission, pid, uid, deviceId); + } + private int resolveDeviceIdForPermissionCheck(String permission) { // When checking a device-aware permission on a remote device, if the permission is CAMERA // or RECORD_AUDIO we need to check remote device's corresponding capability. If the remote // device doesn't have capability fall back to checking permission on the default device. @@ -2387,9 +2391,9 @@ class ContextImpl extends Context { VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId); if (virtualDevice != null) { if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO) - && !virtualDevice.hasCustomAudioInputSupport()) + && !virtualDevice.hasCustomAudioInputSupport()) || (Objects.equals(permission, Manifest.permission.CAMERA) - && !virtualDevice.hasCustomCameraSupport())) { + && !virtualDevice.hasCustomCameraSupport())) { deviceId = Context.DEVICE_ID_DEFAULT; } } else { @@ -2400,8 +2404,7 @@ class ContextImpl extends Context { } } } - - return PermissionManager.checkPermission(permission, pid, uid, deviceId); + return deviceId; } /** @hide */ @@ -2503,6 +2506,16 @@ class ContextImpl extends Context { message); } + /** @hide */ + @Override + public int getPermissionRequestState(String permission) { + Objects.requireNonNull(permission, "Permission name can't be null"); + int deviceId = resolveDeviceIdForPermissionCheck(permission); + PermissionManager permissionManager = getSystemService(PermissionManager.class); + return permissionManager.getPermissionRequestState(getOpPackageName(), permission, + deviceId); + } + @Override public void grantUriPermission(String toPackage, Uri uri, int modeFlags) { try { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 6ec6a62ff639..7abf5600d659 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -788,6 +788,40 @@ public abstract class Context { public static final int RECEIVER_NOT_EXPORTED = 0x4; /** + * The permission is granted. + * + * @hide + */ + public static final int PERMISSION_REQUEST_STATE_GRANTED = 0; + + /** + * The permission isn't granted, but apps can request the permission. When the app request + * the permission, user will be prompted with permission dialog to grant or deny the request. + * + * @hide + */ + public static final int PERMISSION_REQUEST_STATE_REQUESTABLE = 1; + + /** + * The permission is denied, and shouldn't be requested by apps. Permission request + * will be automatically denied by the system, preventing the permission dialog from being + * displayed to the user. + * + * @hide + */ + public static final int PERMISSION_REQUEST_STATE_UNREQUESTABLE = 2; + + + /** @hide */ + @IntDef(prefix = { "PERMISSION_REQUEST_STATE_" }, value = { + PERMISSION_REQUEST_STATE_GRANTED, + PERMISSION_REQUEST_STATE_REQUESTABLE, + PERMISSION_REQUEST_STATE_UNREQUESTABLE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PermissionRequestState {} + + /** * Returns an AssetManager instance for the application's package. * <p> * <strong>Note:</strong> Implementations of this method should return @@ -6989,6 +7023,31 @@ public abstract class Context { @NonNull @PermissionName String permission, @Nullable String message); /** + * Returns the permission request state for a given runtime permission. This method provides a + * streamlined mechanism for applications to determine whether a permission can be + * requested (i.e. whether the user will be prompted with a permission dialog). + * + * <p>Traditionally, determining if a permission has been permanently denied (unrequestable) + * required applications to initiate a permission request and subsequently analyze the result + * of {@link android.app.Activity#shouldShowRequestPermissionRationale} in conjunction with the + * grant result within the {@link android.app.Activity#onRequestPermissionsResult} callback. + * + * @param permission The name of the permission. + * + * @return The current request state of the specified permission, represented by one of the + * following constants: {@link PermissionRequestState#PERMISSION_REQUEST_STATE_GRANTED}, + * {@link PermissionRequestState#PERMISSION_REQUEST_STATE_REQUESTABLE}, or + * {@link PermissionRequestState#PERMISSION_REQUEST_STATE_UNREQUESTABLE}. + * + * @hide + */ + @CheckResult + @PermissionRequestState + public int getPermissionRequestState(@NonNull String permission) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** * Grant permission to access a specific Uri to another package, regardless * of whether that package has general permission to access the Uri's * content provider. This can be used to grant specific, temporary diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 413eb9886392..a146807b9fcf 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -1012,6 +1012,12 @@ public class ContextWrapper extends Context { mBase.enforceCallingOrSelfPermission(permission, message); } + /** @hide */ + @Override + public int getPermissionRequestState(String permission) { + return mBase.getPermissionRequestState(permission); + } + @Override public void grantUriPermission(String toPackage, Uri uri, int modeFlags) { mBase.grantUriPermission(toPackage, uri, modeFlags); diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 55011e52724c..b37581260bb1 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -108,6 +108,8 @@ interface IPermissionManager { int checkUidPermission(int uid, String permissionName, int deviceId); Map<String, PermissionState> getAllPermissionStates(String packageName, String persistentDeviceId, int userId); + + int getPermissionRequestState(String packageName, String permissionName, int deviceId); } /** diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 2473de4ff6d7..bdf8d23438df 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1742,6 +1742,16 @@ public final class PermissionManager { } } + private static int getPermissionRequestStateUncached(String packageName, String permission, + int deviceId) { + try { + return AppGlobals.getPermissionManager().getPermissionRequestState( + packageName, permission, deviceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Identifies a permission query. * @@ -1795,6 +1805,46 @@ public final class PermissionManager { } } + private static final class PermissionRequestStateQuery { + final String mPackageName; + final String mPermission; + final int mDeviceId; + + PermissionRequestStateQuery(@NonNull String packageName, @NonNull String permission, + int deviceId) { + mPackageName = packageName; + mPermission = permission; + mDeviceId = deviceId; + } + + @Override + public String toString() { + return TextUtils.formatSimple("PermissionRequestStateQuery(package=\"%s\"," + + " permission=\"%s\", " + "deviceId=%d)", + mPackageName, mPermission, mDeviceId); + } + + @Override + public int hashCode() { + return Objects.hash(mPackageName, mPermission, mDeviceId); + } + + @Override + public boolean equals(@Nullable Object rval) { + if (rval == null) { + return false; + } + PermissionRequestStateQuery other; + try { + other = (PermissionRequestStateQuery) rval; + } catch (ClassCastException ex) { + return false; + } + return mDeviceId == other.mDeviceId && Objects.equals(mPackageName, other.mPackageName) + && Objects.equals(mPermission, other.mPermission); + } + } + // The legacy system property "package_info" had two purposes: to invalidate PIC caches and to // signal that package information, and therefore permissions, might have changed. // AudioSystem is the only client of the signaling behavior. The "separate permissions @@ -1842,10 +1892,30 @@ public final class PermissionManager { }; /** @hide */ + private static final PropertyInvalidatedCache<PermissionRequestStateQuery, Integer> + sPermissionRequestStateCache = + new PropertyInvalidatedCache<>( + 512, CACHE_KEY_PACKAGE_INFO_CACHE, "getPermissionRequestState") { + @Override + public Integer recompute(PermissionRequestStateQuery query) { + return getPermissionRequestStateUncached(query.mPackageName, query.mPermission, + query.mDeviceId); + } + }; + + /** @hide */ public static int checkPermission(@Nullable String permission, int pid, int uid, int deviceId) { return sPermissionCache.query(new PermissionQuery(permission, pid, uid, deviceId)); } + /** @hide */ + @Context.PermissionRequestState + public int getPermissionRequestState(@NonNull String packageName, @NonNull String permission, + int deviceId) { + return sPermissionRequestStateCache.query( + new PermissionRequestStateQuery(packageName, permission, deviceId)); + } + /** * Gets the permission states for requested package and persistent device. * <p> diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 05bc69a9f1f0..672eb4caf798 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -264,6 +264,16 @@ public class PermissionManagerService extends IPermissionManager.Stub { persistentDeviceId, mPermissionManagerServiceImpl::checkUidPermission); } + @Override + @Context.PermissionRequestState + public int getPermissionRequestState(@NonNull String packageName, + @NonNull String permissionName, int deviceId) { + Objects.requireNonNull(permissionName, "permission can't be null."); + Objects.requireNonNull(packageName, "package name can't be null."); + return mPermissionManagerServiceImpl.getPermissionRequestState(packageName, permissionName, + getPersistentDeviceId(deviceId)); + } + private String getPersistentDeviceId(int deviceId) { if (deviceId == Context.DEVICE_ID_DEFAULT) { return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index ea71953d7cb3..ca70bddc5ac1 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -1014,6 +1014,11 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override + public int getPermissionRequestState(String packageName, String permName, String deviceId) { + throw new IllegalStateException("getPermissionRequestState is not supported."); + } + + @Override public Map<String, PermissionManager.PermissionState> getAllPermissionStates( @NonNull String packageName, @NonNull String deviceId, int userId) { throw new UnsupportedOperationException( diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java index 754b141ff10d..b607832767a1 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -20,6 +20,7 @@ import android.annotation.AppIdInt; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; @@ -407,6 +408,16 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte int checkUidPermission(int uid, String permName, String deviceId); /** + * Returns one of the permission state + * {@link Context.PermissionRequestState#PERMISSION_REQUEST_STATE_GRANTED}, + * {@link Context.PermissionRequestState#PERMISSION_REQUEST_STATE_REQUESTABLE}, or + * {@link Context.PermissionRequestState#PERMISSION_REQUEST_STATE_UNREQUESTABLE} + * for permission request permission flow. + */ + int getPermissionRequestState(@NonNull String packageName, @NonNull String permName, + @NonNull String deviceId); + + /** * Gets the permission states for requested package, persistent device and user. * <p> * <strong>Note: </strong>Default device permissions are not inherited in this API. Returns the diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java index c18f856594ed..ba5e97e7b113 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java @@ -247,6 +247,13 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag } @Override + public int getPermissionRequestState(String packageName, String permName, String deviceId) { + Log.i(LOG_TAG, "checkUidPermissionState(permName = " + permName + ", deviceId = " + + deviceId + ", packageName = " + packageName + ")"); + return mService.getPermissionRequestState(packageName, permName, deviceId); + } + + @Override public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName, @NonNull String deviceId, int userId) { Log.i(LOG_TAG, diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java index 40139baf0e98..008c14db8b65 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java @@ -319,6 +319,11 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override + public int getPermissionRequestState(String packageName, String permName, String deviceId) { + return mNewImplementation.getPermissionRequestState(packageName, permName, deviceId); + } + + @Override public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName, @NonNull String deviceId, int userId) { return mNewImplementation.getAllPermissionStates(packageName, deviceId, userId); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java index 981d3d92b15a..2a47f51da951 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java @@ -345,6 +345,18 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag } } + + @Override + public int getPermissionRequestState(String packageName, String permName, String deviceId) { + Trace.traceBegin(TRACE_TAG, + "TaggedTracingPermissionManagerServiceImpl#checkUidPermissionState"); + try { + return mService.getPermissionRequestState(packageName, permName, deviceId); + } finally { + Trace.traceEnd(TRACE_TAG); + } + } + @Override public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName, @NonNull String deviceId, int userId) { diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index 0b7438cd1b17..018cf20e914f 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -464,6 +464,48 @@ class PermissionService(private val service: AccessCheckingService) : return size } + override fun getPermissionRequestState( + packageName: String, + permissionName: String, + deviceId: String + ): Int { + val uid = Binder.getCallingUid() + val result = context.checkPermission(permissionName, Binder.getCallingPid(), uid) + if (result == PackageManager.PERMISSION_GRANTED) { + return Context.PERMISSION_REQUEST_STATE_GRANTED + } + + val appId = UserHandle.getAppId(uid) + val userId = UserHandle.getUserId(uid) + val packageState = + packageManagerLocal.withFilteredSnapshot(uid, userId).use { + it.getPackageState(packageName) + } ?: return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE + val androidPackage = packageState.androidPackage + ?: return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE + if (appId != packageState.appId) { + return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE + } + val permission = service.getState { + with(policy) { getPermissions()[permissionName] } + } + if (permission == null || !permission.isRuntime) { + return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE + } + if (permissionName !in androidPackage.requestedPermissions) { + return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE + } + + val permissionFlags = service.getState { + getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) + } + return if (permissionFlags.hasAnyBit(UNREQUESTABLE_MASK)) { + Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE + } else { + Context.PERMISSION_REQUEST_STATE_REQUESTABLE + } + } + override fun checkUidPermission(uid: Int, permissionName: String, deviceId: String): Int { val userId = UserHandle.getUserId(uid) if (!userManagerInternal.exists(userId)) { @@ -472,7 +514,7 @@ class PermissionService(private val service: AccessCheckingService) : // PackageManagerInternal.getPackage(int) already checks package visibility and enforces // that instant apps can't see shared UIDs. Note that on the contrary, - // Note that PackageManagerInternal.getPackage(String) doesn't perform any checks. + // PackageManagerInternal.getPackage(String) doesn't perform any checks. val androidPackage = packageManagerInternal.getPackage(uid) if (androidPackage != null) { // Note that PackageManagerInternal.getPackageStateInternal() is not filtered. |