summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Richard MacGregor <rmacgregor@google.com> 2024-11-25 23:07:45 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-11-25 23:07:45 +0000
commit869c0c8f19f73171681c03690f33c86de2793b91 (patch)
treebe390203fd3a14f26ef4ed82a155b7189c8ccc69
parente88c565f7274465f941a1add6410a8b9db6ac326 (diff)
parentdf5008d7ea4a52d0e7ccaf0eb9f27c457205c2ac (diff)
Merge "Set activeuser when setting role holder" into main
-rw-r--r--PermissionController/role-controller/Android.bp1
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java16
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java38
-rw-r--r--service/java/com/android/role/RoleService.java183
-rw-r--r--tests/cts/rolemultiuser/Android.bp5
-rw-r--r--tests/cts/rolemultiuser/AndroidTest.xml5
-rw-r--r--tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/Android.bp23
-rw-r--r--tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/AndroidManifest.xml24
-rw-r--r--tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt248
9 files changed, 466 insertions, 77 deletions
diff --git a/PermissionController/role-controller/Android.bp b/PermissionController/role-controller/Android.bp
index 9a046a397..9eacf975f 100644
--- a/PermissionController/role-controller/Android.bp
+++ b/PermissionController/role-controller/Android.bp
@@ -28,6 +28,7 @@ java_library {
libs: [
"androidx.annotation_annotation",
"com.android.permission.flags-aconfig-java",
+ "framework-annotations-lib",
],
static_libs: [
"modules-utils-build_system",
diff --git a/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java b/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java
index bc7562c11..a5ac5700e 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.role.controller.service;
+import android.annotation.UserIdInt;
import android.app.role.RoleControllerService;
import android.app.role.RoleManager;
import android.content.Context;
@@ -49,11 +50,21 @@ public class RoleControllerServiceImpl extends RoleControllerService {
private static final boolean DEBUG = false;
+ public static volatile SetActiveUserForRoleMethod sSetActiveUserForRoleMethod;
private UserHandle mUser;
private Context mContext;
private RoleManager mUserRoleManager;
+ /** Method for setting active user from role controller */
+ public interface SetActiveUserForRoleMethod {
+ /**
+ * Sets user as active for the given role.
+ * @see RoleManager#setActiveUserForRole(String, UserHandle, int)
+ */
+ void setActiveUserForRole(@NonNull String roleName, @UserIdInt int userId, int flags);
+ }
+
public RoleControllerServiceImpl() {}
public RoleControllerServiceImpl(@NonNull UserHandle user, @NonNull Context context) {
@@ -232,6 +243,11 @@ public class RoleControllerServiceImpl extends RoleControllerService {
boolean added = false;
if (role.isExclusive()) {
+ if (role.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ sSetActiveUserForRoleMethod.setActiveUserForRole(roleName, mUser.getIdentifier(),
+ flags);
+ }
+
List<String> currentPackageNames = mUserRoleManager.getRoleHolders(roleName);
int currentPackageNamesSize = currentPackageNames.size();
for (int i = 0; i < currentPackageNamesSize; i++) {
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java b/PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java
new file mode 100644
index 000000000..f8a8502cd
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.util;
+
+import android.os.Build;
+
+import androidx.annotation.ChecksSdkIntAtLeast;
+
+import com.android.modules.utils.build.SdkLevel;
+
+/** Util class for getting shared feature flag check logic. */
+public final class RoleFlags {
+ private RoleFlags() { /* cannot be instantiated */ }
+
+ /**
+ * Returns whether profile group exclusive roles are available. Profile exclusive roles are
+ * available on B+
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static boolean isProfileGroupExclusivityAvailable() {
+ // TODO(b/372743073): change to isAtLeastB once available
+ return SdkLevel.isAtLeastV() && com.android.permission.flags.Flags.crossUserRoleEnabled();
+ }
+}
diff --git a/service/java/com/android/role/RoleService.java b/service/java/com/android/role/RoleService.java
index 1145f273d..7c2ab01b1 100644
--- a/service/java/com/android/role/RoleService.java
+++ b/service/java/com/android/role/RoleService.java
@@ -18,7 +18,6 @@ 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;
@@ -72,6 +71,8 @@ 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.role.controller.service.RoleControllerServiceImpl;
+import com.android.role.controller.util.RoleFlags;
import com.android.server.LocalManagerRegistry;
import com.android.server.SystemService;
import com.android.server.role.RoleServicePlatformHelper;
@@ -118,6 +119,10 @@ public class RoleService extends SystemService implements RoleUserState.Callback
if (SdkLevel.isAtLeastV()) {
defaultApplicationRoles.add(RoleManager.ROLE_WALLET);
}
+ if (RoleFlags.isProfileGroupExclusivityAvailable()) {
+ defaultApplicationRoles.add(
+ RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY);
+ }
DEFAULT_APPLICATION_ROLES = defaultApplicationRoles.toArray(new String[0]);
}
@@ -169,6 +174,11 @@ public class RoleService extends SystemService implements RoleUserState.Callback
public RoleService(@NonNull Context context) {
super(context);
+ if (RoleFlags.isProfileGroupExclusivityAvailable()) {
+ RoleControllerServiceImpl.sSetActiveUserForRoleMethod =
+ this::setActiveUserForRoleFromController;
+ }
+
mPlatformHelper = LocalManagerRegistry.getManager(RoleServicePlatformHelper.class);
RoleControllerManager.initializeRemoteServiceComponentName(context);
@@ -466,6 +476,94 @@ public class RoleService extends SystemService implements RoleUserState.Callback
}
}
+ private void enforceProfileGroupExclusiveRole(@NonNull String roleName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkArgument(isProfileGroupExclusiveRole(roleName, getContext()),
+ roleName + " is not a profile-group exclusive role");
+ }
+
+ /**
+ * Returns whether the given role has profile group exclusivity
+ *
+ * @param roleName The name of role to check
+ * @param context The context
+ * @return {@code true} if the role is profile group exclusive, {@code false} otherwise
+ */
+ private static boolean isProfileGroupExclusiveRole(String roleName, Context context) {
+ if (!RoleFlags.isProfileGroupExclusivityAvailable()) {
+ return false;
+ }
+ Role role = Roles.get(context).get(roleName);
+ return role != null && role.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP;
+ }
+
+ private void setActiveUserForRoleFromController(@NonNull String roleName, @UserIdInt int userId,
+ @RoleManager.ManageHoldersFlags int flags) {
+ setActiveUserForRoleAsUserInternal(roleName, userId, flags, false, userId);
+ }
+
+ private void setActiveUserForRoleAsUserInternal(@NonNull String roleName,
+ @UserIdInt int activeUserId, @RoleManager.ManageHoldersFlags int flags,
+ boolean clearRoleHoldersForActiveUser, @UserIdInt int userId) {
+ Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(),
+ "setActiveUserForRoleAsUser not available");
+ enforceProfileGroupExclusiveRole(roleName);
+
+ 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;
+ }
+
+ 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++) {
+ int profilesUserId = profiles.get(i).getIdentifier();
+ if (!clearRoleHoldersForActiveUser && profilesUserId == activeUserId) {
+ continue;
+ }
+ 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());
+ }
+ });
+ 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);
+ }
+ }
+ }
+
private class Stub extends IRoleManager.Stub {
@Override
@@ -530,8 +628,9 @@ 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) {
+ boolean enforceForProfileGroup = isProfileGroupExclusiveRole(roleName, getContext());
UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
- /* enforceForProfileGroup= */ false, "addRoleHolderAsUser", getContext());
+ enforceForProfileGroup, "addRoleHolderAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return;
@@ -618,9 +717,9 @@ 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) {
+ boolean enforceForProfileGroup = isProfileGroupExclusiveRole(roleName, getContext());
UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
- /* enforceForProfileGroup= */ false, "setDefaultApplicationAsUser",
- getContext());
+ enforceForProfileGroup, "setDefaultApplicationAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return;
@@ -643,7 +742,7 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public int getActiveUserForRoleAsUser(@NonNull String roleName, @UserIdInt int userId) {
- Preconditions.checkState(isProfileGroupExclusivityAvailable(),
+ Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(),
"getActiveUserForRoleAsUser not available");
enforceProfileGroupExclusiveRole(roleName);
@@ -668,67 +767,15 @@ public class RoleService extends SystemService implements RoleUserState.Callback
public void setActiveUserForRoleAsUser(@NonNull String roleName,
@UserIdInt int activeUserId, @RoleManager.ManageHoldersFlags int flags,
@UserIdInt int userId) {
- Preconditions.checkState(isProfileGroupExclusivityAvailable(),
+ Preconditions.checkState(RoleFlags.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);
- }
- }
+ }, "setActiveUserForRoleAsUser");
+ setActiveUserForRoleAsUserInternal(roleName, activeUserId, flags, true, userId);
}
@Override
@@ -1164,22 +1211,6 @@ public class RoleService extends SystemService implements RoleUserState.Callback
+ " 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/tests/cts/rolemultiuser/Android.bp b/tests/cts/rolemultiuser/Android.bp
index 13fbf28ff..7de55fc1b 100644
--- a/tests/cts/rolemultiuser/Android.bp
+++ b/tests/cts/rolemultiuser/Android.bp
@@ -23,7 +23,6 @@ android_test {
min_sdk_version: "30",
srcs: [
- "src/**/*.java",
"src/**/*.kt",
],
@@ -44,4 +43,8 @@ android_test {
"mts-permission",
"mcts-permission",
],
+
+ data: [
+ ":CtsRoleMultiUserTestApp",
+ ],
}
diff --git a/tests/cts/rolemultiuser/AndroidTest.xml b/tests/cts/rolemultiuser/AndroidTest.xml
index 00f4c0993..15c34f54a 100644
--- a/tests/cts/rolemultiuser/AndroidTest.xml
+++ b/tests/cts/rolemultiuser/AndroidTest.xml
@@ -39,6 +39,11 @@
<option name="teardown-command" value="rm -rf /data/local/tmp/cts-role"/>
</target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsRoleMultiUserTestApp.apk->/data/local/tmp/cts-role/CtsRoleMultiUserTestApp.apk" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.app.rolemultiuser.cts" />
<option name="exclude-annotation" value="com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile" />
diff --git a/tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/Android.bp b/tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/Android.bp
new file mode 100644
index 000000000..71ccd0e59
--- /dev/null
+++ b/tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/Android.bp
@@ -0,0 +1,23 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsRoleMultiUserTestApp",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "30",
+}
diff --git a/tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/AndroidManifest.xml b/tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/AndroidManifest.xml
new file mode 100644
index 000000000..eea3be741
--- /dev/null
+++ b/tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.app.rolemultiuser.cts.app">
+
+ <application android:label="CtsRoleMultiUserTestApp" />
+</manifest>
diff --git a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt
index d1bf5284c..6c9cdfcb8 100644
--- a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt
+++ b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt
@@ -21,6 +21,7 @@ import android.os.Build
import android.os.Process
import androidx.test.filters.SdkSuppress
import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile
+import com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile
import com.android.bedstead.enterprise.workProfile
import com.android.bedstead.flags.annotations.RequireFlagsEnabled
import com.android.bedstead.harrier.BedsteadJUnit4
@@ -29,9 +30,11 @@ import com.android.bedstead.multiuser.annotations.EnsureHasAdditionalUser
import com.android.bedstead.multiuser.annotations.EnsureHasPrivateProfile
import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser
import com.android.bedstead.multiuser.annotations.RequireRunNotOnSecondaryUser
+import com.android.bedstead.multiuser.annotations.RequireRunOnPrimaryUser
import com.android.bedstead.multiuser.privateProfile
import com.android.bedstead.multiuser.secondaryUser
import com.android.bedstead.nene.TestApis.context
+import com.android.bedstead.nene.TestApis.permissions
import com.android.bedstead.nene.TestApis.users
import com.android.bedstead.nene.types.OptionalBoolean
import com.android.bedstead.permissions.CommonPermissions.INTERACT_ACROSS_USERS_FULL
@@ -39,9 +42,15 @@ import com.android.bedstead.permissions.CommonPermissions.MANAGE_DEFAULT_APPLICA
import com.android.bedstead.permissions.CommonPermissions.MANAGE_ROLE_HOLDERS
import com.android.bedstead.permissions.annotations.EnsureDoesNotHavePermission
import com.android.bedstead.permissions.annotations.EnsureHasPermission
+import com.android.compatibility.common.util.SystemUtil
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Assume.assumeFalse
+import org.junit.Before
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
@@ -50,6 +59,18 @@ import org.junit.runner.RunWith
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
@RunWith(BedsteadJUnit4::class)
class RoleManagerMultiUserTest {
+ @Before
+ @Throws(java.lang.Exception::class)
+ fun setUp() {
+ installAppForAllUsers()
+ }
+
+ @After
+ @Throws(java.lang.Exception::class)
+ fun tearDown() {
+ uninstallAppForAllUsers()
+ }
+
@RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
@EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
@Test
@@ -215,10 +236,237 @@ class RoleManagerMultiUserTest {
.isEqualTo(targetActiveUser)
}
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
+ @EnsureDoesNotHavePermission(INTERACT_ACROSS_USERS_FULL)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(Exception::class)
+ fun cannotAddRoleHolderAsUserForProfileExclusiveRoleWithoutInteractAcrossUserPermission() {
+ // Set other user as active
+ val initialUser = deviceState.workProfile().userHandle()
+ // setActiveUserForRole and getActiveUserForRole is used to ensure initial active users
+ // state and requires INTERACT_ACROSS_USERS_FULL
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use { _ ->
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+ }
+
+ val targetActiveUser = users().current().userHandle()
+ val future = CallbackFuture()
+ assertThrows(SecurityException::class.java) {
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ }
+ assertThat(
+ roleManager.getRoleHoldersAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ targetActiveUser,
+ )
+ )
+ .isEmpty()
+
+ // getActiveUserForRole is used to ensure addRoleHolderAsUser didn't set active user, and
+ // requires INTERACT_ACROSS_USERS_FULL
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use { _ ->
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun addRoleHolderAsUserSetsPrimaryUserAsActive() {
+ // Set other user as active
+ val initialUser = deviceState.workProfile().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val targetActiveUser = users().current().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(
+ roleManager
+ .getRoleHoldersAsUser(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser)
+ .first()
+ )
+ .isEqualTo(APP_PACKAGE_NAME)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnWorkProfile
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun addRoleHolderAsUserSetsWorkProfileAsActive() {
+ // Set other user as active
+ val initialUser = users().main()!!.userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(
+ roleManager
+ .getRoleHoldersAsUser(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser)
+ .first()
+ )
+ .isEqualTo(APP_PACKAGE_NAME)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(MANAGE_DEFAULT_APPLICATIONS)
+ @EnsureDoesNotHavePermission(INTERACT_ACROSS_USERS_FULL)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetDefaultApplicationForProfileExclusiveRoleWithoutInteractAcrossUserPermission() {
+ // Set other user as active
+ val initialUser = deviceState.workProfile().userHandle()
+ // setActiveUserForRole and getActiveUserForRole is used to ensure initial active users
+ // state and requires INTERACT_ACROSS_USERS_FULL
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use { _ ->
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+ }
+
+ val future = CallbackFuture()
+ assertThrows(SecurityException::class.java) {
+ roleManager.setDefaultApplication(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ context.mainExecutor,
+ future,
+ )
+ }
+ assertThat(roleManager.getDefaultApplication(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)).isNull()
+
+ // getActiveUserForRole is used to ensure setDefaultApplication didn't set active user,
+ // and requires INTERACT_ACROSS_USERS_FULL
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use { _ ->
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_DEFAULT_APPLICATIONS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun setDefaultApplicationSetsPrimaryUserAsActive() {
+ // Set other user as active
+ val initialUser = deviceState.workProfile().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val future = CallbackFuture()
+ roleManager.setDefaultApplication(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(roleManager.getDefaultApplication(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(APP_PACKAGE_NAME)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(users().current().userHandle())
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_DEFAULT_APPLICATIONS)
+ @EnsureHasWorkProfile
+ @RequireRunOnWorkProfile
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun setDefaultApplicationSetsWorkProfileAsActive() {
+ // Set other user as active
+ val initialUser = users().main()!!.userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val future = CallbackFuture()
+ roleManager.setDefaultApplication(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(roleManager.getDefaultApplication(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(APP_PACKAGE_NAME)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(deviceState.workProfile().userHandle())
+ }
+
+ @Throws(java.lang.Exception::class)
+ private fun installAppForAllUsers() {
+ SystemUtil.runShellCommandOrThrow("pm install -r --user all $APP_APK_PATH")
+ }
+
+ private fun uninstallAppForAllUsers() {
+ SystemUtil.runShellCommand("pm uninstall $APP_PACKAGE_NAME")
+ }
+
+ class CallbackFuture : CompletableFuture<Boolean?>(), Consumer<Boolean?> {
+ override fun accept(successful: Boolean?) {
+ complete(successful)
+ }
+ }
+
companion object {
+ private const val TIMEOUT_MILLIS: Long = (15 * 1000).toLong()
private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME =
RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY
private const val PRIVATE_PROFILE_TYPE_NAME = "android.os.usertype.profile.PRIVATE"
+ private const val APP_APK_PATH: String =
+ "/data/local/tmp/cts-role/CtsRoleMultiUserTestApp.apk"
+ private const val APP_PACKAGE_NAME: String = "android.app.rolemultiuser.cts.app"
private val context: Context = context().instrumentedContext()
private val roleManager: RoleManager = context.getSystemService(RoleManager::class.java)