Headless DO implementation
Bug: 289515470
Test: m
Change-Id: I26721dfb1f53bdde4eb122fe0c545e76b40f5677
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 1c6df75..79b4fd6 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -855,6 +855,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "device_policy_aconfig_flags_lib_host",
+ aconfig_declarations: "device_policy_aconfig_flags",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
cc_aconfig_library {
name: "device_policy_aconfig_flags_c_lib",
aconfig_declarations: "device_policy_aconfig_flags",
diff --git a/core/api/current.txt b/core/api/current.txt
index 3fde9a6..b443073 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7817,6 +7817,7 @@
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DeviceAdminInfo> CREATOR;
field public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1; // 0x1
+ field @FlaggedApi("android.app.admin.flags.headless_device_owner_single_user_enabled") public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2; // 0x2
field public static final int HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED = 0; // 0x0
field public static final int USES_ENCRYPTED_STORAGE = 7; // 0x7
field public static final int USES_POLICY_DISABLE_CAMERA = 8; // 0x8
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c09653d..8730777 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1389,6 +1389,7 @@
field public static final int STATUS_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
field public static final int STATUS_HAS_DEVICE_OWNER = 1; // 0x1
field public static final int STATUS_HAS_PAIRED = 8; // 0x8
+ field @FlaggedApi("android.app.admin.flags.headless_device_owner_single_user_enabled") public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17; // 0x11
field public static final int STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED = 16; // 0x10
field public static final int STATUS_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
field public static final int STATUS_NONSYSTEM_USER_EXISTS = 5; // 0x5
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 14462b8..7d5d5c1 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -16,8 +16,10 @@
package android.app.admin;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.app.admin.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -176,7 +178,18 @@
*/
public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1;
- @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED})
+ /**
+ * Value for {@link #getHeadlessDeviceOwnerMode} which indicates that this DPC should be
+ * provisioned into the first secondary user when on a Headless System User Mode device.
+ *
+ * <p>This mode only allows a single secondary user on the device blocking the creation of
+ * additional secondary users.
+ */
+ @FlaggedApi(Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
+ public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
+
+ @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED,
+ HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER})
@Retention(RetentionPolicy.SOURCE)
private @interface HeadlessDeviceOwnerMode {}
@@ -373,6 +386,8 @@
mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
} else if (deviceOwnerModeStringValue.equalsIgnoreCase("affiliated")) {
mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
+ } else if (deviceOwnerModeStringValue.equalsIgnoreCase("single_user")) {
+ mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
} else {
throw new XmlPullParserException("headless-system-user mode must be valid");
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c8762c6..c649e62 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -84,6 +84,7 @@
import android.app.IServiceConnection;
import android.app.KeyguardManager;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.flags.Flags;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -2863,6 +2864,19 @@
public static final int STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED = 16;
/**
+ * Result code for {@link #checkProvisioningPrecondition}.
+ *
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when provisioning a DPC into the
+ * {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER} mode but only the system
+ * user exists on the device.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
+ public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17;
+
+ /**
* Result codes for {@link #checkProvisioningPrecondition} indicating all the provisioning pre
* conditions.
*
@@ -2876,7 +2890,7 @@
STATUS_CANNOT_ADD_MANAGED_PROFILE, STATUS_DEVICE_ADMIN_NOT_SUPPORTED,
STATUS_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER,
STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS,
- STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED
+ STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED, STATUS_HEADLESS_ONLY_SYSTEM_USER
})
public @interface ProvisioningPrecondition {}
@@ -9178,9 +9192,11 @@
* <p>Calling this after the setup phase of the device owner user has completed is allowed only
* if the caller is the {@link Process#SHELL_UID Shell UID}, and there are no additional users
* (except when the device runs on headless system user mode, in which case it could have exact
- * one extra user, which is the current user - the device owner will be set in the
- * {@link UserHandle#SYSTEM system} user and a profile owner will be set in the current user)
- * and no accounts.
+ * one extra user, which is the current user.
+ *
+ * <p>On a headless devices, if it is in affiliated mode the device owner will be set in the
+ * {@link UserHandle#SYSTEM system} user. If the device is in single user mode, the device owner
+ * will be set in the first secondary user.
*
* @param who the component name to be registered as device owner.
* @param userId ID of the user on which the device owner runs.
@@ -11371,7 +11387,9 @@
* @see UserHandle
* @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the
* user could not be created.
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if headless device is in
+ * {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER} mode.
+ * @throws SecurityException if {@code admin} is not a device owner
* @throws UserOperationException if the user could not be created and the calling app is
* targeting {@link android.os.Build.VERSION_CODES#P} and running on
* {@link android.os.Build.VERSION_CODES#P}.
@@ -16612,7 +16630,10 @@
* before calling this method.
*
* <p>Holders of {@link android.Manifest.permission#PROVISION_DEMO_DEVICE} can call this API
- * only if {@link FullyManagedDeviceProvisioningParams#isDemoDevice()} is {@code true}.</p>
+ * only if {@link FullyManagedDeviceProvisioningParams#isDemoDevice()} is {@code true}.
+ *
+ * <p>If headless device is in {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER}
+ * mode then it sets the device owner on the first secondary user.</p>
*
* @param provisioningParams Params required to provision a fully managed device,
* see {@link FullyManagedDeviceProvisioningParams}.
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 6cc8af8..3c98ef9 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -76,3 +76,10 @@
description: "Enable APIs to provision and manage eSIMs"
bug: "295301164"
}
+
+flag {
+ name: "headless_device_owner_single_user_enabled"
+ namespace: "enterprise"
+ description: "Add Headless DO support."
+ bug: "289515470"
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 05d1c49..85eac29 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -98,6 +98,7 @@
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
+import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
import static android.app.admin.DeviceAdminInfo.USES_POLICY_FORCE_LOCK;
import static android.app.admin.DeviceAdminInfo.USES_POLICY_WIPE_DATA;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
@@ -189,6 +190,7 @@
import static android.app.admin.DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.STATUS_NONSYSTEM_USER_EXISTS;
import static android.app.admin.DevicePolicyManager.STATUS_NOT_SYSTEM_USER;
+import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_ONLY_SYSTEM_USER;
import static android.app.admin.DevicePolicyManager.STATUS_OK;
import static android.app.admin.DevicePolicyManager.STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
import static android.app.admin.DevicePolicyManager.STATUS_SYSTEM_USER;
@@ -225,6 +227,7 @@
import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
import static android.app.admin.flags.Flags.backupServiceSecurityLogEventEnabled;
import static android.app.admin.flags.Flags.dumpsysPolicyEngineMigrationEnabled;
+import static android.app.admin.flags.Flags.headlessDeviceOwnerSingleUserEnabled;
import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
@@ -9460,7 +9463,8 @@
}
if (setProfileOwnerOnCurrentUserIfNecessary
- && mInjector.userManagerIsHeadlessSystemUserMode()) {
+ && mInjector.userManagerIsHeadlessSystemUserMode()
+ && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED) {
int currentForegroundUser;
synchronized (getLockObject()) {
currentForegroundUser = getCurrentForegroundUserId();
@@ -9476,6 +9480,12 @@
return true;
}
+ private int getHeadlessDeviceOwnerMode() {
+ synchronized (getLockObject()) {
+ return getDeviceOwnerAdminLocked().info.getHeadlessDeviceOwnerMode();
+ }
+ }
+
/**
* This API is cached: invalidate with invalidateBinderCaches().
*/
@@ -12226,6 +12236,13 @@
+ admin + " are not in the same package");
}
final CallerIdentity caller = getCallerIdentity(admin);
+
+ if (headlessDeviceOwnerSingleUserEnabled()) {
+ // Block this method if the device is in headless main user mode
+ Preconditions.checkCallAuthorization(
+ getHeadlessDeviceOwnerMode() != HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER,
+ "createAndManageUser was called while in headless single user mode");
+ }
// Only allow the system user to use this method
Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
"createAndManageUser was called from non-system user");
@@ -16636,29 +16653,51 @@
return STATUS_USER_NOT_RUNNING;
}
+ DeviceAdminInfo adminInfo = null;
+
+ boolean isHeadlessModeAffiliated = false;
+
+ boolean isHeadlessModeSingleUser = false;
+
boolean isHeadlessSystemUserMode = mInjector.userManagerIsHeadlessSystemUserMode();
+ int ensureSetUpUser = UserHandle.USER_SYSTEM;
if (isHeadlessSystemUserMode) {
- if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
+ if (owner != null) {
+ adminInfo = findAdmin(owner,
+ deviceOwnerUserId, /* throwForMissingPermission= */ false);
+
+ isHeadlessModeAffiliated =
+ adminInfo.getHeadlessDeviceOwnerMode()
+ == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
+
+ isHeadlessModeSingleUser =
+ adminInfo.getHeadlessDeviceOwnerMode()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
+
+ if (!isHeadlessModeAffiliated && !isHeadlessModeSingleUser) {
+ return STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
+ }
+
+ if (headlessDeviceOwnerSingleUserEnabled() && isHeadlessModeSingleUser) {
+ ensureSetUpUser = mUserManagerInternal.getMainUserId();
+ if (ensureSetUpUser == UserHandle.USER_NULL) {
+ return STATUS_HEADLESS_ONLY_SYSTEM_USER;
+ }
+ }
+ }
+
+ if (isHeadlessModeAffiliated && deviceOwnerUserId != UserHandle.USER_SYSTEM) {
Slogf.e(LOG_TAG, "In headless system user mode, "
+ "device owner can only be set on headless system user.");
return STATUS_NOT_SYSTEM_USER;
}
- if (owner != null) {
- DeviceAdminInfo adminInfo = findAdmin(
- owner, deviceOwnerUserId, /* throwForMissingPermission= */ false);
-
- if (adminInfo.getHeadlessDeviceOwnerMode()
- != HEADLESS_DEVICE_OWNER_MODE_AFFILIATED) {
- return STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
- }
- }
}
if (isAdb) {
// If shell command runs after user setup completed check device status. Otherwise, OK.
- if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
+ if (hasUserSetupCompleted(ensureSetUpUser)) {
// DO can be setup only if there are no users which are neither created by default
// nor marked as FOR_TESTING
@@ -16681,11 +16720,12 @@
return STATUS_OK;
} else {
// DO has to be user 0
- if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
+ if ((!isHeadlessSystemUserMode || isHeadlessModeAffiliated)
+ && deviceOwnerUserId != UserHandle.USER_SYSTEM) {
return STATUS_NOT_SYSTEM_USER;
}
// Only provision DO before setup wizard completes
- if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
+ if (hasUserSetupCompleted(ensureSetUpUser)) {
return STATUS_USER_SETUP_COMPLETED;
}
return STATUS_OK;
@@ -21260,7 +21300,11 @@
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
- int deviceOwnerUserId = UserHandle.USER_SYSTEM;
+ int deviceOwnerUserId = headlessDeviceOwnerSingleUserEnabled()
+ && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER
+ ? mUserManagerInternal.getMainUserId()
+ : UserHandle.USER_SYSTEM;
+
if (!removeNonRequiredAppsForManagedDevice(
deviceOwnerUserId,
provisioningParams.isLeaveAllSystemAppsEnabled(),