diff options
author | 2025-02-06 08:28:42 -0800 | |
---|---|---|
committer | 2025-02-06 08:28:42 -0800 | |
commit | 7ca015743f6bbb7d64c7cc295848d54dcd0a1733 (patch) | |
tree | 416be3a3058bee42a31c70a0821d86d2d0adba7e | |
parent | 733cfd6367e2325486692461bc1d0a973ff81a7f (diff) | |
parent | 92fcb69765493138681fae331e0692f6b9f732d7 (diff) |
Merge "Check DISALLOW_CONFIG_DEFAULT_APPS for all users of a cross-profile role" into main
5 files changed, 591 insertions, 11 deletions
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java index 64682ba79..f0df97acc 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java @@ -47,6 +47,7 @@ import androidx.annotation.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.android.role.controller.util.CollectionUtils; +import com.android.role.controller.util.IntentCompat; import com.android.role.controller.util.PackageUtils; import com.android.role.controller.util.RoleFlags; import com.android.role.controller.util.RoleManagerCompat; @@ -1090,14 +1091,36 @@ public class Role { @Nullable public Intent getRestrictionIntentAsUser(@NonNull UserHandle user, @NonNull Context context) { if (SdkLevel.isAtLeastU() && isExclusive()) { - // TODO(b/379143953): if role is profile group exclusive - // check DISALLOW_CONFIG_DEFAULT_APPS for all users - UserManager userManager = context.getSystemService(UserManager.class); - if (userManager.hasUserRestrictionForUser(UserManager.DISALLOW_CONFIG_DEFAULT_APPS, - user)) { - return new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS) - .putExtra(DevicePolicyManager.EXTRA_RESTRICTION, - UserManager.DISALLOW_CONFIG_DEFAULT_APPS); + boolean crossUserRoleUxBugfixEnabled = + com.android.permission.flags.Flags.crossUserRoleUxBugfixEnabled(); + if (crossUserRoleUxBugfixEnabled && getExclusivity() == EXCLUSIVITY_PROFILE_GROUP) { + DevicePolicyManager devicePolicyManager = + context.getSystemService(DevicePolicyManager.class); + if (!devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) { + // For profileGroup exclusive roles users on BYOD are free to choose personal or + // work profile app regardless of DISALLOW_CONFIG_DEFAULT_APPS + return null; + } + } + + // Otherwise if role is profileGroup exclusive check DISALLOW_CONFIG_DEFAULT_APPS for + // all users + List<UserHandle> profiles = + (crossUserRoleUxBugfixEnabled && getExclusivity() == EXCLUSIVITY_PROFILE_GROUP) + ? UserUtils.getUserProfiles(context, true) : List.of(user); + final int profilesSize = profiles.size(); + for (int i = 0; i < profilesSize; i++) { + UserHandle profile = profiles.get(i); + UserManager userManager = context.getSystemService(UserManager.class); + if (userManager.hasUserRestrictionForUser( + UserManager.DISALLOW_CONFIG_DEFAULT_APPS, profile)) { + return new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS) + .putExtra( + DevicePolicyManager.EXTRA_RESTRICTION, + UserManager.DISALLOW_CONFIG_DEFAULT_APPS) + .putExtra(Intent.EXTRA_USER, profile) + .putExtra(IntentCompat.EXTRA_USER_ID, profile.getIdentifier()); + } } } return null; diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/IntentCompat.java b/PermissionController/role-controller/java/com/android/role/controller/util/IntentCompat.java new file mode 100644 index 000000000..9771ad4cf --- /dev/null +++ b/PermissionController/role-controller/java/com/android/role/controller/util/IntentCompat.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2025 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; + +/** Compat class for {@link android.content.Intent} */ +public final class IntentCompat { + /** + * An int representing the user ID to be used. Copy of + * {@link android.content.Intent#EXTRA_USER_ID} + */ + public static final String EXTRA_USER_ID = "android.intent.extra.USER_ID"; +} diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java b/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java index 598057b16..00c05b17c 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java +++ b/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java @@ -28,6 +28,9 @@ import androidx.annotation.Nullable; import com.android.modules.utils.build.SdkLevel; +import java.util.ArrayList; +import java.util.List; + /** Utility class to deal with Android users. */ public final class UserUtils { @@ -132,4 +135,29 @@ public final class UserUtils { UserManager userManager = userContext.getSystemService(UserManager.class); return userManager.getProfileParent(user); } + + /** + * 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); + 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 profile = profiles.get(i); + if (!isPrivateProfile(profile, context)) { + filteredProfiles.add(profile); + } + } + return filteredProfiles; + } } diff --git a/service/java/com/android/permission/util/UserUtils.java b/service/java/com/android/permission/util/UserUtils.java index c69afb199..82e9cbbae 100644 --- a/service/java/com/android/permission/util/UserUtils.java +++ b/service/java/com/android/permission/util/UserUtils.java @@ -65,7 +65,7 @@ public final class UserUtils { DevicePolicyManager devicePolicyManager = context.getSystemService(DevicePolicyManager.class); if (!devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) { - // For profileGroup exclusive roles users on BYOD are free to choose personal o + // For profileGroup exclusive roles users on BYOD are free to choose personal or // work profile app regardless of DISALLOW_DEBUGGING_FEATURES return; } 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 724549149..98aa5fbf1 100644 --- a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt +++ b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt @@ -25,18 +25,22 @@ import android.content.pm.PackageManager import android.os.Build import android.os.Process import android.os.UserHandle +import android.os.UserManager.DISALLOW_CONFIG_DEFAULT_APPS import android.provider.Settings import android.util.Pair import androidx.test.filters.SdkSuppress import androidx.test.rule.ActivityTestRule import androidx.test.uiautomator.By import com.android.bedstead.enterprise.annotations.EnsureHasNoWorkProfile +import com.android.bedstead.enterprise.annotations.EnsureHasUserRestriction 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 import com.android.bedstead.harrier.DeviceState +import com.android.bedstead.harrier.UserType.INITIAL_USER +import com.android.bedstead.harrier.UserType.WORK_PROFILE import com.android.bedstead.multiuser.annotations.EnsureCanAddUser import com.android.bedstead.multiuser.annotations.EnsureHasAdditionalUser import com.android.bedstead.multiuser.annotations.EnsureHasPrivateProfile @@ -62,6 +66,7 @@ import com.android.compatibility.common.util.SystemUtil import com.android.compatibility.common.util.SystemUtil.eventually import com.android.compatibility.common.util.UiAutomatorUtils2.getUiDevice import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject +import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObjectOrNull import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import java.util.Objects @@ -69,6 +74,7 @@ import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit import java.util.function.Consumer import org.junit.After +import org.junit.Assert.assertNull import org.junit.Assert.assertThrows import org.junit.Assume.assumeFalse import org.junit.Assume.assumeTrue @@ -1172,8 +1178,10 @@ class RoleManagerMultiUserTest { } } - @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED, - com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED) + @RequireFlagsEnabled( + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED, + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED, + ) @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) @EnsureHasWorkProfile @RequireRunOnWorkProfile @@ -1575,6 +1583,492 @@ class RoleManagerMultiUserTest { } } + @RequireFlagsEnabled( + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED, + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED, + ) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = INITIAL_USER) + @EnsureHasWorkProfile(isOrganizationOwned = false) + @RequireRunOnPrimaryUser + @Test + @Throws(java.lang.Exception::class) + fun openDefaultAppListAndOpenDefaultAppWhenBYODHasUserRestrictionOnPrimaryProfile() { + try { + // Set test default role holder. Ensures fallbacks to a default holder + setDefaultHoldersForTestForAllUsers() + setRoleVisibleForTestForAllUsers() + + context.startActivity( + Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) + .addCategory(Intent.CATEGORY_DEFAULT) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + getUiDevice().waitForIdle() + waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click() + getUiDevice().waitForIdle() + + // CollapsingToolbar title can't be found by text, so using description instead. + waitFindObject(By.desc(PROFILE_GROUP_EXCLUSIVITY_ROLE_LABEL)) + + pressBack() + pressBack() + } finally { + clearDefaultHoldersForTestForAllUsers() + clearRoleVisibleForTestForAllUsers() + } + } + + @RequireFlagsEnabled( + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED, + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED, + ) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = INITIAL_USER) + @EnsureHasWorkProfile(isOrganizationOwned = true) + @RequireRunOnPrimaryUser + @Test + @Throws(java.lang.Exception::class) + fun openDefaultAppListAndCannotOpenDefaultAppWhenHasUserRestrictionOnPrimaryProfile() { + try { + // Set test default role holder. Ensures fallbacks to a default holder + setDefaultHoldersForTestForAllUsers() + setRoleVisibleForTestForAllUsers() + + context.startActivity( + Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) + .addCategory(Intent.CATEGORY_DEFAULT) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + getUiDevice().waitForIdle() + waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click() + getUiDevice().waitForIdle() + + // CollapsingToolbar title can't be found by text, so using description instead. + assertNull(waitFindObjectOrNull(By.desc(PROFILE_GROUP_EXCLUSIVITY_ROLE_LABEL))) + + pressBack() + pressBack() + } finally { + clearDefaultHoldersForTestForAllUsers() + clearRoleVisibleForTestForAllUsers() + } + } + + @RequireFlagsEnabled( + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED, + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED, + ) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = WORK_PROFILE) + @EnsureHasWorkProfile(isOrganizationOwned = true) + @RequireRunOnPrimaryUser + @Test + @Throws(java.lang.Exception::class) + fun openDefaultAppListAndCannotOpenDefaultAppWhenHasUserRestrictionOnWorkProfile() { + try { + // Set test default role holder. Ensures fallbacks to a default holder + setDefaultHoldersForTestForAllUsers() + setRoleVisibleForTestForAllUsers() + + context.startActivity( + Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) + .addCategory(Intent.CATEGORY_DEFAULT) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + getUiDevice().waitForIdle() + waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click() + getUiDevice().waitForIdle() + + // CollapsingToolbar title can't be found by text, so using description instead. + assertNull(waitFindObjectOrNull(By.desc(PROFILE_GROUP_EXCLUSIVITY_ROLE_LABEL))) + + pressBack() + pressBack() + } finally { + clearDefaultHoldersForTestForAllUsers() + clearRoleVisibleForTestForAllUsers() + } + } + + @RequireFlagsEnabled( + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED, + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED, + ) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = INITIAL_USER) + @EnsureHasWorkProfile(isOrganizationOwned = false) + @RequireRunOnPrimaryUser + @Test + @Throws(java.lang.Exception::class) + fun openDefaultAppDetailsAndSetDefaultAppWhenBYODHasUserRestrictionOnPrimaryProfile() { + try { + // Set test default role holder. Ensures fallbacks to a default holder + setDefaultHoldersForTestForAllUsers() + setRoleVisibleForTestForAllUsers() + + // Ensure non-target selected first. Request exits early if user and package + // already the role holder + val initialActiveUser = deviceState.workProfile().userHandle() + val future = CallbackFuture() + roleManager.addRoleHolderAsUser( + PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, + APP_PACKAGE_NAME, + 0, + initialActiveUser, + context.mainExecutor, + future, + ) + assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue() + + context.startActivity( + Intent(Intent.ACTION_MANAGE_DEFAULT_APP) + .addCategory(Intent.CATEGORY_DEFAULT) + .putExtra(Intent.EXTRA_ROLE_NAME, PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + getUiDevice().waitForIdle() + + val targetActiveUser = users().current().userHandle() + val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}" + if (isWatch) { + waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click() + waitFindObject( + By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel)) + ) + } else { + waitFindObject( + By.clickable(true) + .hasDescendant(By.checkable(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + .click() + waitFindObject( + By.clickable(true) + .hasDescendant(By.checkable(true).checked(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + } + + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isEqualTo(targetActiveUser) + assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser) + + pressBack() + pressBack() + } finally { + clearDefaultHoldersForTestForAllUsers() + clearRoleVisibleForTestForAllUsers() + } + } + + @RequireFlagsEnabled( + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED, + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED, + ) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = INITIAL_USER) + @EnsureHasWorkProfile(isOrganizationOwned = true) + @RequireRunOnPrimaryUser + @Test + @Throws(java.lang.Exception::class) + fun openDefaultAppDetailsAndCannotSetDefaultAppWhenHasUserRestrictionOnPrimaryProfile() { + try { + // Set test default role holder. Ensures fallbacks to a default holder + setDefaultHoldersForTestForAllUsers() + setRoleVisibleForTestForAllUsers() + + // Ensure non-target selected first. Request exits early if user and package + // already the role holder + val initialActiveUser = deviceState.workProfile().userHandle() + val future = CallbackFuture() + roleManager.addRoleHolderAsUser( + PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, + APP_PACKAGE_NAME, + 0, + initialActiveUser, + context.mainExecutor, + future, + ) + assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue() + + context.startActivity( + Intent(Intent.ACTION_MANAGE_DEFAULT_APP) + .addCategory(Intent.CATEGORY_DEFAULT) + .putExtra(Intent.EXTRA_ROLE_NAME, PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + getUiDevice().waitForIdle() + + val targetActiveUser = users().current().userHandle() + val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}" + if (isWatch) { + waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click() + } else { + waitFindObject( + By.clickable(true) + .hasDescendant(By.checkable(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + .click() + } + + if (isWatch) { + assertNull( + waitFindObjectOrNull( + By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel)) + ) + ) + } else { + assertNull( + waitFindObjectOrNull( + By.clickable(true) + .hasDescendant(By.checkable(true).checked(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + ) + } + + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isEqualTo(initialActiveUser) + assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(initialActiveUser) + + pressBack() + pressBack() + } finally { + clearDefaultHoldersForTestForAllUsers() + clearRoleVisibleForTestForAllUsers() + } + } + + @RequireFlagsEnabled( + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED, + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED, + ) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = WORK_PROFILE) + @EnsureHasWorkProfile(isOrganizationOwned = true) + @RequireRunOnPrimaryUser + @Test + @Throws(java.lang.Exception::class) + fun openDefaultAppDetailsAndCannotSetDefaultAppWhenHasUserRestrictionOnWorkProfile() { + try { + // Set test default role holder. Ensures fallbacks to a default holder + setDefaultHoldersForTestForAllUsers() + setRoleVisibleForTestForAllUsers() + + // Ensure non-target selected first. Request exits early if user and package + // already the role holder + val initialActiveUser = deviceState.workProfile().userHandle() + val future = CallbackFuture() + roleManager.addRoleHolderAsUser( + PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, + APP_PACKAGE_NAME, + 0, + initialActiveUser, + context.mainExecutor, + future, + ) + assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue() + + context.startActivity( + Intent(Intent.ACTION_MANAGE_DEFAULT_APP) + .addCategory(Intent.CATEGORY_DEFAULT) + .putExtra(Intent.EXTRA_ROLE_NAME, PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + getUiDevice().waitForIdle() + + val targetActiveUser = users().current().userHandle() + val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}" + if (isWatch) { + waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click() + } else { + waitFindObject( + By.clickable(true) + .hasDescendant(By.checkable(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + .click() + } + + if (isWatch) { + assertNull( + waitFindObjectOrNull( + By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel)) + ) + ) + } else { + assertNull( + waitFindObjectOrNull( + By.clickable(true) + .hasDescendant(By.checkable(true).checked(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + ) + } + + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isEqualTo(initialActiveUser) + assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(initialActiveUser) + + pressBack() + pressBack() + } finally { + clearDefaultHoldersForTestForAllUsers() + clearRoleVisibleForTestForAllUsers() + } + } + + @RequireFlagsEnabled( + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED, + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED, + ) + @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = INITIAL_USER) + @EnsureHasWorkProfile(isOrganizationOwned = false) + @RequireRunOnPrimaryUser + @Test + @Throws(java.lang.Exception::class) + fun requestRoleAllowedWhenBYODHasUserRestrictionOnPrimaryProfile() { + try { + // setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require + // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user + // role active user and role holder states + permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ -> + // Set test default role holder. Ensures fallbacks to a default holder + setDefaultHoldersForTestForAllUsers() + setRoleVisibleForTestForAllUsers() + + // Ensure non-primary selected first. Request exits early if user and package + // already the role holder + val future = CallbackFuture() + roleManager.addRoleHolderAsUser( + PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, + APP_PACKAGE_NAME, + 0, + deviceState.workProfile().userHandle(), + context.mainExecutor, + future, + ) + assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue() + } + + requestRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME) + + val targetActiveUser = deviceState.initialUser().userHandle() + respondToRoleRequest(true, targetActiveUser) + + // getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and + // MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role + // holder states + permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ -> + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isEqualTo(targetActiveUser) + assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser) + } + } finally { + // clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require + // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user + // role active user and role holder states + permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ -> + clearDefaultHoldersForTestForAllUsers() + clearRoleVisibleForTestForAllUsers() + } + } + } + + @RequireFlagsEnabled( + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED, + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED, + ) + @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = INITIAL_USER) + @EnsureHasWorkProfile(isOrganizationOwned = true) + @RequireRunOnPrimaryUser + @Test + @Throws(java.lang.Exception::class) + fun requestRoleDeniedWhenHasUserRestrictionOnPrimaryProfile() { + try { + // setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require + // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user + // role active user and role holder states + permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ -> + // Set test default role holder. Ensures fallbacks to a default holder + setDefaultHoldersForTestForAllUsers() + setRoleVisibleForTestForAllUsers() + + // Ensure non-primary selected first. Request exits early if user and package + // already the role holder + val future = CallbackFuture() + roleManager.addRoleHolderAsUser( + PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, + APP_PACKAGE_NAME, + 0, + deviceState.workProfile().userHandle(), + context.mainExecutor, + future, + ) + assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue() + } + + requestRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME) + roleRequestNotShown() + } finally { + // clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require + // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user + // role active user and role holder states + permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ -> + clearDefaultHoldersForTestForAllUsers() + clearRoleVisibleForTestForAllUsers() + } + } + } + + @RequireFlagsEnabled( + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED, + com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED, + ) + @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = WORK_PROFILE) + @EnsureHasWorkProfile(isOrganizationOwned = true) + @RequireRunOnPrimaryUser + @Test + @Throws(java.lang.Exception::class) + fun requestRoleDeniedWhenHasUserRestrictionOnWorkProfile() { + try { + // setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require + // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user + // role active user and role holder states + permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ -> + // Set test default role holder. Ensures fallbacks to a default holder + setDefaultHoldersForTestForAllUsers() + setRoleVisibleForTestForAllUsers() + + // Ensure non-primary selected first. Request exits early if user and package + // already the role holder + val future = CallbackFuture() + roleManager.addRoleHolderAsUser( + PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, + APP_PACKAGE_NAME, + 0, + deviceState.workProfile().userHandle(), + context.mainExecutor, + future, + ) + assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue() + } + + requestRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME) + roleRequestNotShown() + } finally { + // clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require + // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user + // role active user and role holder states + permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ -> + clearDefaultHoldersForTestForAllUsers() + clearRoleVisibleForTestForAllUsers() + } + } + } + @Throws(java.lang.Exception::class) private fun installAppForAllUsers() { SystemUtil.runShellCommandOrThrow("pm install -r --user all $APP_APK_PATH") @@ -1623,6 +2117,14 @@ class RoleManagerMultiUserTest { return waitForResult() } + private fun roleRequestNotShown() { + val requestRoleItem = waitFindObjectOrNull(By.textStartsWith(APP_LABEL)) + assertNull(requestRoleItem) + + val result: Pair<Int, Intent?> = waitForResult() + assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED) + } + @Throws(InterruptedException::class) private fun waitForResult(): Pair<Int, Intent?> { return activityRule.getActivity().waitForActivityResult(TIMEOUT_MILLIS) @@ -1751,6 +2253,8 @@ class RoleManagerMultiUserTest { 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 PROFILE_GROUP_EXCLUSIVITY_ROLE_LABEL = + "Default test profile group exclusive role app" private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL = "Test profile group exclusive role app" private const val PRIVATE_PROFILE_TYPE_NAME = "android.os.usertype.profile.PRIVATE" |