diff --git a/core/api/current.txt b/core/api/current.txt index 407855b107cdd..2dfae26099bce 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -10876,6 +10876,8 @@ package android.content { method @Deprecated @RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void removeStickyBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle); method public abstract void revokeUriPermission(android.net.Uri, int); method public abstract void revokeUriPermission(String, android.net.Uri, int); + method public void selfRevokePermission(@NonNull String); + method public void selfRevokePermissions(@NonNull java.util.Collection); method public abstract void sendBroadcast(@RequiresPermission android.content.Intent); method public abstract void sendBroadcast(@RequiresPermission android.content.Intent, @Nullable String); method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 3a3813f29d894..18dae377cd9f2 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9584,6 +9584,7 @@ package android.permission { method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer>>); + method @BinderThread public void onSelfRevokePermissions(@NonNull String, @NonNull java.util.List, @NonNull Runnable); method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer); method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer); method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index f3e9f105500e9..fe512ffbaac3b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -113,6 +113,7 @@ import java.nio.ByteOrder; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Set; @@ -2169,6 +2170,11 @@ class ContextImpl extends Context { return checkPermission(permission, pid, uid); } + @Override + public void selfRevokePermissions(@NonNull Collection permissions) { + getSystemService(PermissionManager.class).selfRevokePermissions(permissions); + } + @Override public int checkCallingPermission(String permission) { if (permission == null) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index bccfacf708421..2df6f9ae2bd67 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -94,8 +94,11 @@ import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Interface to global information about an application environment. This is @@ -6407,6 +6410,43 @@ public abstract class Context { @Nullable String writePermission, int pid, int uid, @Intent.AccessUriMode int modeFlags, @Nullable String message); + + /** + * Triggers the asynchronous revocation of a permission. + * + * @param permName The name of the permission to be revoked. + * @see #selfRevokePermissions(Collection) + */ + public void selfRevokePermission(@NonNull String permName) { + selfRevokePermissions(Collections.singletonList(permName)); + } + + /** + * Triggers the revocation of one or more permissions for the calling package. A package is only + * able to revoke a permission under the following conditions: + *
    + *
  • Each permission in {@code permissions} must be granted to the calling package. + *
  • Each permission in {@code permissions} must be a runtime permission. + *
+ *

+ * For every permission in {@code permissions}, the entire permission group it belongs to will + * be revoked. The revocation happens asynchronously and kills all processes running in the + * calling UID. It will be triggered once it is safe to do so. In particular, it will not be + * triggered as long as the package remains in the foreground, or has any active manifest + * components (e.g. when another app is accessing a content provider in the package). + *

+ * If you want to revoke the permissions right away, you could call {@code System.exit()}, but + * this could affect other apps that are accessing your app at the moment. For example, apps + * accessing a content provider in your app will all crash. + * + * @param permissions Collection of permissions to be revoked. + * @see PackageManager#getGroupOfPlatformPermission(String, Executor, Consumer) + * @see PackageManager#getPlatformPermissionsForGroup(String, Executor, Consumer) + */ + public void selfRevokePermissions(@NonNull Collection permissions) { + throw new AbstractMethodError("Must be overridden in implementing class"); + } + /** @hide */ @IntDef(flag = true, prefix = { "CONTEXT_" }, value = { CONTEXT_INCLUDE_CODE, diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 3a02004edb1f9..805e499bba465 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -53,6 +53,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; @@ -1014,6 +1015,11 @@ public class ContextWrapper extends Context { message); } + @Override + public void selfRevokePermissions(@NonNull Collection permissions) { + mBase.selfRevokePermissions(permissions); + } + @Override public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException { diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 0e32a78411dd2..5814bac06f597 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -56,4 +56,6 @@ oneway interface IPermissionController { in AndroidFuture callback); void getUnusedAppCount( in AndroidFuture callback); + void selfRevokePermissions(in String packageName, in List permissions, + in AndroidFuture callback); } diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 90b5e518ef1bc..8e5581b1b80e4 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -76,6 +76,8 @@ interface IPermissionManager { List getSplitPermissions(); + void selfRevokePermissions(String packageName, in List permissions); + void startOneTimePermissionSession(String packageName, int userId, long timeout, int importanceToResetTimer, int importanceToKeepSessionAlive); diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index a0788e70b2476..47cd10765da02 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -817,4 +817,40 @@ public final class PermissionControllerManager { } }); } + + /** + * Triggers the revocation of one or more permissions for a package, under the following + * conditions: + *

    + *
  • The package {@code packageName} must be under the same UID as the calling process + * (typically, the target package is the calling package). + *
  • Each permission in {@code permissions} must be granted to the package + * {@code packageName}. + *
  • Each permission in {@code permissions} must be a runtime permission. + *
+ *

+ * For every permission in {@code permissions}, the entire permission group it belongs to will + * be revoked. This revocation happens asynchronously and kills all processes running in the + * same UID as {@code packageName}. It will be triggered once it is safe to do so. + * + * @param packageName The name of the package for which the permissions will be revoked. + * @param permissions List of permissions to be revoked. + * + * @see Context#selfRevokePermissions(Collection) + * + * @hide + */ + public void selfRevokePermissions(@NonNull String packageName, + @NonNull List permissions) { + mRemoteService.postAsync(service -> { + AndroidFuture future = new AndroidFuture<>(); + service.selfRevokePermissions(packageName, permissions, future); + return future; + }).whenComplete((result, err) -> { + if (err != null) { + Log.e(TAG, "Failed to self revoke " + String.join(",", permissions) + + " for package " + packageName, err); + } + }); + } } diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index c9793032c7eb2..dcbab62530b1d 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -324,6 +324,27 @@ public abstract class PermissionControllerService extends Service { @NonNull Consumer callback) { throw new AbstractMethodError("Must be overridden in implementing class"); } + + /** + * Triggers the revocation of one or more permissions for a package. This should only be called + * at the request of {@code packageName}. + *

+ * For every permission in {@code permissions}, the entire permission group it belongs to will + * be revoked. This revocation happens asynchronously and kills all processes running in the + * same UID as {@code packageName}. It will be triggered once it is safe to do so. + * + * @param packageName The name of the package for which the permissions will be revoked. + * @param permissions List of permissions to be revoked. + * @param callback Callback waiting for operation to be complete. + * + * @see PermissionManager#selfRevokePermissions(java.util.Collection) + */ + @BinderThread + public void onSelfRevokePermissions(@NonNull String packageName, + @NonNull List permissions, @NonNull Runnable callback) { + throw new AbstractMethodError("Must be overridden in implementing class"); + } + /** * Get a user-readable sentence, describing the set of privileges that are to be granted to a * companion app managing a device of the given profile. @@ -646,6 +667,20 @@ public abstract class PermissionControllerService extends Service { callback.completeExceptionally(t); } } + + @Override + public void selfRevokePermissions(@NonNull String packageName, + @NonNull List permissions, @NonNull AndroidFuture callback) { + try { + enforceSomePermissionsGrantedToCaller( + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS); + Objects.requireNonNull(callback); + onSelfRevokePermissions(packageName, permissions, + () -> callback.complete(null)); + } catch (Throwable t) { + callback.completeExceptionally(t); + } + } }; } } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 4f2287606e05e..13941dc5ef823 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -68,6 +68,7 @@ import com.android.internal.annotations.Immutable; import com.android.internal.util.CollectionUtils; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -560,6 +561,19 @@ public final class PermissionManager { } } + /** + * @see Context#selfRevokePermissions(Collection) + * @hide + */ + public void selfRevokePermissions(@NonNull Collection permissions) { + try { + mPermissionManager.selfRevokePermissions(mContext.getPackageName(), + new ArrayList(permissions)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Gets the state flags associated with a permission. * 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 85e54f3766ea1..1cfcdf51f5b8c 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -386,7 +386,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void startOneTimePermissionSession(String packageName, @UserIdInt int userId, long timeoutMillis, int importanceToResetTimer, int importanceToKeepSessionAlive) { - mContext.enforceCallingPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS, + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS, "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS + " to register permissions as one time."); Objects.requireNonNull(packageName); @@ -556,6 +557,12 @@ public class PermissionManagerService extends IPermissionManager.Stub { packageName, userId); } + @Override + public void selfRevokePermissions(@NonNull String packageName, + @NonNull List permissions) { + mPermissionManagerServiceImpl.selfRevokePermissions(packageName, permissions); + } + @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName, int userId) { 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 21cb2c96fc6a5..7833c4341a941 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -1591,6 +1591,25 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } } + @Override + public void selfRevokePermissions(String packageName, List permissions) { + final int callingUid = Binder.getCallingUid(); + int callingUserId = UserHandle.getUserId(callingUid); + int targetPackageUid = mPackageManagerInt.getPackageUid(packageName, 0, callingUserId); + if (targetPackageUid != callingUid) { + throw new SecurityException("uid " + callingUid + + " cannot revoke permissions for package " + packageName + " with uid " + + targetPackageUid); + } + for (String permName : permissions) { + if (!checkCallingOrSelfPermission(permName)) { + throw new SecurityException("uid " + callingUid + " cannot revoke permission " + + permName + " because it does not hold that permission"); + } + } + mPermissionControllerManager.selfRevokePermissions(packageName, permissions); + } + private boolean mayManageRolePermission(int uid) { final PackageManager packageManager = mContext.getPackageManager(); final String[] packageNames = packageManager.getPackagesForUid(uid); 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 3771f030aefaf..c582f9efa7a00 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -326,6 +326,26 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte */ void revokePostNotificationPermissionWithoutKillForTest(String packageName, int userId); + /** + * Triggers the revocation of one or more permissions for a package, under the following + * conditions: + *

    + *
  • The package {@code packageName} must be under the same UID as the calling process + * (typically, the target package is the calling package). + *
  • Each permission in {@code permissions} must be granted to the package + * {@code packageName}. + *
  • Each permission in {@code permissions} must be a runtime permission. + *
+ *

+ * For every permission in {@code permissions}, the entire permission group it belongs to will + * be revoked. This revocation happens asynchronously and kills all processes running in the + * same UID as {@code packageName}. It will be triggered once it is safe to do so. + * + * @param packageName The name of the package for which the permissions will be revoked. + * @param permissions List of permissions to be revoked. + */ + void selfRevokePermissions(String packageName, List permissions); + /** * Get whether you should show UI with rationale for requesting a permission. You should do this * only if you do not have the permission and the context in which the permission is requested