summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/android/app/supervision/ISupervisionManager.aidl2
-rw-r--r--core/java/android/app/supervision/SupervisionManager.java22
-rw-r--r--core/java/android/permission/flags.aconfig9
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionService.java39
-rw-r--r--services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt58
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)
+ )
}
}