summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Clara Thomas <cthom@google.com> 2025-02-11 21:02:40 +0000
committer Clara Thomas <cthom@google.com> 2025-02-28 22:41:27 +0000
commit44216415f96664a8a7083d39ddbb4d3096ba7080 (patch)
treedf87289b35ab61f1479d2386affc14e9c9ccc2e0
parentd280057b695acbe35bc76cde4fa9b5aebaaf01df (diff)
Add the supervision profile type.
This new user type is based on the parentless profile used by the communal profile. This profile can be started in the background by any user and can show UI over any user. It is meant to run very infrequently only for actions involved in setting the supervision credentials. This CL just allows for creating the supervision profile, but does not yet introduce changes required for the keyboard to work correctly for activities running in the context of this user. Future work will also limit the number of applications installed into the profile. Test: atest com.android.server.pm.UserManagerTest Bug: 389712089 Flag: android.multiuser.allow_supervising_profile Change-Id: Ic3fe1c2b11b24dd709721a858e88adf2cdb1e640
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/content/pm/UserInfo.java5
-rw-r--r--core/java/android/content/pm/multiuser.aconfig7
-rw-r--r--core/java/android/os/UserManager.java29
-rw-r--r--core/res/res/values/strings.xml2
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--services/core/java/com/android/server/pm/UserTypeFactory.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java47
9 files changed, 124 insertions, 0 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 984bc680c685..32b170a6286b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11725,6 +11725,7 @@ package android.os {
field public static final String USER_TYPE_FULL_GUEST = "android.os.usertype.full.GUEST";
field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY";
field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM";
+ field @FlaggedApi("android.multiuser.allow_supervising_profile") public static final String USER_TYPE_PROFILE_SUPERVISING = "android.os.usertype.profile.SUPERVISING";
field public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS";
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 00ec48b79541..f1f58025238e 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1199,6 +1199,7 @@ package android.content.pm {
method public boolean isProfile();
method public boolean isQuietModeEnabled();
method public boolean isRestricted();
+ method @FlaggedApi("android.multiuser.allow_supervising_profile") public boolean isSupervisingProfile();
method public boolean supportsSwitchTo();
method @Deprecated public boolean supportsSwitchToByUser();
method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 582a1a9442ce..53203eba4020 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -410,6 +410,11 @@ public class UserInfo implements Parcelable {
return UserManager.isUserTypePrivateProfile(userType);
}
+ @FlaggedApi(android.multiuser.Flags.FLAG_ALLOW_SUPERVISING_PROFILE)
+ public boolean isSupervisingProfile() {
+ return UserManager.isUserTypeSupervisingProfile(userType);
+ }
+
/** See {@link #FLAG_DISABLED}*/
@UnsupportedAppUsage
public boolean isEnabled() {
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 5c904c15e706..5af06405b45d 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -629,3 +629,10 @@ flag {
description: "Enable moving content into the Private Space"
bug: "360066001"
}
+
+flag {
+ name: "allow_supervising_profile"
+ namespace: "supervision"
+ description: "Enables support for new supervising user type"
+ bug: "389712089"
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 767019d97758..c01c3cdc7ace 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -209,6 +209,23 @@ public class UserManager {
public static final String USER_TYPE_PROFILE_COMMUNAL = "android.os.usertype.profile.COMMUNAL";
/**
+ * User type representing a user who manages supervision on the device.
+ * When any full user on the device is supervised, the credentials for this profile will be
+ * required in order to perform certain actions for that user (i.e. those controlled by
+ * {@link android.app.supervision.SupervisionManager} or the
+ * {@link android.app.role.RoleManager#ROLE_SYSTEM_SUPERVISION supervision role holder}).
+ * There can only be one supervising profile per device, and the credentials set for that
+ * profile will be used to authorize actions for any supervised user on the device. This is
+ * distinct from a managed profile in that it functions only to authorize certain supervised
+ * actions; it does not represent the user to which restriction or management is applied.
+ * @hide
+ */
+ @FlaggedApi(android.multiuser.Flags.FLAG_ALLOW_SUPERVISING_PROFILE)
+ @SystemApi
+ public static final String USER_TYPE_PROFILE_SUPERVISING =
+ "android.os.usertype.profile.SUPERVISING";
+
+ /**
* User type representing a {@link UserHandle#USER_SYSTEM system} user that is <b>not</b> a
* human user.
* This type of user cannot be created; it can only pre-exist on first boot.
@@ -3226,6 +3243,18 @@ public class UserManager {
}
/**
+ * Returns whether the user type is a
+ * {@link UserManager#USER_TYPE_PROFILE_SUPERVISING supervising profile}.
+ *
+ * @hide
+ */
+ @FlaggedApi(android.multiuser.Flags.FLAG_ALLOW_SUPERVISING_PROFILE)
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static boolean isUserTypeSupervisingProfile(@Nullable String userType) {
+ return USER_TYPE_PROFILE_SUPERVISING.equals(userType);
+ }
+
+ /**
* @hide
* @deprecated Use {@link #isRestrictedProfile()}
*/
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index cb3dfc70d135..c7786b371ea5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6698,6 +6698,8 @@ ul.</string>
<string name="profile_label_test">Test</string>
<!-- Communal profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] -->
<string name="profile_label_communal">Communal</string>
+ <!-- Supervising profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] -->
+ <string name="profile_label_supervising">Supervising</string>
<!-- Accessibility label for managed profile user type [CHAR LIMIT=30] -->
<string name="accessibility_label_managed_profile">Work profile</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a18c1d4df98b..a31d22cab21d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1129,6 +1129,7 @@
<java-symbol type="string" name="profile_label_work_3" />
<java-symbol type="string" name="profile_label_test" />
<java-symbol type="string" name="profile_label_communal" />
+ <java-symbol type="string" name="profile_label_supervising" />
<java-symbol type="string" name="accessibility_label_managed_profile" />
<java-symbol type="string" name="accessibility_label_private_profile" />
<java-symbol type="string" name="accessibility_label_clone_profile" />
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 58c5b1c90a66..5798aa919d96 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -36,6 +36,7 @@ import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_COMMUNAL;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
+import static android.os.UserManager.USER_TYPE_PROFILE_SUPERVISING;
import static android.os.UserManager.USER_TYPE_PROFILE_TEST;
import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
@@ -111,6 +112,7 @@ public final class UserTypeFactory {
builders.put(USER_TYPE_PROFILE_CLONE, getDefaultTypeProfileClone());
builders.put(USER_TYPE_PROFILE_COMMUNAL, getDefaultTypeProfileCommunal());
builders.put(USER_TYPE_PROFILE_PRIVATE, getDefaultTypeProfilePrivate());
+ builders.put(USER_TYPE_PROFILE_SUPERVISING, getDefaultTypeProfileSupervising());
if (Build.IS_DEBUGGABLE) {
builders.put(USER_TYPE_PROFILE_TEST, getDefaultTypeProfileTest());
}
@@ -343,6 +345,29 @@ public final class UserTypeFactory {
}
/**
+ * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_SUPERVISING}
+ * configuration.
+ */
+ private static UserTypeDetails.Builder getDefaultTypeProfileSupervising() {
+ return new UserTypeDetails.Builder()
+ .setName(USER_TYPE_PROFILE_SUPERVISING)
+ .setBaseType(FLAG_PROFILE)
+ .setMaxAllowed(1)
+ .setProfileParentRequired(false)
+ .setEnabled(android.multiuser.Flags.allowSupervisingProfile() ? 1 : 0)
+ .setLabels(R.string.profile_label_supervising)
+ .setDefaultRestrictions(getDefaultSupervisingProfileRestrictions())
+ .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
+ .setDefaultUserProperties(new UserProperties.Builder()
+ .setStartWithParent(false)
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_NO)
+ .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_NO)
+ .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
+ .setCredentialShareableWithParent(false)
+ .setAlwaysVisible(true));
+ }
+
+ /**
* Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SECONDARY}
* configuration.
*/
@@ -449,6 +474,12 @@ public final class UserTypeFactory {
return restrictions;
}
+ private static Bundle getDefaultSupervisingProfileRestrictions() {
+ final Bundle restrictions = getDefaultProfileRestrictions();
+ restrictions.putBoolean(UserManager.DISALLOW_INSTALL_APPS, true);
+ return restrictions;
+ }
+
private static Bundle getDefaultManagedProfileSecureSettings() {
// Only add String values to the bundle, settings are written as Strings eventually
final Bundle settings = new Bundle();
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index f9946604ad5d..b842d3a42f26 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -389,6 +389,44 @@ public final class UserManagerTest {
}
@Test
+ public void testSupervisingProfile() throws Exception {
+ assumeTrue("Device doesn't support supervising profiles ",
+ mUserManager.isUserTypeEnabled(UserManager.USER_TYPE_PROFILE_SUPERVISING));
+
+ final UserTypeDetails userTypeDetails =
+ UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_SUPERVISING);
+ assertWithMessage("No supervising user type on device").that(userTypeDetails).isNotNull();
+
+
+ // Create supervising profile if it doesn't exist
+ UserInfo supervisingUser = getSupervisingProfile();
+ if (supervisingUser == null) {
+ supervisingUser = createUser("Supervising",
+ UserManager.USER_TYPE_PROFILE_SUPERVISING, /*flags*/ 0);
+ }
+ assertWithMessage("Couldn't create supervising profile").that(supervisingUser).isNotNull();
+ UserHandle supervisingHandle = supervisingUser.getUserHandle();
+
+ // Test that only one supervising profile can be created
+ final UserInfo secondSupervisingProfile =
+ createUser("Supervising", UserManager.USER_TYPE_PROFILE_SUPERVISING,
+ /*flags*/ 0);
+ assertThat(secondSupervisingProfile).isNull();
+
+ // Verify that the supervising profile doesn't have a parent
+ assertThat(mUserManager.getProfileParent(supervisingHandle.getIdentifier())).isNull();
+
+ // Make sure that the supervising profile can be started in the background, and that it
+ // is visible
+ final boolean isStarted = mActivityManager.startProfile(supervisingHandle);
+ assertWithMessage("Unable to start supervising profile").that(isStarted).isTrue();
+ final UserManager umSupervising = (UserManager) mContext.createPackageContextAsUser(
+ "android", 0, supervisingHandle).getSystemService(Context.USER_SERVICE);
+ assertWithMessage("Supervising profile not visible").that(
+ umSupervising.isUserVisible()).isTrue();
+ }
+
+ @Test
public void testGetProfileAccessibilityString_throwsExceptionForNonProfileUser() {
UserInfo user1 = createUser("Guest 1", UserInfo.FLAG_GUEST);
assertThat(user1).isNotNull();
@@ -2198,4 +2236,13 @@ public final class UserManagerTest {
assertEquals(actual.getLevel(), expected.getLevel());
}
+ @Nullable
+ private UserInfo getSupervisingProfile() {
+ for (UserInfo user : mUserManager.getUsers()) {
+ if (user.userType.equals(UserManager.USER_TYPE_PROFILE_SUPERVISING)) {
+ return user;
+ }
+ }
+ return null;
+ }
}