summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PermissionController/jarjar-rules.txt4
-rw-r--r--PermissionController/res/xml/roles.xml12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/compat/AppOpsManagerCompat.java3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java36
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt25
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt9
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt6
-rw-r--r--PermissionController/tests/inprocess/Android.bp3
-rw-r--r--framework-s/api/system-current.txt3
-rw-r--r--framework-s/java/android/app/role/IRoleManager.aidl4
-rw-r--r--framework-s/java/android/app/role/RoleManager.java92
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationService.java3
-rw-r--r--service/java/com/android/permission/util/UserUtils.java79
-rw-r--r--service/java/com/android/role/RoleService.java202
-rw-r--r--service/java/com/android/role/RoleShellCommand.java27
-rw-r--r--service/java/com/android/role/RoleUserState.java37
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterService.java3
-rw-r--r--service/java/com/android/safetycenter/UserProfileGroup.java10
-rw-r--r--tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt39
-rw-r--r--tests/cts/permissionpolicy/res/raw/android_manifest.xml45
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt15
-rw-r--r--tests/cts/role/Android.bp2
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleManagerTest.java47
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt42
-rw-r--r--tests/functional/safetycenter/singleuser/AndroidTest.xml4
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt3
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,