diff options
28 files changed, 649 insertions, 115 deletions
diff --git a/PermissionController/jarjar-rules.txt b/PermissionController/jarjar-rules.txt index 7bfda1ab1..05fe2a148 100644 --- a/PermissionController/jarjar-rules.txt +++ b/PermissionController/jarjar-rules.txt @@ -22,4 +22,8 @@ rule android.os.*FeatureFlags* com.android.permissioncontroller.jarjar.@0 rule android.os.FeatureFlags* com.android.permissioncontroller.jarjar.@0 rule android.os.FeatureFlags com.android.permissioncontroller.jarjar.@0 rule android.os.Flags com.android.permissioncontroller.jarjar.@0 +rule com.android.permission.flags.*FeatureFlags* com.android.permissioncontroller.jarjar.@0 +rule com.android.permission.flags.FeatureFlags* com.android.permissioncontroller.jarjar.@0 +rule com.android.permission.flags.FeatureFlags com.android.permissioncontroller.jarjar.@0 +rule com.android.permission.flags.Flags com.android.permissioncontroller.jarjar.@0 # LINT.ThenChange(PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java:applyJarjarTransform) diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml index 106f4020f..2989d8f96 100644 --- a/PermissionController/res/xml/roles.xml +++ b/PermissionController/res/xml/roles.xml @@ -1863,4 +1863,16 @@ </permissions> </role> + <!--- + ~ A role for testing cross-user roles (exclusivity="profileGroup"). This should never be used + ~ to gate any actual functionality. + --> + <role + name="android.app.role.RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY" + exclusive="true" + exclusivity="profileGroup" + featureFlag="com.android.permission.flags.Flags.crossUserRoleEnabled" + showNone="true" + visible="false"/> + </roles> diff --git a/PermissionController/src/com/android/permissioncontroller/permission/compat/AppOpsManagerCompat.java b/PermissionController/src/com/android/permissioncontroller/permission/compat/AppOpsManagerCompat.java index 912c894f2..05d69e998 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/compat/AppOpsManagerCompat.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/compat/AppOpsManagerCompat.java @@ -16,6 +16,7 @@ package com.android.permissioncontroller.permission.compat; +import android.annotation.SuppressLint; import android.app.AppOpsManager; import android.permission.flags.Flags; @@ -34,7 +35,9 @@ public class AppOpsManagerCompat { * * @return the raw mode of the op */ + // TODO: b/379749734 @SuppressWarnings("deprecation") + @SuppressLint("NewApi") public static int checkOpRawNoThrow(@NonNull AppOpsManager appOpsManager, @NonNull String op, int uid, @NonNull String packageName) { if (Flags.checkOpOverloadApiEnabled()) { diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java index a4f629d80..c1479caf2 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java @@ -46,7 +46,6 @@ import android.app.KeyguardManager; import android.app.ecm.EnhancedConfirmationManager; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.res.Resources; @@ -195,7 +194,7 @@ public class GrantPermissionsActivity extends SettingsActivity /** A list of permissions requested on an app's behalf by the system. Usually Implicitly * requested, although this isn't necessarily always the case. */ - private List<String> mSystemRequestedPermissions = new ArrayList<>(); + private final List<String> mSystemRequestedPermissions = new ArrayList<>(); /** A copy of the list of permissions originally requested in the intent to this activity */ private String[] mOriginalRequestedPermissions = new String[0]; @@ -209,7 +208,7 @@ public class GrantPermissionsActivity extends SettingsActivity * A list of other GrantPermissionActivities for the same package which passed their list of * permissions to this one. They need to be informed when this activity finishes. */ - private List<GrantPermissionsActivity> mFollowerActivities = new ArrayList<>(); + private final List<GrantPermissionsActivity> mFollowerActivities = new ArrayList<>(); /** Whether this activity has asked another GrantPermissionsActivity to show on its behalf */ private boolean mDelegated; @@ -235,7 +234,7 @@ public class GrantPermissionsActivity extends SettingsActivity private PackageManager mPackageManager; - private ActivityResultLauncher<Intent> mShowWarningDialog = + private final ActivityResultLauncher<Intent> mShowWarningDialog = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { @@ -284,7 +283,7 @@ public class GrantPermissionsActivity extends SettingsActivity return; } try { - PackageInfo packageInfo = mPackageManager.getPackageInfo(mTargetPackage, 0); + mPackageManager.getPackageInfo(mTargetPackage, 0); } catch (PackageManager.NameNotFoundException e) { Log.e(LOG_TAG, "Unable to get package info for the calling package.", e); finishAfterTransition(); @@ -314,20 +313,23 @@ public class GrantPermissionsActivity extends SettingsActivity .getPackageManager(); } - // When the dialog is streamed to a remote device, verify requested permissions are all - // device aware and target device is the same as the remote device. Otherwise show a - // warning dialog. + // When the permission grant dialog is streamed to a virtual device, and when requested + // permissions include both device-aware permissions and non-device aware permissions, + // device-aware permissions will use virtual device id and non-device aware permissions + // will use default device id for granting. If flag is not enabled, we would show a + // warning dialog for this use case. if (getDeviceId() != ContextCompat.DEVICE_ID_DEFAULT) { boolean showWarningDialog = mTargetDeviceId != getDeviceId(); for (String permission : mRequestedPermissions) { - if (!MultiDeviceUtils.isPermissionDeviceAware( - getApplicationContext(), mTargetDeviceId, permission)) { + if (!MultiDeviceUtils.isPermissionDeviceAware(getApplicationContext(), + mTargetDeviceId, permission)) { showWarningDialog = true; + break; } } - if (showWarningDialog) { + if (showWarningDialog && !Flags.allowHostPermissionDialogsOnVirtualDevices()) { mShowWarningDialog.launch( new Intent(this, PermissionDialogStreamingBlockedActivity.class)); return; @@ -1115,9 +1117,17 @@ public class GrantPermissionsActivity extends SettingsActivity if ((mDelegated || (mViewModel != null && mViewModel.shouldReturnPermissionState())) && mTargetPackage != null) { + PackageManager defaultDevicePackageManager = SdkLevel.isAtLeastV() + && mTargetDeviceId != ContextCompat.DEVICE_ID_DEFAULT + ? createDeviceContext(ContextCompat.DEVICE_ID_DEFAULT).getPackageManager() + : mPackageManager; + PackageManager targetDevicePackageManager = mPackageManager; for (int i = 0; i < resultPermissions.length; i++) { - grantResults[i] = - mPackageManager.checkPermission(resultPermissions[i], mTargetPackage); + String permission = resultPermissions[i]; + PackageManager pm = MultiDeviceUtils.isPermissionDeviceAware( + getApplicationContext(), mTargetDeviceId, permission) + ? targetDevicePackageManager : defaultDevicePackageManager; + grantResults[i] = pm.checkPermission(resultPermissions[i], mTargetPackage); } } else { grantResults = new int[0]; diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt index 0a01929e6..1e5b96c2e 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt @@ -29,6 +29,7 @@ import android.annotation.SuppressLint import android.app.Activity import android.app.Application import android.app.admin.DevicePolicyManager +import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED @@ -41,6 +42,8 @@ import android.os.Build import android.os.Bundle import android.os.Process import android.permission.PermissionManager +import android.permission.flags.Flags +import android.util.ArrayMap import android.util.Log import androidx.core.util.Consumer import androidx.lifecycle.ViewModel @@ -116,6 +119,7 @@ import com.android.permissioncontroller.permission.utils.SafetyNetLogger import com.android.permissioncontroller.permission.utils.Utils import com.android.permissioncontroller.permission.utils.v31.AdminRestrictedPermissionsUtils import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils +import com.android.permissioncontroller.permission.utils.v35.MultiDeviceUtils.isPermissionDeviceAware /** * ViewModel for the GrantPermissionsActivity. Tracks all permission groups that are affected by the @@ -153,6 +157,21 @@ class GrantPermissionsViewModel( } else { null } + private val permissionGroupToDeviceIdMap: Map<String, Int> = + if (SdkLevel.isAtLeastV() && Flags.allowHostPermissionDialogsOnVirtualDevices()) { + requestedPermissions + .filter({ PermissionMapping.getGroupOfPlatformPermission(it) != null }) + .associateBy({ PermissionMapping.getGroupOfPlatformPermission(it)!! }, { + if (isPermissionDeviceAware( + app.applicationContext, + deviceId, + it + ) + ) deviceId else Context.DEVICE_ID_DEFAULT + }) + } else { + ArrayMap() + } private val dpm = app.getSystemService(DevicePolicyManager::class.java)!! private val permissionPolicy = dpm.getPermissionPolicy(null) private val groupStates = mutableMapOf<String, GroupState>() @@ -314,7 +333,8 @@ class GrantPermissionsViewModel( } val getLiveDataFun = { groupName: String -> - LightAppPermGroupLiveData[packageName, groupName, user, deviceId] + LightAppPermGroupLiveData[packageName, groupName, user, + permissionGroupToDeviceIdMap.get(groupName) ?: deviceId] } setSourcesToDifference(requestedGroups.keys, appPermGroupLiveDatas, getLiveDataFun) } @@ -398,7 +418,8 @@ class GrantPermissionsViewModel( safetyLabel, groupState.group.permGroupName ), - deviceId + permissionGroupToDeviceIdMap.get(groupState.group.permGroupName) + ?: deviceId ) ) } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt index 50a19e571..1498b91b6 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt @@ -22,7 +22,6 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.stringResource -import com.android.permission.flags.Flags import com.android.permissioncontroller.R import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALWAYS_BUTTON import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON @@ -43,6 +42,7 @@ import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipTo import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControl import com.android.permissioncontroller.permission.ui.wear.model.WearGrantPermissionsViewModel +import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5 import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL3 @@ -58,9 +58,8 @@ fun WearGrantPermissionsScreen( val locationVisibilities = viewModel.locationVisibilitiesLiveData.observeAsState(emptyList()) val preciseLocationChecked = viewModel.preciseLocationCheckedLiveData.observeAsState(false) val buttonVisibilities = viewModel.buttonVisibilitiesLiveData.observeAsState(emptyList()) - val useMaterial3Controls = Flags.wearComposeMaterial3() val materialUIVersion = - if (useMaterial3Controls) { + if (ResourceHelper.material3Enabled) { MATERIAL3 } else { MATERIAL2_5 diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt index 37b526105..c7ed0958c 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt @@ -16,6 +16,7 @@ package com.android.permissioncontroller.permission.ui.wear.theme import android.content.Context +import android.os.SystemProperties import androidx.annotation.ColorRes import androidx.annotation.DimenRes import androidx.annotation.DoNotInline @@ -23,6 +24,14 @@ import androidx.annotation.StringRes import androidx.compose.ui.graphics.Color internal object ResourceHelper { + + private const val MATERIAL3_ENABLED_SYSPROP = "persist.cw_build.bluechip.enabled" + + val material3Enabled: Boolean + get() { + return SystemProperties.getBoolean(MATERIAL3_ENABLED_SYSPROP, false) + } + @DoNotInline fun getColor(context: Context, @ColorRes id: Int): Color? { return try { diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt index adf179be6..8823bee07 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt @@ -30,7 +30,6 @@ import androidx.wear.compose.material.Colors import androidx.wear.compose.material.MaterialTheme import androidx.wear.compose.material.Typography import androidx.wear.compose.material3.MaterialTheme as Material3Theme -import com.android.permission.flags.Flags import com.android.permissioncontroller.R import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5 import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL3 @@ -55,7 +54,8 @@ fun WearPermissionTheme( WearPermissionLegacyTheme(content) } else { // Whether we are ready to use material3 for any screen. - val useBridgedTheme = Flags.wearComposeMaterial3() + val useBridgedTheme = ResourceHelper.material3Enabled + // Material3 UI controls are still being used in the screen that the theme is applied if (version == MATERIAL3) { val material3Theme = WearOverlayableMaterial3Theme(LocalContext.current) diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt index ee8fe2545..aa9b31e0d 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt @@ -30,7 +30,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.android.permission.flags.Flags import com.android.permissioncontroller.R import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl @@ -40,6 +39,7 @@ import com.android.permissioncontroller.permission.ui.wear.elements.material3.We import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionListFooter import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControl import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlStyle +import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5 import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL3 @@ -78,10 +78,8 @@ fun WearRequestRoleScreen( helper.initializeSelectedPackageName() } } - - val useMaterial3Controls = Flags.wearComposeMaterial3() val materialUIVersion = - if (useMaterial3Controls) { + if (ResourceHelper.material3Enabled) { MATERIAL3 } else { MATERIAL2_5 diff --git a/PermissionController/tests/inprocess/Android.bp b/PermissionController/tests/inprocess/Android.bp index 60b35e80f..4cd9e0e6f 100644 --- a/PermissionController/tests/inprocess/Android.bp +++ b/PermissionController/tests/inprocess/Android.bp @@ -50,6 +50,9 @@ android_test { "compatibility-device-util-axt", "kotlin-test", "permission-test-util-lib", + // This may result in two flag libs being included. This should only be used for Flag + //string referencing for test annotations. + "com.android.permission.flags-aconfig-java-export", ], data: [ diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt index e15887576..3222eeda8 100644 --- a/framework-s/api/system-current.txt +++ b/framework-s/api/system-current.txt @@ -37,6 +37,7 @@ package android.app.role { method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void addRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean addRoleHolderFromController(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void clearRoleHoldersAsUser(@NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); + method @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") @Nullable @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, android.Manifest.permission.MANAGE_ROLE_HOLDERS, android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS}, conditional=true) public android.os.UserHandle getActiveUserForRole(@NonNull String); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS) public String getDefaultApplication(@NonNull String); method @Deprecated @NonNull @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public java.util.List<java.lang.String> getHeldRolesFromController(@NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHolders(@NonNull String); @@ -48,6 +49,7 @@ package android.app.role { method @RequiresPermission(android.Manifest.permission.OBSERVE_ROLE_HOLDERS) public void removeOnRoleHoldersChangedListenerAsUser(@NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void removeRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String); + method @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, android.Manifest.permission.MANAGE_ROLE_HOLDERS, android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS}, conditional=true) public void setActiveUserForRole(@NonNull String, @NonNull android.os.UserHandle, int); method @RequiresPermission(android.Manifest.permission.BYPASS_ROLE_QUALIFICATION) public void setBypassingRoleQualification(boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS) public void setDefaultApplication(@NonNull String, @Nullable String, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @FlaggedApi("android.permission.flags.system_server_role_controller_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void setRoleFallbackEnabled(@NonNull String, boolean); @@ -55,6 +57,7 @@ package android.app.role { field public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1; // 0x1 field public static final String ROLE_DEVICE_POLICY_MANAGEMENT = "android.app.role.DEVICE_POLICY_MANAGEMENT"; field public static final String ROLE_FINANCED_DEVICE_KIOSK = "android.app.role.FINANCED_DEVICE_KIOSK"; + field @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") public static final String ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY = "android.app.role.RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY"; field public static final String ROLE_SYSTEM_ACTIVITY_RECOGNIZER = "android.app.role.SYSTEM_ACTIVITY_RECOGNIZER"; field public static final String ROLE_SYSTEM_CALL_STREAMING = "android.app.role.SYSTEM_CALL_STREAMING"; field public static final String ROLE_SYSTEM_SUPERVISION = "android.app.role.SYSTEM_SUPERVISION"; diff --git a/framework-s/java/android/app/role/IRoleManager.aidl b/framework-s/java/android/app/role/IRoleManager.aidl index 522967630..cd0079e08 100644 --- a/framework-s/java/android/app/role/IRoleManager.aidl +++ b/framework-s/java/android/app/role/IRoleManager.aidl @@ -45,6 +45,10 @@ interface IRoleManager { void setDefaultApplicationAsUser(in String roleName, in String packageName, int flags, int userId, in RemoteCallback callback); + int getActiveUserForRoleAsUser(in String roleName, int userId); + + void setActiveUserForRoleAsUser(in String roleName, int activeUserId, int flags, int userId); + void addOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener, int userId); void removeOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener, diff --git a/framework-s/java/android/app/role/RoleManager.java b/framework-s/java/android/app/role/RoleManager.java index 4b8c9b388..6f62fdd76 100644 --- a/framework-s/java/android/app/role/RoleManager.java +++ b/framework-s/java/android/app/role/RoleManager.java @@ -40,6 +40,7 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.permission.flags.Flags; +import android.permission.internal.compat.UserHandleCompat; import android.util.ArrayMap; import android.util.SparseArray; @@ -211,6 +212,16 @@ public final class RoleManager { "android.app.role.SYSTEM_CALL_STREAMING"; /** + * The name of the role used for testing cross-user roles. + * + * @hide + */ + @FlaggedApi(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @SystemApi + public static final String ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY = + "android.app.role.RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY"; + + /** * @hide */ @IntDef(flag = true, value = { MANAGE_HOLDERS_FLAG_DONT_KILL_APP }) @@ -574,6 +585,87 @@ public final class RoleManager { } } + /** + * Get the {@link UserHandle} of the user who that is the active user for the specified role. + * <p> + * Only profile-group exclusive roles can be used with this method, and they will + * have one active user within a profile group. + * <p> + * <strong>Note:</strong> Using this API requires holding + * {@code android.permission.INTERACT_ACROSS_USERS_FULL} and one of + * {@code android.permission.MANAGE_ROLE_HOLDERS} or + * {@code android.permission.MANAGE_DEFAULT_APPLICATIONS}. + * + * @param roleName the name of the role to get the active user for + * + * @return a {@link UserHandle} of the active user for the specified role + * + * @see #setActiveUserForRole(String, UserHandle, int) + * + * @hide + */ + @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL, + Manifest.permission.MANAGE_ROLE_HOLDERS, + Manifest.permission.MANAGE_DEFAULT_APPLICATIONS}, + conditional = true) + @RequiresApi(Build.VERSION_CODES.BAKLAVA) + @SystemApi + @UserHandleAware + @FlaggedApi(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @Nullable + public UserHandle getActiveUserForRole(@NonNull String roleName) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + try { + int userId = mService.getActiveUserForRoleAsUser(roleName, + mContext.getUser().getIdentifier()); + return userId == UserHandleCompat.USER_NULL ? null : UserHandle.of(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set a specific user as active user for a role. + * <p> + * Only profile-group exclusive roles can be used with this method, and they will have + * one active user within a profile group. + * <p> + * <strong>Note:</strong> Using this API requires holding + * {@code android.permission.INTERACT_ACROSS_USERS_FULL} and one of + * {@code android.permission.MANAGE_ROLE_HOLDERS} or + * {@code android.permission.MANAGE_DEFAULT_APPLICATIONS}. + * + * @param roleName the name of the role to set the active user for + * @param user the user to set as active user for specified role + * @param flags optional behavior flags + * + * @see #getActiveUserForRole(String) + * + * @hide + */ + @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL, + Manifest.permission.MANAGE_ROLE_HOLDERS, + Manifest.permission.MANAGE_DEFAULT_APPLICATIONS}, + conditional = true) + @RequiresApi(Build.VERSION_CODES.BAKLAVA) + @SystemApi + @UserHandleAware + @FlaggedApi(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + // The user handle parameter is a value to be set by this method, while the context user of the + // operation is indeed read from the context + @SuppressLint("UserHandle") + public void setActiveUserForRole( + @NonNull String roleName, @NonNull UserHandle user, @ManageHoldersFlags int flags) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Objects.requireNonNull(user, "user cannot be null"); + try { + mService.setActiveUserForRoleAsUser(roleName, user.getIdentifier(), flags, + mContext.getUser().getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @NonNull private static RemoteCallback createRemoteCallback(@NonNull Executor executor, @NonNull Consumer<Boolean> callback) { diff --git a/service/java/com/android/ecm/EnhancedConfirmationService.java b/service/java/com/android/ecm/EnhancedConfirmationService.java index 708884e85..73f66609e 100644 --- a/service/java/com/android/ecm/EnhancedConfirmationService.java +++ b/service/java/com/android/ecm/EnhancedConfirmationService.java @@ -228,7 +228,8 @@ public class EnhancedConfirmationService extends SystemService { } private void enforcePermissions(@NonNull String methodName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, methodName, mContext); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, methodName, mContext); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES, methodName); } diff --git a/service/java/com/android/permission/util/UserUtils.java b/service/java/com/android/permission/util/UserUtils.java index 986b5af5b..c69afb199 100644 --- a/service/java/com/android/permission/util/UserUtils.java +++ b/service/java/com/android/permission/util/UserUtils.java @@ -17,9 +17,10 @@ package com.android.permission.util; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.admin.DevicePolicyManager; import android.content.Context; -import android.content.pm.PackageManager; import android.os.Binder; import android.os.Process; import android.os.UserHandle; @@ -30,8 +31,8 @@ import com.android.internal.util.Preconditions; import com.android.modules.utils.build.SdkLevel; import com.android.permission.flags.Flags; +import java.util.ArrayList; import java.util.List; -import java.util.Objects; /** Utility class to deal with Android users. */ public final class UserUtils { @@ -42,11 +43,12 @@ public final class UserUtils { public static void enforceCrossUserPermission( @UserIdInt int userId, boolean allowAll, + boolean enforceForProfileGroup, @NonNull String message, @NonNull Context context) { final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandleCompat.getUserId(callingUid); - if (userId == callingUserId) { + if (userId == callingUserId && !enforceForProfileGroup) { return; } Preconditions.checkArgument( @@ -55,13 +57,40 @@ public final class UserUtils { "Invalid user " + userId); context.enforceCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); - if (callingUid != Process.SHELL_UID || userId < UserHandleCompat.USER_SYSTEM) { + if (callingUid != Process.SHELL_UID || userId == UserHandleCompat.USER_ALL) { return; } + + if (enforceForProfileGroup) { + DevicePolicyManager devicePolicyManager = + context.getSystemService(DevicePolicyManager.class); + if (!devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) { + // For profileGroup exclusive roles users on BYOD are free to choose personal o + // work profile app regardless of DISALLOW_DEBUGGING_FEATURES + return; + } + + Context userContext = UserUtils.getUserContext(userId, context); + List<UserHandle> profiles = getUserProfiles(userContext, true); + final int profilesSize = profiles.size(); + for (int i = 0; i < profilesSize; i++) { + int profileId = profiles.get(i).getIdentifier(); + if (profileId == callingUserId) { + continue; + } + enforceShellRestriction(profileId, context); + } + } else { + enforceShellRestriction(userId, context); + } + } + + private static void enforceShellRestriction(int userId, @NonNull Context context) { UserManager userManager = context.getSystemService(UserManager.class); if (userManager.hasUserRestrictionForUser( UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.of(userId))) { - throw new SecurityException("Shell does not have permission to access user " + userId); + throw new SecurityException( + "Shell does not have permission to access user " + userId); } } @@ -86,18 +115,54 @@ public final class UserUtils { /** Returns all the enabled user profiles on the device. */ @NonNull public static List<UserHandle> getUserProfiles(@NonNull Context context) { + return getUserProfiles(context, false); + } + + /** + * Returns all the enabled user profiles on the device + * + * @param context the {@link Context} + * @param excludePrivate {@code true} to exclude private profiles from returned list of users + */ + @NonNull + public static List<UserHandle> getUserProfiles(@NonNull Context context, + boolean excludePrivate) { UserManager userManager = context.getSystemService(UserManager.class); // This call requires the QUERY_USERS permission. final long identity = Binder.clearCallingIdentity(); try { - return userManager.getUserProfiles(); + List<UserHandle> profiles = userManager.getUserProfiles(); + if (!excludePrivate) { + return profiles; + } + List<UserHandle> filteredProfiles = new ArrayList<>(); + final int profilesSize = profiles.size(); + for (int i = 0; i < profilesSize; i++) { + UserHandle user = profiles.get(i); + if (!isPrivateProfile(user.getIdentifier(), context)) { + filteredProfiles.add(user); + } + } + return filteredProfiles; } finally { Binder.restoreCallingIdentity(identity); } } + /** + * Returns the parent of a given user, or userId if it has no parent (e.g. it is the primary + * profile) + */ + @UserIdInt + public static int getProfileParentIdOrSelf(@UserIdInt int userId, @NonNull Context context) { + UserHandle profileParent = getProfileParent(userId, context); + // If profile parent userhandle is null, then original user id is the parent + return profileParent != null ? profileParent.getIdentifier() : userId; + } + /** Returns the parent of a given user. */ - public static UserHandle getProfileParent(@UserIdInt int userId, @NonNull Context context) { + @Nullable + private static UserHandle getProfileParent(@UserIdInt int userId, @NonNull Context context) { Context userContext = getUserContext(userId, context); UserManager userManager = userContext.getSystemService(UserManager.class); // This call requires the INTERACT_ACROSS_USERS permission. diff --git a/service/java/com/android/role/RoleService.java b/service/java/com/android/role/RoleService.java index c4316ff71..1145f273d 100644 --- a/service/java/com/android/role/RoleService.java +++ b/service/java/com/android/role/RoleService.java @@ -18,6 +18,7 @@ package com.android.role; import android.Manifest; import android.annotation.AnyThread; +import android.annotation.ChecksSdkIntAtLeast; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; @@ -45,8 +46,8 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; -import android.permission.internal.compat.UserHandleCompat; import android.permission.flags.Flags; +import android.permission.internal.compat.UserHandleCompat; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; @@ -69,6 +70,8 @@ import com.android.permission.util.ForegroundThread; import com.android.permission.util.PackageUtils; import com.android.permission.util.ThrottledRunnable; import com.android.permission.util.UserUtils; +import com.android.role.controller.model.Role; +import com.android.role.controller.model.Roles; import com.android.server.LocalManagerRegistry; import com.android.server.SystemService; import com.android.server.role.RoleServicePlatformHelper; @@ -77,6 +80,7 @@ import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -466,8 +470,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public boolean isRoleAvailableAsUser(@NonNull String roleName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "isRoleAvailableAsUser", - getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "isRoleAvailableAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return false; @@ -483,7 +487,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @UserIdInt int userId) { mAppOpsManager.checkPackage(getCallingUid(), packageName); - UserUtils.enforceCrossUserPermission(userId, false, "isRoleHeldAsUser", getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "isRoleHeldAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return false; @@ -502,8 +507,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @NonNull @Override public List<String> getRoleHoldersAsUser(@NonNull String roleName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "getRoleHoldersAsUser", - getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "getRoleHoldersAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return Collections.emptyList(); @@ -525,8 +530,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName, @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId, @NonNull RemoteCallback callback) { - UserUtils.enforceCrossUserPermission(userId, false, "addRoleHolderAsUser", - getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "addRoleHolderAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return; @@ -546,8 +551,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName, @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId, @NonNull RemoteCallback callback) { - UserUtils.enforceCrossUserPermission(userId, false, "removeRoleHolderAsUser", - getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "removeRoleHolderAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return; @@ -568,8 +573,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback public void clearRoleHoldersAsUser(@NonNull String roleName, @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId, @NonNull RemoteCallback callback) { - UserUtils.enforceCrossUserPermission(userId, false, "clearRoleHoldersAsUser", - getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "clearRoleHoldersAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return; @@ -587,7 +592,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override @Nullable public String getDefaultApplicationAsUser(@NonNull String roleName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "getDefaultApplicationAsUser", + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "getDefaultApplicationAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); @@ -612,7 +618,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback public void setDefaultApplicationAsUser(@NonNull String roleName, @Nullable String packageName, @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId, @NonNull RemoteCallback callback) { - UserUtils.enforceCrossUserPermission(userId, false, "setDefaultApplicationAsUser", + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "setDefaultApplicationAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); @@ -635,10 +642,101 @@ public class RoleService extends SystemService implements RoleUserState.Callback } @Override + public int getActiveUserForRoleAsUser(@NonNull String roleName, @UserIdInt int userId) { + Preconditions.checkState(isProfileGroupExclusivityAvailable(), + "getActiveUserForRoleAsUser not available"); + enforceProfileGroupExclusiveRole(roleName); + + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ true, "getActiveUserForRole", getContext()); + if (!UserUtils.isUserExistent(userId, getContext())) { + Log.e(LOG_TAG, "user " + userId + " does not exist"); + return UserHandleCompat.USER_NULL; + } + + enforceCallingOrSelfAnyPermissions(new String[] { + Manifest.permission.MANAGE_DEFAULT_APPLICATIONS, + Manifest.permission.MANAGE_ROLE_HOLDERS + }, "getActiveUserForRole"); + + int profileParentId = UserUtils.getProfileParentIdOrSelf(userId, getContext()); + RoleUserState userState = getOrCreateUserState(profileParentId); + return userState.getActiveUserForRole(roleName); + } + + @Override + public void setActiveUserForRoleAsUser(@NonNull String roleName, + @UserIdInt int activeUserId, @RoleManager.ManageHoldersFlags int flags, + @UserIdInt int userId) { + Preconditions.checkState(isProfileGroupExclusivityAvailable(), + "setActiveUserForRoleAsUser not available"); + enforceProfileGroupExclusiveRole(roleName); + + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ true, "setActiveUserForRole", getContext()); + if (!UserUtils.isUserExistent(userId, getContext())) { + Log.e(LOG_TAG, "user " + userId + " does not exist"); + return; + } + if (!UserUtils.isUserExistent(activeUserId, getContext())) { + Log.e(LOG_TAG, "user " + activeUserId + " does not exist"); + return; + } + if (UserUtils.isPrivateProfile(activeUserId, getContext())) { + Log.e(LOG_TAG, "Cannot set private profile " + activeUserId + " as active user" + + " for role"); + return; + } + Context userContext = UserUtils.getUserContext(userId, getContext()); + List<UserHandle> profiles = UserUtils.getUserProfiles(userContext, true); + if (!profiles.contains(UserHandle.of(activeUserId))) { + Log.e(LOG_TAG, "User " + activeUserId + " is not in the same profile-group as " + + userId); + return; + } + + enforceCallingOrSelfAnyPermissions(new String[] { + Manifest.permission.MANAGE_DEFAULT_APPLICATIONS, + Manifest.permission.MANAGE_ROLE_HOLDERS + }, "setDefaultApplicationAsUser"); + + int profileParentId = UserUtils.getProfileParentIdOrSelf(userId, getContext()); + RoleUserState userState = getOrCreateUserState(profileParentId); + + if (!userState.setActiveUserForRole(roleName, activeUserId)) { + Log.i(LOG_TAG, "User " + activeUserId + " is already the active user for role"); + return; + } + + final int profilesSize = profiles.size(); + for (int i = 0; i < profilesSize; i++) { + final AndroidFuture<Void> future = new AndroidFuture<>(); + final RemoteCallback callback = new RemoteCallback(result -> { + boolean successful = result != null; + if (successful) { + future.complete(null); + } else { + future.completeExceptionally(new RuntimeException()); + } + }); + int profilesUserId = profiles.get(i).getIdentifier(); + getOrCreateController(profilesUserId) + .onClearRoleHolders(roleName, flags, callback); + try { + future.get(5, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Log.e(LOG_TAG, "Exception while clearing role holders for non-active" + + "user: " + profilesUserId, e); + } + } + } + + @Override public void addOnRoleHoldersChangedListenerAsUser( @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, true, - "addOnRoleHoldersChangedListenerAsUser", getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ true, + /* enforceForProfileGroup= */ false, "addOnRoleHoldersChangedListenerAsUser", + getContext()); if (userId != UserHandleCompat.USER_ALL && !UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); @@ -658,7 +756,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public void removeOnRoleHoldersChangedListenerAsUser( @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, true, + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ true, + /* enforceForProfileGroup= */ false, "removeOnRoleHoldersChangedListenerAsUser", getContext()); if (userId != UserHandleCompat.USER_ALL && !UserUtils.isUserExistent(userId, getContext())) { @@ -711,7 +810,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public boolean isRoleFallbackEnabledAsUser(@NonNull String roleName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "isRoleFallbackEnabledAsUser", + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "isRoleFallbackEnabledAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); @@ -729,7 +829,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public void setRoleFallbackEnabledAsUser(@NonNull String roleName, boolean fallbackEnabled, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "setRoleFallbackEnabledAsUser", + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "setRoleFallbackEnabledAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); @@ -747,7 +848,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public void setRoleNamesFromControllerAsUser(@NonNull List<String> roleNames, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "setRoleNamesFromControllerAsUser", + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "setRoleNamesFromControllerAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); @@ -766,8 +868,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public boolean addRoleHolderFromControllerAsUser(@NonNull String roleName, @NonNull String packageName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, - "addRoleHolderFromControllerAsUser", getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "addRoleHolderFromControllerAsUser", + getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return false; @@ -786,8 +889,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public boolean removeRoleHolderFromControllerAsUser(@NonNull String roleName, @NonNull String packageName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, - "removeRoleHolderFromControllerAsUser", getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "removeRoleHolderFromControllerAsUser", + getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return false; @@ -806,8 +910,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public List<String> getHeldRolesFromControllerAsUser(@NonNull String packageName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, - "getHeldRolesFromControllerAsUser", getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "getHeldRolesFromControllerAsUser", + getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return Collections.emptyList(); @@ -916,7 +1021,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public String getSmsRoleHolder(int userId) { final Context context = getContext(); - UserUtils.enforceCrossUserPermission(userId, false, "getSmsRoleHolder", context); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "getSmsRoleHolder", context); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return null; @@ -940,7 +1046,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public String getEmergencyRoleHolder(int userId) { final Context context = getContext(); - UserUtils.enforceCrossUserPermission(userId, false, "getEmergencyRoleHolder", context); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "getEmergencyRoleHolder", context); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return null; @@ -966,8 +1073,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public boolean isRoleVisibleAsUser(@NonNull String roleName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "isRoleVisibleAsUser", - getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "isRoleVisibleAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return false; @@ -984,8 +1091,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public boolean isApplicationVisibleForRoleAsUser(@NonNull String roleName, @NonNull String packageName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, - "isApplicationVisibleForRoleAsUser", getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "isApplicationVisibleForRoleAsUser", + getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return false; @@ -1042,6 +1150,36 @@ public class RoleService extends SystemService implements RoleUserState.Callback return true; } } + + private void enforceCallingOrSelfAnyPermissions(@NonNull String[] permissions, + @NonNull String message) { + for (String permission : permissions) { + if (getContext().checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED) { + return; + } + } + + throw new SecurityException(message + ": Neither user " + Binder.getCallingUid() + + " nor current process has at least one of" + Arrays.toString(permissions) + + "."); + } + + private void enforceProfileGroupExclusiveRole(@NonNull String roleName) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Role role = Roles.get(getContext()).get(roleName); + Objects.requireNonNull(role, "Unknown role: " + roleName); + Preconditions.checkArgument( + role.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP, + roleName + " is not a profile-group exclusive role"); + } + + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.VANILLA_ICE_CREAM) + private boolean isProfileGroupExclusivityAvailable() { + // TODO(b/372743073): change to isAtLeastB once available + return SdkLevel.isAtLeastV() + && com.android.permission.flags.Flags.crossUserRoleEnabled(); + } } private class Local implements RoleManagerLocal { diff --git a/service/java/com/android/role/RoleShellCommand.java b/service/java/com/android/role/RoleShellCommand.java index 41f0702a2..538e62f47 100644 --- a/service/java/com/android/role/RoleShellCommand.java +++ b/service/java/com/android/role/RoleShellCommand.java @@ -87,6 +87,10 @@ class RoleShellCommand extends BasicShellCommandHandler { return runClearRoleHolders(); case "set-bypassing-role-qualification": return runSetBypassingRoleQualification(); + case "get-active-user-for-role": + return runGetActiveUserForRole(); + case "set-active-user-for-role": + return runSetActiveUserForRole(); default: return handleDefaultCommands(cmd); } @@ -162,6 +166,27 @@ class RoleShellCommand extends BasicShellCommandHandler { return 0; } + private int runGetActiveUserForRole() throws RemoteException { + int userId = getUserIdMaybe(); + String roleName = getNextArgRequired(); + + int activeUserId = mRoleManager.getActiveUserForRoleAsUser(roleName, userId); + if (activeUserId != UserHandleCompat.USER_NULL) { + getOutPrintWriter().println(activeUserId); + } + return 0; + } + + private int runSetActiveUserForRole() throws RemoteException { + int userId = getUserIdMaybe(); + String roleName = getNextArgRequired(); + int activeUserId = Integer.parseInt(getNextArgRequired()); + int flags = getFlagsMaybe(); + + mRoleManager.setActiveUserForRoleAsUser(roleName, activeUserId, flags, userId); + return 0; + } + @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); @@ -174,6 +199,8 @@ class RoleShellCommand extends BasicShellCommandHandler { pw.println(" remove-role-holder [--user USER_ID] ROLE PACKAGE [FLAGS]"); pw.println(" clear-role-holders [--user USER_ID] ROLE [FLAGS]"); pw.println(" set-bypassing-role-qualification true|false"); + pw.println(" get-active-user-for-role [--user USER_ID] ROLE"); + pw.println(" set-active-user-for-role [--user USER_ID] ROLE ACTIVE_USER_ID [FLAGS]"); pw.println(); } } diff --git a/service/java/com/android/role/RoleUserState.java b/service/java/com/android/role/RoleUserState.java index cda7fcfa8..c94b58826 100644 --- a/service/java/com/android/role/RoleUserState.java +++ b/service/java/com/android/role/RoleUserState.java @@ -24,6 +24,7 @@ import android.annotation.WorkerThread; import android.os.Build; import android.os.Handler; import android.os.UserHandle; +import android.permission.internal.compat.UserHandleCompat; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -428,6 +429,42 @@ class RoleUserState { } /** + * Return the active user for the role + * + * @param roleName the name of the role to get the active user for + */ + public int getActiveUserForRole(@NonNull String roleName) { + synchronized (mLock) { + return mActiveUserIds.getOrDefault(roleName, UserHandleCompat.USER_NULL); + } + } + + /** + * Set the active user for the role + * + * @param roleName the name of the role to set the active user for + * @param userId User id to set as active for this role + * @return whether any changes were made + */ + public boolean setActiveUserForRole(@NonNull String roleName, @UserIdInt int userId) { + if (!com.android.permission.flags.Flags.crossUserRoleEnabled()) { + return false; + } + synchronized (mLock) { + Integer currentActiveUserId = mActiveUserIds.get(roleName); + // If we have pre-existing roles that weren't profile group exclusive and don't have an + // active user, ensure we set and write value, and return modified, otherwise other + // users might not have role holder revoked. + if (currentActiveUserId != null && currentActiveUserId == userId) { + return false; + } + mActiveUserIds.put(roleName, userId); + scheduleWriteFileLocked(); + return true; + } + } + + /** * Schedule writing the state to file. */ @GuardedBy("mLock") diff --git a/service/java/com/android/safetycenter/SafetyCenterService.java b/service/java/com/android/safetycenter/SafetyCenterService.java index 250be5f25..6df4184e1 100644 --- a/service/java/com/android/safetycenter/SafetyCenterService.java +++ b/service/java/com/android/safetycenter/SafetyCenterService.java @@ -694,7 +694,8 @@ public final class SafetyCenterService extends SystemService { /** Enforces cross user permission and returns whether the user is valid. */ private boolean enforceCrossUserPermission(String message, @UserIdInt int userId) { UserUtils.enforceCrossUserPermission( - userId, /* allowAll= */ false, message, getContext()); + userId, /* allowAll= */ false, /* enforceForProfileGroup= */ false, message, + getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.w( TAG, diff --git a/service/java/com/android/safetycenter/UserProfileGroup.java b/service/java/com/android/safetycenter/UserProfileGroup.java index d4b051d0f..1f5258437 100644 --- a/service/java/com/android/safetycenter/UserProfileGroup.java +++ b/service/java/com/android/safetycenter/UserProfileGroup.java @@ -21,16 +21,12 @@ import static java.util.Objects.requireNonNull; import android.annotation.IntDef; import android.annotation.UserIdInt; import android.content.Context; -import android.content.pm.PackageManager; import android.os.Binder; -import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.permission.internal.compat.UserHandleCompat; import android.util.Log; -import androidx.annotation.Nullable; - import com.android.permission.util.UserUtils; import java.lang.annotation.Retention; @@ -135,11 +131,7 @@ public final class UserProfileGroup { public static UserProfileGroup fromUser(Context context, @UserIdInt int userId) { Context userContext = UserUtils.getUserContext(userId, context); List<UserHandle> userProfiles = UserUtils.getUserProfiles(userContext); - UserHandle profileParent = UserUtils.getProfileParent(userId, userContext); - int profileParentUserId = userId; - if (profileParent != null) { - profileParentUserId = profileParent.getIdentifier(); - } + int profileParentUserId = UserUtils.getProfileParentIdOrSelf(userId, userContext); int[] managedProfilesUserIds = new int[userProfiles.size()]; int[] managedRunningProfilesUserIds = new int[userProfiles.size()]; diff --git a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt index f0a0e3fc1..687234582 100644 --- a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt +++ b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt @@ -21,6 +21,7 @@ import android.app.Instrumentation import android.companion.virtual.VirtualDeviceManager import android.companion.virtual.VirtualDeviceParams import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM +import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT import android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA import android.content.ComponentName import android.content.Intent @@ -39,7 +40,9 @@ import android.permissionmultidevice.cts.UiAutomatorUtils.click import android.permissionmultidevice.cts.UiAutomatorUtils.findTextForView import android.permissionmultidevice.cts.UiAutomatorUtils.waitFindObject import android.platform.test.annotations.AppModeFull +import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.annotations.RequiresFlagsEnabled +import android.provider.Settings import android.view.Display import android.virtualdevice.cts.common.VirtualDeviceRule import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -47,7 +50,6 @@ import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.By import com.android.compatibility.common.util.SystemUtil -import com.google.common.truth.Truth import com.google.common.truth.Truth.assertThat import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit @@ -146,6 +148,7 @@ class DeviceAwarePermissionGrantTest { Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED, Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED ) + @RequiresFlagsDisabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES) @Test fun onRemoteDevice_requestPermissionForHostDevice_shouldShowWarningDialog() { requestPermissionOnDevice(virtualDisplay.display.displayId, DEVICE_ID_DEFAULT) @@ -156,6 +159,32 @@ class DeviceAwarePermissionGrantTest { @RequiresFlagsEnabled( Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED, + Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED, + Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES + ) + @Test + fun onRemoteDevice_requestPermissionForHostDevice_shouldGrantPermission() { + // Create a virtual device with default policy, so that camera permission request will + // correspond to default device camera access. + virtualDevice = + virtualDeviceRule.createManagedVirtualDevice( + VirtualDeviceParams.Builder() + .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_DEFAULT) + .build() + ) + testGrantPermissionForDevice( + virtualDisplay.display.displayId, + virtualDevice.deviceId, + true, + Settings.Global.getString(defaultDeviceContext.contentResolver, + Settings.Global.DEVICE_NAME), + expectPermissionGrantedOnDefaultDevice = true, + expectPermissionGrantedOnRemoteDevice = false + ) + } + + @RequiresFlagsEnabled( + Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED, Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED ) @Test @@ -199,7 +228,7 @@ class DeviceAwarePermissionGrantTest { TestConstants.PERMISSION_RESULT_KEY_PERMISSIONS ) ) - .isEqualTo(arrayOf(DEVICE_AWARE_PERMISSION)) + .isEqualTo(arrayOf(PERMISSION)) assertThat( grantPermissionResult.getIntArray(TestConstants.PERMISSION_RESULT_KEY_GRANT_RESULTS) ) @@ -237,7 +266,7 @@ class DeviceAwarePermissionGrantTest { private fun assertPermissionMessageContainsDeviceName(displayId: Int, deviceName: String) { waitFindObject(By.displayId(displayId).res(PERMISSION_MESSAGE_ID)) val text = findTextForView(By.displayId(displayId).res(PERMISSION_MESSAGE_ID)) - Truth.assertThat(text).contains(deviceName) + assertThat(text).contains(deviceName) } private fun assertAppHasPermissionForDevice(deviceId: Int, expectPermissionGranted: Boolean) { @@ -245,7 +274,7 @@ class DeviceAwarePermissionGrantTest { defaultDeviceContext .createDeviceContext(deviceId) .packageManager - .checkPermission(DEVICE_AWARE_PERMISSION, APP_PACKAGE_NAME) + .checkPermission(PERMISSION, APP_PACKAGE_NAME) if (expectPermissionGranted) { Assert.assertEquals(PackageManager.PERMISSION_GRANTED, checkPermissionResult) @@ -269,7 +298,7 @@ class DeviceAwarePermissionGrantTest { "com.android.permissioncontroller:id/permission_allow_foreground_only_button" const val DEVICE_ID_DEFAULT = 0 const val PERSISTENT_DEVICE_ID_DEFAULT = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT - const val DEVICE_AWARE_PERMISSION = Manifest.permission.CAMERA + const val PERMISSION = Manifest.permission.CAMERA const val TIMEOUT = 5000L private const val DISPLAY_HEIGHT = 1920 private const val DISPLAY_WIDTH = 1080 diff --git a/tests/cts/permissionpolicy/res/raw/android_manifest.xml b/tests/cts/permissionpolicy/res/raw/android_manifest.xml index b8dad240d..e1608f878 100644 --- a/tests/cts/permissionpolicy/res/raw/android_manifest.xml +++ b/tests/cts/permissionpolicy/res/raw/android_manifest.xml @@ -3968,7 +3968,7 @@ @FlaggedApi("android.security.aapm_api") @SystemApi @hide --> - <permission android:name="android.permission.SET_ADVANCED_PROTECTION_MODE" + <permission android:name="android.permission.MANAGE_ADVANCED_PROTECTION_MODE" android:protectionLevel="signature|privileged" android:featureFlag="android.security.aapm_api"/> @@ -3978,36 +3978,36 @@ android:protectionLevel="normal" android:featureFlag="android.security.aapm_api"/> - <!-- Allows an application to read the state of the ForensicService + <!-- Allows an application to read the state of the IntrusionDetectionService @FlaggedApi(android.security.Flags.FLAG_AFL_API) @SystemApi @hide --> - <permission android:name="android.permission.READ_FORENSIC_STATE" + <permission android:name="android.permission.READ_INTRUSION_DETECTION_STATE" android:featureFlag="android.security.afl_api" android:protectionLevel="signature|privileged" /> - <uses-permission android:name="android.permission.READ_FORENSIC_STATE" + <uses-permission android:name="android.permission.READ_INTRUSION_DETECTION_STATE" android:featureFlag="android.security.afl_api"/> - <!-- Allows an application to change the state of the ForensicService + <!-- Allows an application to change the state of the IntrusionDetectionService @FlaggedApi(android.security.Flags.FLAG_AFL_API) @SystemApi @hide --> - <permission android:name="android.permission.MANAGE_FORENSIC_STATE" + <permission android:name="android.permission.MANAGE_INTRUSION_DETECTION_STATE" android:featureFlag="android.security.afl_api" android:protectionLevel="signature|privileged" /> - <uses-permission android:name="android.permission.MANAGE_FORENSIC_STATE" + <uses-permission android:name="android.permission.MANAGE_INTRUSION_DETECTION_STATE" android:featureFlag="android.security.afl_api"/> - <!-- Must be required by any ForensicEventTransportService to ensure that + <!-- Must be required by any IntrusionDetectionEventTransportService to ensure that only the system can bind to it. @FlaggedApi(android.security.Flags.FLAG_AFL_API) @SystemApi @hide --> - <permission android:name="android.permission.BIND_FORENSIC_EVENT_TRANSPORT_SERVICE" + <permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" android:featureFlag="android.security.afl_api" android:protectionLevel="signature" /> - <uses-permission android:name="android.permission.BIND_FORENSIC_EVENT_TRANSPORT_SERVICE" - android:featureFlag="android.security.afl_api"/> + <uses-permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" + android:featureFlag="android.security.afl_api"/> <!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.--> <permission android:name="android.permission.PROVISION_DEMO_DEVICE" @@ -8501,29 +8501,6 @@ <permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE" android:protectionLevel="signature"/> - <!-- @SystemApi - @FlaggedApi("android.content.pm.verification_service") - Allows app to be the verification agent to verify packages. - <p>Protection level: signature|privileged - @hide - --> - <permission android:name="android.permission.VERIFICATION_AGENT" - android:protectionLevel="signature|privileged" - android:featureFlag="android.content.pm.verification_service"/> - - <!-- @SystemApi - @FlaggedApi("android.content.pm.verification_service") - Must be required by a privileged {@link android.content.pm.verify.pkg.VerifierService} - to ensure that only the system can bind to it. - This permission should not be held by anything other than the system. - <p>Not for use by third-party applications. </p> - <p>Protection level: signature - @hide - --> - <permission android:name="android.permission.BIND_VERIFICATION_AGENT" - android:protectionLevel="internal" - android:featureFlag="android.content.pm.verification_service" /> - <!-- Allows app to enter trade-in-mode. <p>Protection level: signature|privileged @hide diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt index 70832b6ba..2ce48af44 100644 --- a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt +++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt @@ -59,6 +59,7 @@ import android.app.AppOpsManager.permissionToOp import android.content.pm.PackageManager.GET_PERMISSIONS import android.content.pm.PermissionInfo.PROTECTION_DANGEROUS import android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP +import android.health.connect.HealthPermissions import android.os.Build import android.permission.flags.Flags import android.permission.PermissionManager @@ -195,6 +196,18 @@ class RuntimePermissionProperties { expectedPerms.add(RANGING) } - assertThat(expectedPerms).containsExactlyElementsIn(platformRuntimePerms.map { it.name }) + // Separately check health permissions. + if (Flags.replaceBodySensorPermissionEnabled()) { + assertThat(expectedPerms).contains(HealthPermissions.READ_HEART_RATE); + assertThat(expectedPerms).contains(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND); + + // Remove these from the expected list once we've confirmed their + // present. These are not permissions owned by "android" so won't be + // in the list of platform runtime permissions. + expectedPerms.remove(HealthPermissions.READ_HEART_RATE); + expectedPerms.remove(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND); + } + + assertThat(platformRuntimePerms.map { it.name }).containsExactlyElementsIn(expectedPerms) } } diff --git a/tests/cts/role/Android.bp b/tests/cts/role/Android.bp index 5751aaada..9f1e6cff6 100644 --- a/tests/cts/role/Android.bp +++ b/tests/cts/role/Android.bp @@ -30,10 +30,12 @@ android_test { static_libs: [ "android.permission.flags-aconfig-java-export", "androidx.test.rules", + "com.android.permission.flags-aconfig-java-export", "compatibility-device-util-axt", "ctstestrunner-axt", "Harrier", "bedstead-multiuser", + "flag-junit", "platform-test-annotations", "truth", ], diff --git a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java index e3bf054b0..9f89140d7 100644 --- a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java +++ b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java @@ -104,6 +104,8 @@ public class RoleManagerTest { private static final String ROLE_NAME = RoleManager.ROLE_BROWSER; private static final String ROLE_PHONE_NAME = RoleManager.ROLE_DIALER; private static final String ROLE_SMS_NAME = RoleManager.ROLE_SMS; + private static final String PROFILE_GROUP_EXCLUSIVE_ROLE_NAME = + RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY; private static final String ROLE_SHORT_LABEL = "Browser app"; private static final String APP_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestApp.apk"; @@ -1349,6 +1351,51 @@ public class RoleManagerTest { }); } + @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @Test + public void cannotGetActiveUserForRoleWithoutPermission() throws Exception { + assertThrows(SecurityException.class, ()-> + sRoleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME)); + } + + @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @Test + public void cannotGetActiveUserForNonProfileGroupExclusiveRole() throws Exception { + runWithShellPermissionIdentity(() -> + assertThrows(IllegalArgumentException.class, () -> + sRoleManager.getActiveUserForRole( + RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER))); + } + + @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @Test + public void cannotSetActiveUserForRoleWithoutPermission() throws Exception { + assertThrows(SecurityException.class, ()-> + sRoleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME, + Process.myUserHandle(), 0)); + } + + @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @Test + public void cannotSetActiveUserForNonProfileGroupExclusiveRole() throws Exception { + runWithShellPermissionIdentity(() -> + assertThrows(IllegalArgumentException.class, () -> + sRoleManager.setActiveUserForRole( + RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER, Process.myUserHandle(), + 0))); + } + + @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @Test + public void setAndGetActiveUserForRole() throws Exception { + runWithShellPermissionIdentity(() -> { + sRoleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME, + Process.myUserHandle(), 0); + assertThat(sRoleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME)) + .isEqualTo(Process.myUserHandle()); + }); + } + @NonNull private List<String> getRoleHolders(@NonNull String roleName) throws Exception { return callWithShellPermissionIdentity(() -> sRoleManager.getRoleHolders(roleName)); diff --git a/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt b/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt index 83d4f78ad..f615f0f4b 100644 --- a/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt +++ b/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt @@ -19,15 +19,19 @@ package android.app.role.cts import android.app.role.RoleManager import android.os.Build import android.os.UserHandle +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.DeviceFlagsValueProvider import androidx.test.InstrumentationRegistry import androidx.test.filters.SdkSuppress import androidx.test.runner.AndroidJUnit4 import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow +import com.android.permission.flags.Flags import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Assert.assertThrows import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -43,6 +47,8 @@ class RoleShellCommandTest { private var roleHolder: String? = null private var wasBypassingRoleQualification: Boolean = false + @get:Rule val flagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + @Before public fun setUp() { saveRoleHolder() @@ -156,6 +162,28 @@ class RoleShellCommandTest { assertThat(isBypassingRoleQualification()).isFalse() } + @RequiresFlagsEnabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @Test + fun setActiveUserForProfileGroupExclusiveRoleAsUser() { + val activeUser = userId + setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, activeUser) + + val currentActiveUserId = getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME) + assertThat(currentActiveUserId).isEqualTo(activeUser) + } + + @RequiresFlagsEnabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @Test + fun setActiveUserForNonProfileGroupExclusiveRoleThenFails() { + assertThrows(AssertionError::class.java) { setActiveUserForRole(ROLE_NAME, userId) } + } + + @RequiresFlagsEnabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @Test + fun getActiveUserForNonProfileGroupExclusiveRoleThenFails() { + assertThrows(AssertionError::class.java) { getActiveUserForRole(ROLE_NAME) } + } + private fun addRoleHolder(packageName: String = APP_PACKAGE_NAME) { runShellCommandOrThrow("cmd role add-role-holder --user $userId $ROLE_NAME $packageName") } @@ -204,8 +232,22 @@ class RoleShellCommandTest { callWithShellPermissionIdentity { roleManager.setBypassingRoleQualification(value) } } + private fun getActiveUserForRole(roleName: String): Int? { + return runShellCommandOrThrow("cmd role get-active-user-for-role --user $userId $roleName") + .trim() + .toIntOrNull() + } + + private fun setActiveUserForRole(roleName: String, activeUserId: Int) { + runShellCommandOrThrow( + "cmd role set-active-user-for-role --user $userId $roleName $activeUserId" + ) + } + companion object { private const val ROLE_NAME = RoleManager.ROLE_BROWSER + private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME = + RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY private const val APP_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestApp.apk" private const val APP_PACKAGE_NAME = "android.app.role.cts.app" private const val APP_CLONE_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestAppClone.apk" diff --git a/tests/functional/safetycenter/singleuser/AndroidTest.xml b/tests/functional/safetycenter/singleuser/AndroidTest.xml index 3aa173508..af040eb6f 100644 --- a/tests/functional/safetycenter/singleuser/AndroidTest.xml +++ b/tests/functional/safetycenter/singleuser/AndroidTest.xml @@ -47,6 +47,10 @@ <!-- Disable syncing to prevent overwriting flags during testing. --> <option name="run-command" value="device_config set_sync_disabled_for_tests persistent" /> <option name="teardown-command" value="device_config set_sync_disabled_for_tests none" /> + <!-- TODO(b/379928062): Ensure device not on lockscreen. Reassess when keyguard bug is + closed --> + <option name="run-command" value="input keyevent KEYCODE_WAKEUP" /> + <option name="run-command" value="wm dismiss-keyguard" /> <!-- Dismiss any system dialogs (e.g. crashes, ANR). --> <option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" /> </target_preparer> diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt index 912ea44ad..f6bef747d 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt @@ -17,6 +17,7 @@ package com.android.safetycenter.testing import android.Manifest.permission.READ_DEVICE_CONFIG +import android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG import android.Manifest.permission.WRITE_DEVICE_CONFIG import android.annotation.TargetApi import android.app.job.JobInfo @@ -532,7 +533,7 @@ object SafetyCenterFlags { } private fun writeDeviceConfigProperty(name: String, stringValue: String?) { - callWithShellPermissionIdentity(WRITE_DEVICE_CONFIG) { + callWithShellPermissionIdentity(WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG) { val valueWasSet = DeviceConfig.setProperty( NAMESPACE_PRIVACY, |