summaryrefslogtreecommitdiff
path: root/PermissionController/role-controller
diff options
context:
space:
mode:
Diffstat (limited to 'PermissionController/role-controller')
-rw-r--r--PermissionController/role-controller/Android.bp7
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java58
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java4
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java108
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Role.java134
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java8
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java271
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java30
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java45
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java21
10 files changed, 429 insertions, 257 deletions
diff --git a/PermissionController/role-controller/Android.bp b/PermissionController/role-controller/Android.bp
index 166823b08..9f217660a 100644
--- a/PermissionController/role-controller/Android.bp
+++ b/PermissionController/role-controller/Android.bp
@@ -17,6 +17,9 @@ package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
+// Any place that role-controller is added as a dependency must also include
+// "com.android.permission.flags-aconfig-java" or
+// "com.android.permission.flags-aconfig-java-export",
java_library {
name: "role-controller",
srcs: [
@@ -24,13 +27,17 @@ java_library {
],
libs: [
"androidx.annotation_annotation",
+ "com.android.permission.flags-aconfig-java",
+ "framework-annotations-lib",
],
static_libs: [
"modules-utils-build_system",
"android.app.appfunctions.exported-flags-aconfig-java",
"android.companion.virtualdevice.flags-aconfig-java-export",
+ "android.content.pm.flags-aconfig-java-export",
"android.permission.flags-aconfig-java-export",
"android.os.flags-aconfig-java-export",
+ "device_policy_aconfig_flags_java_export",
],
apex_available: [
"com.android.permission",
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java
new file mode 100644
index 000000000..a9be00806
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.behavior;
+
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+import com.android.role.controller.util.RoleFlags;
+import com.android.role.controller.util.UserUtils;
+
+import java.util.List;
+
+public class ReservedForTestingProfileGroupExclusivityRoleBehavior implements RoleBehavior {
+ @Nullable
+ @Override
+ public List<String> getDefaultHoldersAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ if (RoleFlags.isProfileGroupExclusivityAvailable()) {
+ Context userContext = UserUtils.getUserContext(context, user);
+ RoleManager roleManager = userContext.getSystemService(RoleManager.class);
+ return roleManager.getDefaultHoldersForTest(role.getName());
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ if (RoleFlags.isProfileGroupExclusivityAvailable()) {
+ Context userContext = UserUtils.getUserContext(context, user);
+ RoleManager roleManager = userContext.getSystemService(RoleManager.class);
+ return roleManager.isRoleVisibleForTest(role.getName());
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java b/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java
index 1c71e0c99..56c4944a0 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java
@@ -23,6 +23,7 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.role.controller.util.PackageUtils;
@@ -130,7 +131,8 @@ public class AppOp {
return Permissions.setAppOpUidModeAsUser(packageName, mName, defaultMode, user, context);
}
- boolean isAvailableByFeatureFlagAndSdkVersion() {
+ @VisibleForTesting
+ public boolean isAvailableByFeatureFlagAndSdkVersion() {
if (mFeatureFlag != null && !mFeatureFlag.get()) {
return false;
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java b/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java
index e788fdce1..820ff3d4e 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java
@@ -51,11 +51,13 @@ public class Permissions {
private static final boolean DEBUG = false;
+ private static final Object sPermissionInfoLock = new Object();
+ private static final ArrayMap<String, Boolean> sIsRuntimePermission = new ArrayMap<>();
+ private static final ArrayMap<String, Boolean> sIsRestrictedPermission = new ArrayMap<>();
+
+ private static final Object sForegroundBackgroundPermissionMappingsLock = new Object();
private static ArrayMap<String, String> sForegroundToBackgroundPermission;
private static ArrayMap<String, List<String>> sBackgroundToForegroundPermissions;
- private static final Object sForegroundBackgroundPermissionMappingsLock = new Object();
-
- private static final ArrayMap<String, Boolean> sRestrictedPermissions = new ArrayMap<>();
/**
* Filter a list of permissions based on their SDK versions.
@@ -86,8 +88,9 @@ public class Permissions {
*
* @param packageName the package name of the application to be granted permissions to
* @param permissions the list of permissions to be granted
- * @param overrideDisabledSystemPackage whether to ignore the permissions of a disabled system
- * package (if this package is an updated system package)
+ * @param ignoreDisabledSystemPackage whether to ignore the requested permissions of a disabled
+ * system package (if this package is an updated system
+ * package) when granting runtime permissions
* @param overrideUserSetAndFixed whether to override user set and fixed flags on the permission
* @param setGrantedByRole whether the permissions will be granted as granted-by-role
* @param setGrantedByDefault whether the permissions will be granted as granted-by-default
@@ -101,7 +104,7 @@ public class Permissions {
* PackageInfo, java.util.Set, boolean, boolean, int)
*/
public static boolean grantAsUser(@NonNull String packageName,
- @NonNull List<String> permissions, boolean overrideDisabledSystemPackage,
+ @NonNull List<String> permissions, boolean ignoreDisabledSystemPackage,
boolean overrideUserSetAndFixed, boolean setGrantedByRole, boolean setGrantedByDefault,
boolean setSystemFixed, @NonNull UserHandle user, @NonNull Context context) {
if (setGrantedByRole == setGrantedByDefault) {
@@ -145,15 +148,17 @@ public class Permissions {
// choice to grant this app the permissions needed to function. For all other
// apps, (default grants on first boot and user creation) we don't grant default
// permissions if the version on the system image does not declare them.
- if (!overrideDisabledSystemPackage && isUpdatedSystemApp(packageInfo)) {
+ if (!ignoreDisabledSystemPackage && isUpdatedSystemApp(packageInfo)) {
PackageInfo disabledSystemPackageInfo = getFactoryPackageInfoAsUser(packageName, user,
context);
if (disabledSystemPackageInfo != null) {
- if (ArrayUtils.isEmpty(disabledSystemPackageInfo.requestedPermissions)) {
- return false;
+ for (int i = permissionsToGrant.size() - 1; i >= 0; i--) {
+ String permission = permissionsToGrant.valueAt(i);
+ if (isRuntimePermission(permission, context) && !ArrayUtils.contains(
+ disabledSystemPackageInfo.requestedPermissions, permission)) {
+ permissionsToGrant.removeAt(i);
+ }
}
- CollectionUtils.retainAll(permissionsToGrant,
- disabledSystemPackageInfo.requestedPermissions);
if (permissionsToGrant.isEmpty()) {
return false;
}
@@ -258,7 +263,8 @@ public class Permissions {
if (!wasPermissionOrAppOpGranted) {
// If we've granted a permission which wasn't granted, it's no longer user set or fixed.
newMask |= PackageManager.FLAG_PERMISSION_USER_FIXED
- | PackageManager.FLAG_PERMISSION_USER_SET;
+ | PackageManager.FLAG_PERMISSION_USER_SET
+ | PackageManager.FLAG_PERMISSION_ONE_TIME;
}
// If a component gets a permission for being the default handler A and also default handler
// B, we grant the weaker grant form. This only applies to default permission grant.
@@ -629,7 +635,8 @@ public class Permissions {
}
if (!overrideUserSetAndFixed) {
fixedFlags |= PackageManager.FLAG_PERMISSION_USER_FIXED
- | PackageManager.FLAG_PERMISSION_USER_SET;
+ | PackageManager.FLAG_PERMISSION_USER_SET
+ | PackageManager.FLAG_PERMISSION_ONE_TIME;
}
return (flags & fixedFlags) != 0;
}
@@ -701,6 +708,50 @@ public class Permissions {
return true;
}
+ private static boolean isRuntimePermission(@NonNull String permission,
+ @NonNull Context context) {
+ synchronized (sPermissionInfoLock) {
+ Boolean isRuntimePermission = sIsRuntimePermission.get(permission);
+ if (isRuntimePermission != null) {
+ return isRuntimePermission;
+ }
+ fetchPermissionInfoLocked(permission, context);
+ return sIsRuntimePermission.get(permission);
+ }
+ }
+
+ private static boolean isRestrictedPermission(@NonNull String permission,
+ @NonNull Context context) {
+ synchronized (sPermissionInfoLock) {
+ Boolean isRestrictedPermission = sIsRestrictedPermission.get(permission);
+ if (isRestrictedPermission != null) {
+ return isRestrictedPermission;
+ }
+ fetchPermissionInfoLocked(permission, context);
+ return sIsRestrictedPermission.get(permission);
+ }
+ }
+
+ private static void fetchPermissionInfoLocked(@NonNull String permission,
+ @NonNull Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ PermissionInfo permissionInfo = null;
+ try {
+ permissionInfo = packageManager.getPermissionInfo(permission, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Cannot get PermissionInfo for permission: " + permission);
+ }
+
+ // Don't expect that to be a transient error, so we can still cache the failed information.
+ boolean isRuntimePermission = permissionInfo != null
+ && permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS;
+ boolean isRestrictedPermission = permissionInfo != null
+ && (permissionInfo.flags & (PermissionInfo.FLAG_SOFT_RESTRICTED
+ | PermissionInfo.FLAG_HARD_RESTRICTED)) != 0;
+ sIsRuntimePermission.put(permission, isRuntimePermission);
+ sIsRestrictedPermission.put(permission, isRestrictedPermission);
+ }
+
private static boolean isForegroundPermission(@NonNull String permission,
@NonNull Context context) {
ensureForegroundBackgroundPermissionMappings(context);
@@ -731,40 +782,13 @@ public class Permissions {
synchronized (sForegroundBackgroundPermissionMappingsLock) {
if (sForegroundToBackgroundPermission == null
&& sBackgroundToForegroundPermissions == null) {
- createForegroundBackgroundPermissionMappings(context);
+ createForegroundBackgroundPermissionMappingsLocked(context);
}
}
}
- private static boolean isRestrictedPermission(@NonNull String permission,
+ private static void createForegroundBackgroundPermissionMappingsLocked(
@NonNull Context context) {
- synchronized (sRestrictedPermissions) {
- if (sRestrictedPermissions.containsKey(permission)) {
- return sRestrictedPermissions.get(permission);
- }
- }
-
- PackageManager packageManager = context.getPackageManager();
- PermissionInfo permissionInfo = null;
- try {
- permissionInfo = packageManager.getPermissionInfo(permission, 0);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(LOG_TAG, "Cannot get PermissionInfo for permission: " + permission);
- }
-
- // Don't expect that to be a transient error, so we can still cache the failed information.
- boolean isRestrictedPermission = permissionInfo != null
- && (permissionInfo.flags & (PermissionInfo.FLAG_SOFT_RESTRICTED
- | PermissionInfo.FLAG_HARD_RESTRICTED)) != 0;
-
- synchronized (sRestrictedPermissions) {
- sRestrictedPermissions.put(permission, isRestrictedPermission);
- }
-
- return isRestrictedPermission;
- }
-
- private static void createForegroundBackgroundPermissionMappings(@NonNull Context context) {
List<String> permissions = new ArrayList<>();
sBackgroundToForegroundPermissions = new ArrayMap<>();
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
index 04fd615e1..02fa0d455 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
@@ -37,17 +37,23 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseBooleanArray;
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.role.controller.util.CollectionUtils;
import com.android.role.controller.util.PackageUtils;
+import com.android.role.controller.util.RoleFlags;
import com.android.role.controller.util.RoleManagerCompat;
import com.android.role.controller.util.UserUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -82,6 +88,36 @@ public class Role {
private static final String CERTIFICATE_SEPARATOR = ":";
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ EXCLUSIVITY_NONE,
+ EXCLUSIVITY_USER,
+ EXCLUSIVITY_PROFILE_GROUP
+ })
+ public @interface Exclusivity {}
+
+ /**
+ * Does not enforce any exclusivity, which means multiple apps may hold this role in a user.
+ */
+ public static final int EXCLUSIVITY_NONE = 0;
+
+ /** Enforces exclusivity within one user. */
+ public static final int EXCLUSIVITY_USER = 1;
+
+ /**
+ * Enforces exclusivity across all users (including profile users) in the same profile group.
+ */
+ public static final int EXCLUSIVITY_PROFILE_GROUP = 2;
+
+ /** Set of valid exclusivity values. */
+ private static final SparseBooleanArray sExclusivityValues = new SparseBooleanArray();
+ static {
+ sExclusivityValues.put(EXCLUSIVITY_NONE, true);
+ sExclusivityValues.put(EXCLUSIVITY_USER, true);
+ sExclusivityValues.put(EXCLUSIVITY_PROFILE_GROUP,
+ RoleFlags.isProfileGroupExclusivityAvailable());
+ }
+
/**
* The name of this role. Must be unique.
*/
@@ -109,9 +145,10 @@ public class Role {
private final int mDescriptionResource;
/**
- * Whether this role is exclusive, i.e. allows at most one holder.
+ * The exclusivity of this role, i.e. whether this role allows multiple holders, or allows at
+ * most one holder within a user or a profile group.
*/
- private final boolean mExclusive;
+ private final int mExclusivity;
/**
* Whether this role should fall back to the default holder.
@@ -185,8 +222,8 @@ public class Role {
/**
* Whether the UI for this role will show the "None" item. Only valid if this role is
- * {@link #mExclusive exclusive}, and {@link #getFallbackHolder(Context)} should also return
- * empty to allow actually selecting "None".
+ * {@link #isExclusive()}, and {@link #getFallbackHolder(Context)} should
+ * also return empty to allow actually selecting "None".
*/
private final boolean mShowNone;
@@ -240,14 +277,14 @@ public class Role {
public Role(@NonNull String name, boolean allowBypassingQualification,
@Nullable RoleBehavior behavior, @Nullable String defaultHoldersResourceName,
- @StringRes int descriptionResource, boolean exclusive, boolean fallBackToDefaultHolder,
- @Nullable Supplier<Boolean> featureFlag, @StringRes int labelResource,
- int maxSdkVersion, int minSdkVersion, boolean onlyGrantWhenAdded,
- boolean overrideUserWhenGranting, @StringRes int requestDescriptionResource,
- @StringRes int requestTitleResource, boolean requestable,
- @StringRes int searchKeywordsResource, @StringRes int shortLabelResource,
- boolean showNone, boolean statik, boolean systemOnly, boolean visible,
- @NonNull List<RequiredComponent> requiredComponents,
+ @StringRes int descriptionResource, @Exclusivity int exclusivity,
+ boolean fallBackToDefaultHolder, @Nullable Supplier<Boolean> featureFlag,
+ @StringRes int labelResource, int maxSdkVersion, int minSdkVersion,
+ boolean onlyGrantWhenAdded, boolean overrideUserWhenGranting,
+ @StringRes int requestDescriptionResource, @StringRes int requestTitleResource,
+ boolean requestable, @StringRes int searchKeywordsResource,
+ @StringRes int shortLabelResource, boolean showNone, boolean statik, boolean systemOnly,
+ boolean visible, @NonNull List<RequiredComponent> requiredComponents,
@NonNull List<Permission> permissions, @NonNull List<Permission> appOpPermissions,
@NonNull List<AppOp> appOps, @NonNull List<PreferredActivity> preferredActivities,
@Nullable String uiBehaviorName) {
@@ -256,7 +293,7 @@ public class Role {
mBehavior = behavior;
mDefaultHoldersResourceName = defaultHoldersResourceName;
mDescriptionResource = descriptionResource;
- mExclusive = exclusive;
+ mExclusivity = exclusivity;
mFallBackToDefaultHolder = fallBackToDefaultHolder;
mFeatureFlag = featureFlag;
mLabelResource = labelResource;
@@ -297,7 +334,29 @@ public class Role {
}
public boolean isExclusive() {
- return mExclusive;
+ return getExclusivity() != EXCLUSIVITY_NONE;
+ }
+
+ @Exclusivity
+ public int getExclusivity() {
+ if (com.android.permission.flags.Flags.crossUserRoleEnabled() && mBehavior != null) {
+ Integer exclusivity = mBehavior.getExclusivity();
+ if (exclusivity != null) {
+ if (!sExclusivityValues.get(exclusivity)) {
+ throw new IllegalArgumentException("Invalid exclusivity: " + exclusivity);
+ }
+ if (mShowNone && exclusivity == EXCLUSIVITY_NONE) {
+ throw new IllegalArgumentException(
+ "Role cannot be non-exclusive when showNone is true: " + exclusivity);
+ }
+ if (!mPreferredActivities.isEmpty() && exclusivity == EXCLUSIVITY_PROFILE_GROUP) {
+ throw new IllegalArgumentException(
+ "Role cannot have preferred activities when exclusivity is profileGroup");
+ }
+ return exclusivity;
+ }
+ }
+ return mExclusivity;
}
@Nullable
@@ -413,8 +472,25 @@ public class Role {
if (!isAvailableByFeatureFlagAndSdkVersion()) {
return false;
}
+
+ if (getExclusivity() == EXCLUSIVITY_PROFILE_GROUP
+ && UserUtils.isPrivateProfile(user, context)) {
+ return false;
+ }
+
if (mBehavior != null) {
- return mBehavior.isAvailableAsUser(this, user, context);
+ boolean isAvailableAsUser = mBehavior.isAvailableAsUser(this, user, context);
+ // Ensure that cross-user role is only available if also available for
+ // the profile-group's full user
+ if (isAvailableAsUser && getExclusivity() == EXCLUSIVITY_PROFILE_GROUP) {
+ UserHandle profileParent = UserUtils.getProfileParentOrSelf(user, context);
+ if (!Objects.equals(profileParent, user)
+ && !mBehavior.isAvailableAsUser(this, profileParent, context)) {
+ throw new IllegalArgumentException("Role is not available for profile parent: "
+ + profileParent.getIdentifier());
+ }
+ }
+ return isAvailableAsUser;
}
return true;
}
@@ -424,7 +500,8 @@ public class Role {
*
* @return whether this role is available based on SDK version
*/
- boolean isAvailableByFeatureFlagAndSdkVersion() {
+ @VisibleForTesting
+ public boolean isAvailableByFeatureFlagAndSdkVersion() {
if (mFeatureFlag != null && !mFeatureFlag.get()) {
return false;
}
@@ -449,6 +526,12 @@ public class Role {
@NonNull
public List<String> getDefaultHoldersAsUser(@NonNull UserHandle user,
@NonNull Context context) {
+ // Do not allow default role holder for non-active user if the role is exclusive to profile
+ // group
+ if (isNonActiveUserForProfileGroupExclusiveRole(user, context)) {
+ return Collections.emptyList();
+ }
+
if (mBehavior != null) {
List<String> defaultHolders = mBehavior.getDefaultHoldersAsUser(this, user, context);
if (defaultHolders != null) {
@@ -560,6 +643,10 @@ public class Role {
if (!RoleManagerCompat.isRoleFallbackEnabledAsUser(this, user, context)) {
return null;
}
+ // Do not fall back for non-active user if the role is exclusive to profile group
+ if (isNonActiveUserForProfileGroupExclusiveRole(user, context)) {
+ return null;
+ }
if (mFallBackToDefaultHolder) {
return CollectionUtils.firstOrNull(getDefaultHoldersAsUser(user, context));
}
@@ -569,6 +656,17 @@ public class Role {
return null;
}
+ private boolean isNonActiveUserForProfileGroupExclusiveRole(@NonNull UserHandle user,
+ @NonNull Context context) {
+ if (RoleFlags.isProfileGroupExclusivityAvailable()
+ && getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ Context userContext = UserUtils.getUserContext(context, user);
+ RoleManager userRoleManager = userContext.getSystemService(RoleManager.class);
+ return !Objects.equals(userRoleManager.getActiveUserForRole(mName), user);
+ }
+ return false;
+ }
+
/**
* Check whether this role is allowed to bypass qualification, if enabled globally.
*
@@ -1039,7 +1137,7 @@ public class Role {
*/
@Nullable
public Intent getRestrictionIntentAsUser(@NonNull UserHandle user, @NonNull Context context) {
- if (SdkLevel.isAtLeastU() && mExclusive) {
+ if (SdkLevel.isAtLeastU() && isExclusive()) {
UserManager userManager = context.getSystemService(UserManager.class);
if (userManager.hasUserRestrictionForUser(UserManager.DISALLOW_CONFIG_DEFAULT_APPS,
user)) {
@@ -1102,7 +1200,7 @@ public class Role {
+ ", mBehavior=" + mBehavior
+ ", mDefaultHoldersResourceName=" + mDefaultHoldersResourceName
+ ", mDescriptionResource=" + mDescriptionResource
- + ", mExclusive=" + mExclusive
+ + ", mExclusivity=" + mExclusivity
+ ", mFallBackToDefaultHolder=" + mFallBackToDefaultHolder
+ ", mFeatureFlag=" + mFeatureFlag
+ ", mLabelResource=" + mLabelResource
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java
index 3849a50e3..86ca8e2ce 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java
@@ -32,6 +32,14 @@ import java.util.List;
public interface RoleBehavior {
/**
+ * @see Role#getExclusivity()
+ */
+ @Nullable
+ default Integer getExclusivity() {
+ return null;
+ }
+
+ /**
* @see Role#onRoleAddedAsUser(UserHandle, Context)
*/
default void onRoleAddedAsUser(@NonNull Role role, @NonNull UserHandle user,
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java b/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java
index f1a275daf..4b05554e3 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java
@@ -19,12 +19,9 @@ package com.android.role.controller.model;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Build;
-import android.os.Process;
import android.permission.flags.Flags;
import android.util.ArrayMap;
import android.util.Log;
@@ -92,6 +89,7 @@ public class RoleParser {
private static final String ATTRIBUTE_DEFAULT_HOLDERS = "defaultHolders";
private static final String ATTRIBUTE_DESCRIPTION = "description";
private static final String ATTRIBUTE_EXCLUSIVE = "exclusive";
+ private static final String ATTRIBUTE_EXCLUSIVITY = "exclusivity";
private static final String ATTRIBUTE_FALL_BACK_TO_DEFAULT_HOLDER = "fallBackToDefaultHolder";
private static final String ATTRIBUTE_FEATURE_FLAG = "featureFlag";
private static final String ATTRIBUTE_LABEL = "label";
@@ -138,6 +136,10 @@ public class RoleParser {
sModeNameToMode.put(MODE_NAME_FOREGROUND, AppOpsManager.MODE_FOREGROUND);
}
+ private static final String EXCLUSIVITY_NONE = "none";
+ private static final String EXCLUSIVITY_USER = "user";
+ private static final String EXCLUSIVITY_PROFILE_GROUP = "profileGroup";
+
private static final Supplier<Boolean> sFeatureFlagFallback = () -> false;
private static final ArrayMap<Class<?>, Class<?>> sPrimitiveToWrapperClass = new ArrayMap<>();
@@ -156,16 +158,16 @@ public class RoleParser {
@NonNull
private final Context mContext;
- private final boolean mValidationEnabled;
+ private final boolean mThrowOnError;
public RoleParser(@NonNull Context context) {
this(context, false);
}
@VisibleForTesting
- public RoleParser(@NonNull Context context, boolean validationEnabled) {
+ public RoleParser(@NonNull Context context, boolean throwOnError) {
mContext = context;
- mValidationEnabled = validationEnabled;
+ mThrowOnError = throwOnError;
}
/**
@@ -175,18 +177,21 @@ public class RoleParser {
*/
@NonNull
public ArrayMap<String, Role> parse() {
+ Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> xml = parseRolesXml();
+ if (xml == null) {
+ return new ArrayMap<>();
+ }
+ return xml.second;
+ }
+
+ @Nullable
+ @VisibleForTesting
+ public Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> parseRolesXml() {
try (XmlResourceParser parser = getRolesXml()) {
- Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> xml = parseXml(parser);
- if (xml == null) {
- return new ArrayMap<>();
- }
- ArrayMap<String, PermissionSet> permissionSets = xml.first;
- ArrayMap<String, Role> roles = xml.second;
- validateResult(permissionSets, roles);
- return roles;
+ return parseXml(parser);
} catch (XmlPullParserException | IOException e) {
throwOrLogMessage("Unable to parse roles.xml", e);
- return new ArrayMap<>();
+ return null;
}
}
@@ -413,13 +418,45 @@ public class RoleParser {
shortLabelResource = 0;
}
- Boolean exclusive = requireAttributeBooleanValue(parser, ATTRIBUTE_EXCLUSIVE, true,
- TAG_ROLE);
- if (exclusive == null) {
- skipCurrentTag(parser);
- return null;
+ int exclusivity;
+ if (com.android.permission.flags.Flags.crossUserRoleEnabled()) {
+ String exclusivityName = requireAttributeValue(parser, ATTRIBUTE_EXCLUSIVITY, TAG_ROLE);
+ if (exclusivityName == null) {
+ skipCurrentTag(parser);
+ return null;
+ }
+ switch (exclusivityName) {
+ case EXCLUSIVITY_NONE:
+ exclusivity = Role.EXCLUSIVITY_NONE;
+ break;
+ case EXCLUSIVITY_USER:
+ exclusivity = Role.EXCLUSIVITY_USER;
+ break;
+ case EXCLUSIVITY_PROFILE_GROUP:
+ // TODO(b/372743073): change to isAtLeastB once available
+ // EXCLUSIVITY_PROFILE behavior only available for B+
+ // fallback to default of EXCLUSIVITY_USER
+ exclusivity = SdkLevel.isAtLeastV()
+ ? Role.EXCLUSIVITY_PROFILE_GROUP
+ : Role.EXCLUSIVITY_USER;
+ break;
+ default:
+ throwOrLogMessage("Invalid value for \"exclusivity\" on <role>: " + name
+ + ", exclusivity: " + exclusivityName);
+ skipCurrentTag(parser);
+ return null;
+ }
+ } else {
+ Boolean exclusive =
+ requireAttributeBooleanValue(parser, ATTRIBUTE_EXCLUSIVE, true, TAG_ROLE);
+ if (exclusive == null) {
+ skipCurrentTag(parser);
+ return null;
+ }
+ exclusivity = exclusive ? Role.EXCLUSIVITY_USER : Role.EXCLUSIVITY_NONE;
}
+
boolean fallBackToDefaultHolder = getAttributeBooleanValue(parser,
ATTRIBUTE_FALL_BACK_TO_DEFAULT_HOLDER, false);
@@ -470,7 +507,7 @@ public class RoleParser {
0);
boolean showNone = getAttributeBooleanValue(parser, ATTRIBUTE_SHOW_NONE, false);
- if (showNone && !exclusive) {
+ if (showNone && exclusivity == Role.EXCLUSIVITY_NONE) {
throwOrLogMessage("showNone=\"true\" is invalid for a non-exclusive role: " + name);
skipCurrentTag(parser);
return null;
@@ -543,6 +580,12 @@ public class RoleParser {
skipCurrentTag(parser);
continue;
}
+ if (exclusivity == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ throwOrLogMessage("<preferred-activities> is not supported for a"
+ + " profile-group-exclusive role: " + name);
+ skipCurrentTag(parser);
+ continue;
+ }
preferredActivities = parsePreferredActivities(parser);
break;
default:
@@ -567,12 +610,12 @@ public class RoleParser {
preferredActivities = Collections.emptyList();
}
return new Role(name, allowBypassingQualification, behavior, defaultHoldersResourceName,
- descriptionResource, exclusive, fallBackToDefaultHolder, featureFlag, labelResource,
- maxSdkVersion, minSdkVersion, onlyGrantWhenAdded, overrideUserWhenGranting,
- requestDescriptionResource, requestTitleResource, requestable,
- searchKeywordsResource, shortLabelResource, showNone, statik, systemOnly, visible,
- requiredComponents, permissions, appOpPermissions, appOps, preferredActivities,
- uiBehaviorName);
+ descriptionResource, exclusivity, fallBackToDefaultHolder, featureFlag,
+ labelResource, maxSdkVersion, minSdkVersion, onlyGrantWhenAdded,
+ overrideUserWhenGranting, requestDescriptionResource, requestTitleResource,
+ requestable, searchKeywordsResource, shortLabelResource, showNone, statik,
+ systemOnly, visible, requiredComponents, permissions, appOpPermissions, appOps,
+ preferredActivities, uiBehaviorName);
}
@NonNull
@@ -624,7 +667,7 @@ public class RoleParser {
int queryFlags = getAttributeIntValue(parser, ATTRIBUTE_QUERY_FLAGS, 0);
IntentFilterData intentFilterData = null;
List<RequiredMetaData> metaData = new ArrayList<>();
- List<String> validationMetaDataNames = mValidationEnabled ? new ArrayList<>() : null;
+ List<String> validationMetaDataNames = mThrowOnError ? new ArrayList<>() : null;
int type;
int depth;
@@ -651,7 +694,7 @@ public class RoleParser {
if (metaDataName == null) {
continue;
}
- if (mValidationEnabled) {
+ if (mThrowOnError) {
validateNoDuplicateElement(metaDataName, validationMetaDataNames,
"meta data");
}
@@ -668,7 +711,7 @@ public class RoleParser {
RequiredMetaData requiredMetaData = new RequiredMetaData(metaDataName,
metaDataValue, metaDataProhibited);
metaData.add(requiredMetaData);
- if (mValidationEnabled) {
+ if (mThrowOnError) {
validationMetaDataNames.add(metaDataName);
}
break;
@@ -1204,7 +1247,7 @@ public class RoleParser {
}
private void throwOrLogMessage(String message) {
- if (mValidationEnabled) {
+ if (mThrowOnError) {
throw new IllegalArgumentException(message);
} else {
Log.wtf(LOG_TAG, message);
@@ -1212,7 +1255,7 @@ public class RoleParser {
}
private void throwOrLogMessage(String message, Throwable cause) {
- if (mValidationEnabled) {
+ if (mThrowOnError) {
throw new IllegalArgumentException(message, cause);
} else {
Log.wtf(LOG_TAG, message, cause);
@@ -1222,168 +1265,4 @@ public class RoleParser {
private void throwOrLogForUnknownTag(@NonNull XmlResourceParser parser) {
throwOrLogMessage("Unknown tag: " + parser.getName());
}
-
- /**
- * Validates the permission names with {@code PackageManager} and ensures that all app ops with
- * a permission in {@code AppOpsManager} have declared that permission in its role and ensures
- * that all preferred activities are listed in the required components.
- */
- private void validateResult(@NonNull ArrayMap<String, PermissionSet> permissionSets,
- @NonNull ArrayMap<String, Role> roles) {
- if (!mValidationEnabled) {
- return;
- }
-
- int permissionSetsSize = permissionSets.size();
- for (int permissionSetsIndex = 0; permissionSetsIndex < permissionSetsSize;
- permissionSetsIndex++) {
- PermissionSet permissionSet = permissionSets.valueAt(permissionSetsIndex);
-
- List<Permission> permissions = permissionSet.getPermissions();
- int permissionsSize = permissions.size();
- for (int permissionsIndex = 0; permissionsIndex < permissionsSize; permissionsIndex++) {
- Permission permission = permissions.get(permissionsIndex);
-
- validatePermission(permission);
- }
- }
-
- int rolesSize = roles.size();
- for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) {
- Role role = roles.valueAt(rolesIndex);
-
- if (!role.isAvailableByFeatureFlagAndSdkVersion()) {
- continue;
- }
-
- List<RequiredComponent> requiredComponents = role.getRequiredComponents();
- int requiredComponentsSize = requiredComponents.size();
- for (int requiredComponentsIndex = 0; requiredComponentsIndex < requiredComponentsSize;
- requiredComponentsIndex++) {
- RequiredComponent requiredComponent = requiredComponents.get(
- requiredComponentsIndex);
-
- String permission = requiredComponent.getPermission();
- if (permission != null) {
- validatePermission(permission);
- }
- }
-
- List<Permission> permissions = role.getPermissions();
- int permissionsSize = permissions.size();
- for (int i = 0; i < permissionsSize; i++) {
- Permission permission = permissions.get(i);
-
- validatePermission(permission);
- }
-
- List<AppOp> appOps = role.getAppOps();
- int appOpsSize = appOps.size();
- for (int i = 0; i < appOpsSize; i++) {
- AppOp appOp = appOps.get(i);
-
- validateAppOp(appOp);
- }
-
- List<Permission> appOpPermissions = role.getAppOpPermissions();
- int appOpPermissionsSize = appOpPermissions.size();
- for (int i = 0; i < appOpPermissionsSize; i++) {
- validateAppOpPermission(appOpPermissions.get(i));
- }
-
- List<PreferredActivity> preferredActivities = role.getPreferredActivities();
- int preferredActivitiesSize = preferredActivities.size();
- for (int preferredActivitiesIndex = 0;
- preferredActivitiesIndex < preferredActivitiesSize;
- preferredActivitiesIndex++) {
- PreferredActivity preferredActivity = preferredActivities.get(
- preferredActivitiesIndex);
-
- if (!role.getRequiredComponents().contains(preferredActivity.getActivity())) {
- throw new IllegalArgumentException("<activity> of <preferred-activity> not"
- + " required in <required-components>, role: " + role.getName()
- + ", preferred activity: " + preferredActivity);
- }
- }
- }
- }
-
- private void validatePermission(@NonNull Permission permission) {
- if (!permission.isAvailableAsUser(Process.myUserHandle(), mContext)) {
- return;
- }
- validatePermission(permission.getName(), true);
- }
-
- private void validatePermission(@NonNull String permission) {
- validatePermission(permission, false);
- }
-
- private void validatePermission(@NonNull String permission, boolean enforceIsRuntimeOrRole) {
- PackageManager packageManager = mContext.getPackageManager();
- boolean isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
- // Skip validation for car permissions which may not be available on all build targets.
- if (!isAutomotive && permission.startsWith("android.car")) {
- return;
- }
-
- PermissionInfo permissionInfo;
- try {
- permissionInfo = packageManager.getPermissionInfo(permission, 0);
- } catch (PackageManager.NameNotFoundException e) {
- throw new IllegalArgumentException("Unknown permission: " + permission, e);
- }
-
- if (enforceIsRuntimeOrRole) {
- if (!(permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS
- || (permissionInfo.getProtectionFlags() & PermissionInfo.PROTECTION_FLAG_ROLE)
- == PermissionInfo.PROTECTION_FLAG_ROLE)) {
- throw new IllegalArgumentException(
- "Permission is not a runtime or role permission: " + permission);
- }
- }
- }
-
- private void validateAppOpPermission(@NonNull Permission appOpPermission) {
- if (!appOpPermission.isAvailableAsUser(Process.myUserHandle(), mContext)) {
- return;
- }
- validateAppOpPermission(appOpPermission.getName());
- }
-
- private void validateAppOpPermission(@NonNull String appOpPermission) {
- PackageManager packageManager = mContext.getPackageManager();
- PermissionInfo permissionInfo;
- try {
- permissionInfo = packageManager.getPermissionInfo(appOpPermission, 0);
- } catch (PackageManager.NameNotFoundException e) {
- throw new IllegalArgumentException("Unknown app op permission: " + appOpPermission, e);
- }
- if ((permissionInfo.getProtectionFlags() & PermissionInfo.PROTECTION_FLAG_APPOP)
- != PermissionInfo.PROTECTION_FLAG_APPOP) {
- throw new IllegalArgumentException("Permission is not an app op permission: "
- + appOpPermission);
- }
- }
-
- private void validateAppOp(@NonNull AppOp appOp) {
- if (!appOp.isAvailableByFeatureFlagAndSdkVersion()) {
- return;
- }
- // This throws IllegalArgumentException if app op is unknown.
- String permission = AppOpsManager.opToPermission(appOp.getName());
- if (permission != null) {
- PackageManager packageManager = mContext.getPackageManager();
- PermissionInfo permissionInfo;
- try {
- permissionInfo = packageManager.getPermissionInfo(permission, 0);
- } catch (PackageManager.NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS) {
- throw new IllegalArgumentException("App op has an associated runtime permission: "
- + appOp.getName());
- }
- }
- }
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java b/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java
index bc7562c11..d00fd47af 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.role.controller.service;
+import android.annotation.UserIdInt;
import android.app.role.RoleControllerService;
import android.app.role.RoleManager;
import android.content.Context;
@@ -34,6 +35,7 @@ import com.android.role.controller.model.Roles;
import com.android.role.controller.util.CollectionUtils;
import com.android.role.controller.util.LegacyRoleFallbackEnabledUtils;
import com.android.role.controller.util.PackageUtils;
+import com.android.role.controller.util.RoleFlags;
import com.android.role.controller.util.UserUtils;
import java.util.ArrayList;
@@ -49,11 +51,21 @@ public class RoleControllerServiceImpl extends RoleControllerService {
private static final boolean DEBUG = false;
+ public static volatile SetActiveUserForRoleMethod sSetActiveUserForRoleMethod;
private UserHandle mUser;
private Context mContext;
private RoleManager mUserRoleManager;
+ /** Method for setting active user from role controller */
+ public interface SetActiveUserForRoleMethod {
+ /**
+ * Sets user as active for the given role.
+ * @see RoleManager#setActiveUserForRole(String, UserHandle, int)
+ */
+ void setActiveUserForRole(@NonNull String roleName, @UserIdInt int userId, int flags);
+ }
+
public RoleControllerServiceImpl() {}
public RoleControllerServiceImpl(@NonNull UserHandle user, @NonNull Context context) {
@@ -121,6 +133,19 @@ public class RoleControllerServiceImpl extends RoleControllerService {
String roleName = role.getName();
+ if (RoleFlags.isProfileGroupExclusivityAvailable()
+ && role.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ if (mUserRoleManager.getActiveUserForRole(roleName) == null) {
+ UserHandle profileParent = UserUtils.getProfileParentOrSelf(mUser, mContext);
+ if (Objects.equals(mUser, profileParent)) {
+ Log.i(LOG_TAG, "No active user for role: " + roleName + ", setting "
+ + "active user to user: " + mUser.getIdentifier());
+ sSetActiveUserForRoleMethod.setActiveUserForRole(roleName,
+ mUser.getIdentifier(), 0);
+ }
+ }
+ }
+
// For each of the current holders, check if it is still qualified, redo grant if so, or
// remove it otherwise.
List<String> currentPackageNames = mUserRoleManager.getRoleHolders(roleName);
@@ -232,6 +257,11 @@ public class RoleControllerServiceImpl extends RoleControllerService {
boolean added = false;
if (role.isExclusive()) {
+ if (role.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ sSetActiveUserForRoleMethod.setActiveUserForRole(roleName, mUser.getIdentifier(),
+ flags);
+ }
+
List<String> currentPackageNames = mUserRoleManager.getRoleHolders(roleName);
int currentPackageNamesSize = currentPackageNames.size();
for (int i = 0; i < currentPackageNamesSize; i++) {
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java b/PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java
new file mode 100644
index 000000000..2c5a247b6
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.util;
+
+import android.os.Build;
+
+import androidx.annotation.ChecksSdkIntAtLeast;
+
+import java.util.Objects;
+
+/** Util class for getting shared feature flag check logic. */
+public final class RoleFlags {
+ private RoleFlags() { /* cannot be instantiated */ }
+
+ /**
+ * Returns whether profile group exclusive roles are available. Profile exclusive roles are
+ * available on B+
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.BAKLAVA)
+ public static boolean isProfileGroupExclusivityAvailable() {
+ // TODO(b/372743073): change to isAtLeastB once available
+ return isAtLeastB() && com.android.permission.flags.Flags.crossUserRoleEnabled();
+ }
+
+ // TODO(b/372743073): remove once SdkLevel.isAtLeastB available
+ @ChecksSdkIntAtLeast(api = 36 /* BUILD_VERSION_CODES.Baklava */)
+ public static boolean isAtLeastB() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA
+ || Objects.equals(Build.VERSION.CODENAME, "Baklava");
+ }
+}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java b/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java
index 1b6926ef8..f3cb7926a 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java
@@ -24,6 +24,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.modules.utils.build.SdkLevel;
@@ -111,4 +112,24 @@ public final class UserUtils {
return context.createContextAsUser(user, 0);
}
}
+
+ /**
+ * Returns the parent of a given user, or user if it has no parent (e.g. it is the primary
+ * user)
+ */
+ @NonNull
+ public static UserHandle getProfileParentOrSelf(@NonNull UserHandle user,
+ @NonNull Context context) {
+ UserHandle profileParent = getProfileParent(user, context);
+ // If profile parent user is null, then original user is the parent
+ return profileParent != null ? profileParent : user;
+ }
+
+ /** Returns the parent of a given user. */
+ @Nullable
+ private static UserHandle getProfileParent(UserHandle user, @NonNull Context context) {
+ Context userContext = getUserContext(context, user);
+ UserManager userManager = userContext.getSystemService(UserManager.class);
+ return userManager.getProfileParent(user);
+ }
}