diff options
author | 2024-11-25 23:07:45 +0000 | |
---|---|---|
committer | 2024-11-25 23:07:45 +0000 | |
commit | 869c0c8f19f73171681c03690f33c86de2793b91 (patch) | |
tree | be390203fd3a14f26ef4ed82a155b7189c8ccc69 | |
parent | e88c565f7274465f941a1add6410a8b9db6ac326 (diff) | |
parent | df5008d7ea4a52d0e7ccaf0eb9f27c457205c2ac (diff) |
Merge "Set activeuser when setting role holder" into main
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) |