diff options
author | 2025-03-10 13:48:15 -0500 | |
---|---|---|
committer | 2025-03-21 14:29:00 -0700 | |
commit | 5844ebbde903e1a104c63a97b405ed1593ce804c (patch) | |
tree | 55d8ef6a7229ece3cb559c62a8e81a7adee0ab64 | |
parent | a54d13db7adbf076531873ebb3ce404355200945 (diff) |
Allow setting of the supervision role for CTS
Bug: 378102594
Flag: android.permission.flags.enable_system_supervision_role_behavior
Test: atest CtsSupervisionTestCases
Change-Id: Ie1bdf36410c366f29ce7a004fa20bfb2d3d48d6a
6 files changed, 131 insertions, 0 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b92df4cf7884..a0547411cd9e 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2950,6 +2950,7 @@ package android.app.supervision { @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") public class SupervisionManager { method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public android.content.Intent createConfirmSupervisionCredentialsIntent(); method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSupervisionEnabled(); + method @FlaggedApi("android.permission.flags.enable_system_supervision_role_behavior") @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public boolean shouldAllowBypassingSupervisionRoleQualification(); } } diff --git a/core/java/android/app/supervision/ISupervisionManager.aidl b/core/java/android/app/supervision/ISupervisionManager.aidl index 2f67a8abcd17..801162f3cbd3 100644 --- a/core/java/android/app/supervision/ISupervisionManager.aidl +++ b/core/java/android/app/supervision/ISupervisionManager.aidl @@ -27,4 +27,6 @@ interface ISupervisionManager { boolean isSupervisionEnabledForUser(int userId); void setSupervisionEnabledForUser(int userId, boolean enabled); String getActiveSupervisionAppPackage(int userId); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS)") + boolean shouldAllowBypassingSupervisionRoleQualification(); } diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java index 172ed2358a5d..76a789d3426f 100644 --- a/core/java/android/app/supervision/SupervisionManager.java +++ b/core/java/android/app/supervision/SupervisionManager.java @@ -19,6 +19,7 @@ package android.app.supervision; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.MANAGE_USERS; import static android.Manifest.permission.QUERY_USERS; +import static android.permission.flags.Flags.FLAG_ENABLE_SYSTEM_SUPERVISION_ROLE_BEHAVIOR; import android.annotation.FlaggedApi; import android.annotation.Nullable; @@ -193,4 +194,25 @@ public class SupervisionManager { } return null; } + + + /** + * @return {@code true} if bypassing the qualification is allowed for the specified role based + * on the current state of the device. + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_ENABLE_SYSTEM_SUPERVISION_ROLE_BEHAVIOR) + @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) + public boolean shouldAllowBypassingSupervisionRoleQualification() { + if (mService != null) { + try { + return mService.shouldAllowBypassingSupervisionRoleQualification(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } } diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 34272b17cf54..ef6f37ac6f9c 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -554,3 +554,12 @@ flag { description: "This flag is used to add role protection to READ_BLOCKED_NUMBERS for SYSTEM_UI_INTELLIGENCE" bug: "354758615" } + +flag { + name: "enable_system_supervision_role_behavior" + is_fixed_read_only: true + is_exported: true + namespace: "supervision" + description: "This flag is used to enable the role behavior for the system supervision role" + bug: "378102594" +} diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java index 0b5a95b0e888..c419fd2ecbd7 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionService.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java @@ -17,6 +17,7 @@ package com.android.server.supervision; import static android.Manifest.permission.INTERACT_ACROSS_USERS; +import static android.Manifest.permission.MANAGE_ROLE_HOLDERS; import static android.Manifest.permission.MANAGE_USERS; import static android.Manifest.permission.QUERY_USERS; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -171,6 +172,44 @@ public class SupervisionService extends ISupervisionManager.Stub { } @Override + public boolean shouldAllowBypassingSupervisionRoleQualification() { + enforcePermission(MANAGE_ROLE_HOLDERS); + + if (hasNonTestDefaultUsers()) { + return false; + } + + synchronized (getLockObject()) { + for (int i = 0; i < mUserData.size(); i++) { + if (mUserData.valueAt(i).supervisionEnabled) { + return false; + } + } + } + + return true; + } + + /** + * Returns true if there are any non-default non-test users. + * + * This excludes the system and main user(s) as those users are created by default. + */ + private boolean hasNonTestDefaultUsers() { + List<UserInfo> users = mInjector.getUserManagerInternal().getUsers(true); + for (var user : users) { + if (!user.isForTesting() && !user.isMain() && !isSystemUser(user)) { + return true; + } + } + return false; + } + + private static boolean isSystemUser(UserInfo userInfo) { + return (userInfo.flags & UserInfo.FLAG_SYSTEM) == UserInfo.FLAG_SYSTEM; + } + + @Override public void onShellCommand( @Nullable FileDescriptor in, @Nullable FileDescriptor out, diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt index c59f0a05c619..02b97442b218 100644 --- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt +++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt @@ -29,9 +29,15 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.content.pm.UserInfo +import android.content.pm.UserInfo.FLAG_FOR_TESTING +import android.content.pm.UserInfo.FLAG_FULL +import android.content.pm.UserInfo.FLAG_MAIN +import android.content.pm.UserInfo.FLAG_SYSTEM import android.os.Handler import android.os.PersistableBundle import android.os.UserHandle +import android.os.UserHandle.MIN_SECONDARY_USER_ID +import android.os.UserHandle.USER_SYSTEM import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.DeviceFlagsValueProvider import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -49,6 +55,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule +import org.mockito.kotlin.any import org.mockito.kotlin.whenever /** @@ -289,6 +296,36 @@ class SupervisionServiceTest { assertThat(service.createConfirmSupervisionCredentialsIntent()).isNull() } + fun shouldAllowBypassingSupervisionRoleQualification_returnsTrue() { + assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + assertThat(service.shouldAllowBypassingSupervisionRoleQualification()).isTrue() + + addDefaultAndTestUsers() + assertThat(service.shouldAllowBypassingSupervisionRoleQualification()).isTrue() + } + + @Test + fun shouldAllowBypassingSupervisionRoleQualification_returnsFalse() { + assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + assertThat(service.shouldAllowBypassingSupervisionRoleQualification()).isTrue() + + addDefaultAndTestUsers() + assertThat(service.shouldAllowBypassingSupervisionRoleQualification()).isTrue() + + // Enabling supervision on any user will disallow bypassing + service.setSupervisionEnabledForUser(USER_ID, true) + assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + assertThat(service.shouldAllowBypassingSupervisionRoleQualification()).isFalse() + + // Adding non-default users should also disallow bypassing + addDefaultAndFullUsers() + assertThat(service.shouldAllowBypassingSupervisionRoleQualification()).isFalse() + + // Turning off supervision with non-default users should still disallow bypassing + service.setSupervisionEnabledForUser(USER_ID, false) + assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + } + private val systemSupervisionPackage: String get() = context.getResources().getString(R.string.config_systemSupervision) @@ -310,10 +347,31 @@ class SupervisionServiceTest { context.sendBroadcastAsUser(intent, UserHandle.of(userId)) } + private fun addDefaultAndTestUsers() { + val userInfos = userData.map { (userId, flags) -> + UserInfo(userId, "user" + userId, USER_ICON, flags, USER_TYPE) + } + whenever(mockUserManagerInternal.getUsers(any())).thenReturn(userInfos) + } + + private fun addDefaultAndFullUsers() { + val userInfos = userData.map { (userId, flags) -> + UserInfo(userId, "user" + userId, USER_ICON, flags, USER_TYPE) + } + UserInfo(USER_ID, "user" + USER_ID, USER_ICON, FLAG_FULL, USER_TYPE) + whenever(mockUserManagerInternal.getUsers(any())).thenReturn(userInfos) + } + private companion object { const val USER_ID = 100 const val APP_UID = USER_ID * UserHandle.PER_USER_RANGE const val SUPERVISING_USER_ID = 10 + const val USER_ICON = "user_icon" + const val USER_TYPE = "fake_user_type" + val userData: Map<Int, Int> = mapOf( + USER_SYSTEM to FLAG_SYSTEM, + MIN_SECONDARY_USER_ID to FLAG_MAIN, + (MIN_SECONDARY_USER_ID + 1) to (FLAG_FULL or FLAG_FOR_TESTING) + ) } } |