createAndManageUser should communicate reason of failure to caller
Bug: 71844474
Test: cts-tradefed run singleCommand cts -m CtsDevicePolicyManagerTestCases --test com.android.cts.devicepolicy.DeviceOwnerTest#testCreateAndManageUser_LowStorage
Test: cts-tradefed run singleCommand cts -m CtsDevicePolicyManagerTestCases --test com.android.cts.devicepolicy.DeviceOwnerTest#testCreateAndManageUser_MaxUsers
Change-Id: I3c069ba86822178fa3f51f1d31cd4792883151cc
diff --git a/api/current.txt b/api/current.txt
index 1c97c66..cb3b91b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6702,11 +6702,6 @@
field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
- field public static final int USER_OPERATION_ERROR_CURRENT_USER = 4; // 0x4
- field public static final int USER_OPERATION_ERROR_MANAGED_PROFILE = 2; // 0x2
- field public static final int USER_OPERATION_ERROR_MAX_RUNNING_USERS = 3; // 0x3
- field public static final int USER_OPERATION_ERROR_UNKNOWN = 1; // 0x1
- field public static final int USER_OPERATION_SUCCESS = 0; // 0x0
field public static final int WIPE_EUICC = 4; // 0x4
field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
@@ -33165,6 +33160,17 @@
field public static final java.lang.String KEY_RESTRICTIONS_PENDING = "restrictions_pending";
field public static final int USER_CREATION_FAILED_NOT_PERMITTED = 1; // 0x1
field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
+ field public static final int USER_OPERATION_ERROR_CURRENT_USER = 4; // 0x4
+ field public static final int USER_OPERATION_ERROR_LOW_STORAGE = 5; // 0x5
+ field public static final int USER_OPERATION_ERROR_MANAGED_PROFILE = 2; // 0x2
+ field public static final int USER_OPERATION_ERROR_MAX_RUNNING_USERS = 3; // 0x3
+ field public static final int USER_OPERATION_ERROR_MAX_USERS = 6; // 0x6
+ field public static final int USER_OPERATION_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int USER_OPERATION_SUCCESS = 0; // 0x0
+ }
+
+ public static class UserManager.UserOperationException extends java.lang.RuntimeException {
+ method public int getUserOperationResult();
}
public abstract class VibrationEffect implements android.os.Parcelable {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 16e36bc..0b9471d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -56,8 +56,11 @@
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManager.UserOperationException;
+import android.os.UserManager.UserOperationResult;
import android.provider.ContactsContract.Directory;
import android.provider.Settings;
import android.security.AttestedKeyPair;
@@ -6549,6 +6552,9 @@
* <p>
* If the adminExtras are not null, they will be stored on the device until the user is started
* for the first time. Then the extras will be passed to the admin when onEnable is called.
+ * <p>From {@link android.os.Build.VERSION_CODES#P} onwards, if targeting
+ * {@link android.os.Build.VERSION_CODES#P}, throws {@link UserOperationException} instead of
+ * returning {@code null} on failure.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param name The user's name.
@@ -6563,6 +6569,9 @@
* @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 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}.
*/
public @Nullable UserHandle createAndManageUser(@NonNull ComponentName admin,
@NonNull String name,
@@ -6571,6 +6580,8 @@
throwIfParentInstance("createAndManageUser");
try {
return mService.createAndManageUser(admin, name, profileOwner, adminExtras, flags);
+ } catch (ServiceSpecificException e) {
+ throw new UserOperationException(e.getMessage(), e.errorCode);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -6614,77 +6625,15 @@
}
/**
- * Indicates user operation is successful.
- *
- * @see #startUserInBackground(ComponentName, UserHandle)
- * @see #stopUser(ComponentName, UserHandle)
- * @see #logoutUser(ComponentName)
- */
- public static final int USER_OPERATION_SUCCESS = 0;
-
- /**
- * Indicates user operation failed for unknown reason.
- *
- * @see #startUserInBackground(ComponentName, UserHandle)
- * @see #stopUser(ComponentName, UserHandle)
- * @see #logoutUser(ComponentName)
- */
- public static final int USER_OPERATION_ERROR_UNKNOWN = 1;
-
- /**
- * Indicates user operation failed because target user is a managed profile.
- *
- * @see #startUserInBackground(ComponentName, UserHandle)
- * @see #stopUser(ComponentName, UserHandle)
- * @see #logoutUser(ComponentName)
- */
- public static final int USER_OPERATION_ERROR_MANAGED_PROFILE = 2;
-
- /**
- * Indicates user operation failed because maximum running user limit has reached.
- *
- * @see #startUserInBackground(ComponentName, UserHandle)
- */
- public static final int USER_OPERATION_ERROR_MAX_RUNNING_USERS = 3;
-
- /**
- * Indicates user operation failed because the target user is in foreground.
- *
- * @see #stopUser(ComponentName, UserHandle)
- * @see #logoutUser(ComponentName)
- */
- public static final int USER_OPERATION_ERROR_CURRENT_USER = 4;
-
- /**
- * Result returned from
- * <ul>
- * <li>{@link #startUserInBackground(ComponentName, UserHandle)}</li>
- * <li>{@link #stopUser(ComponentName, UserHandle)}</li>
- * <li>{@link #logoutUser(ComponentName)}</li>
- * </ul>
- *
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "USER_OPERATION_" }, value = {
- USER_OPERATION_SUCCESS,
- USER_OPERATION_ERROR_UNKNOWN,
- USER_OPERATION_ERROR_MANAGED_PROFILE,
- USER_OPERATION_ERROR_MAX_RUNNING_USERS,
- USER_OPERATION_ERROR_CURRENT_USER
- })
- public @interface UserOperationResult {}
-
- /**
* Called by a device owner to start the specified secondary user in background.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param userHandle the user to be started in background.
* @return one of the following result codes:
- * {@link #USER_OPERATION_ERROR_UNKNOWN},
- * {@link #USER_OPERATION_SUCCESS},
- * {@link #USER_OPERATION_ERROR_MANAGED_PROFILE},
- * {@link #USER_OPERATION_ERROR_MAX_RUNNING_USERS},
+ * {@link UserManager#USER_OPERATION_ERROR_UNKNOWN},
+ * {@link UserManager#USER_OPERATION_SUCCESS},
+ * {@link UserManager#USER_OPERATION_ERROR_MANAGED_PROFILE},
+ * {@link UserManager#USER_OPERATION_ERROR_MAX_RUNNING_USERS},
* @throws SecurityException if {@code admin} is not a device owner.
* @see #getSecondaryUsers(ComponentName)
*/
@@ -6704,10 +6653,10 @@
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param userHandle the user to be stopped.
* @return one of the following result codes:
- * {@link #USER_OPERATION_ERROR_UNKNOWN},
- * {@link #USER_OPERATION_SUCCESS},
- * {@link #USER_OPERATION_ERROR_MANAGED_PROFILE},
- * {@link #USER_OPERATION_ERROR_CURRENT_USER}
+ * {@link UserManager#USER_OPERATION_ERROR_UNKNOWN},
+ * {@link UserManager#USER_OPERATION_SUCCESS},
+ * {@link UserManager#USER_OPERATION_ERROR_MANAGED_PROFILE},
+ * {@link UserManager#USER_OPERATION_ERROR_CURRENT_USER}
* @throws SecurityException if {@code admin} is not a device owner.
* @see #getSecondaryUsers(ComponentName)
*/
@@ -6727,10 +6676,10 @@
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return one of the following result codes:
- * {@link #USER_OPERATION_ERROR_UNKNOWN},
- * {@link #USER_OPERATION_SUCCESS},
- * {@link #USER_OPERATION_ERROR_MANAGED_PROFILE},
- * {@link #USER_OPERATION_ERROR_CURRENT_USER}
+ * {@link UserManager#USER_OPERATION_ERROR_UNKNOWN},
+ * {@link UserManager#USER_OPERATION_SUCCESS},
+ * {@link UserManager#USER_OPERATION_ERROR_MANAGED_PROFILE},
+ * {@link UserManager#USER_OPERATION_ERROR_CURRENT_USER}
* @throws SecurityException if {@code admin} is not a profile owner affiliated with the device.
* @see #getSecondaryUsers(ComponentName)
*/
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 1856200..5cfbb85 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1039,6 +1039,85 @@
*/
public static final int USER_CREATION_FAILED_NO_MORE_USERS = Activity.RESULT_FIRST_USER + 1;
+ /**
+ * Indicates user operation is successful.
+ */
+ public static final int USER_OPERATION_SUCCESS = 0;
+
+ /**
+ * Indicates user operation failed for unknown reason.
+ */
+ public static final int USER_OPERATION_ERROR_UNKNOWN = 1;
+
+ /**
+ * Indicates user operation failed because target user is a managed profile.
+ */
+ public static final int USER_OPERATION_ERROR_MANAGED_PROFILE = 2;
+
+ /**
+ * Indicates user operation failed because maximum running user limit has been reached.
+ */
+ public static final int USER_OPERATION_ERROR_MAX_RUNNING_USERS = 3;
+
+ /**
+ * Indicates user operation failed because the target user is in the foreground.
+ */
+ public static final int USER_OPERATION_ERROR_CURRENT_USER = 4;
+
+ /**
+ * Indicates user operation failed because device has low data storage.
+ */
+ public static final int USER_OPERATION_ERROR_LOW_STORAGE = 5;
+
+ /**
+ * Indicates user operation failed because maximum user limit has been reached.
+ */
+ public static final int USER_OPERATION_ERROR_MAX_USERS = 6;
+
+ /**
+ * Result returned from various user operations.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "USER_OPERATION_" }, value = {
+ USER_OPERATION_SUCCESS,
+ USER_OPERATION_ERROR_UNKNOWN,
+ USER_OPERATION_ERROR_MANAGED_PROFILE,
+ USER_OPERATION_ERROR_MAX_RUNNING_USERS,
+ USER_OPERATION_ERROR_CURRENT_USER,
+ USER_OPERATION_ERROR_LOW_STORAGE,
+ USER_OPERATION_ERROR_MAX_USERS
+ })
+ public @interface UserOperationResult {}
+
+ /**
+ * Thrown to indicate user operation failed.
+ */
+ public static class UserOperationException extends RuntimeException {
+ private final @UserOperationResult int mUserOperationResult;
+
+ /**
+ * Constructs a UserOperationException with specific result code.
+ *
+ * @param message the detail message
+ * @param userOperationResult the result code
+ * @hide
+ */
+ public UserOperationException(String message,
+ @UserOperationResult int userOperationResult) {
+ super(message);
+ mUserOperationResult = userOperationResult;
+ }
+
+ /**
+ * Returns the operation result code.
+ */
+ public @UserOperationResult int getUserOperationResult() {
+ return mUserOperationResult;
+ }
+ }
+
/** @hide */
public static UserManager get(Context context) {
return (UserManager) context.getSystemService(Context.USER_SERVICE);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 29d5d54..e5d5065 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -160,6 +160,7 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -219,6 +220,7 @@
import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.UserRestrictionsUtils;
+import com.android.server.storage.DeviceStorageMonitorInternal;
import com.google.android.collect.Sets;
@@ -8873,13 +8875,40 @@
final boolean demo = (flags & DevicePolicyManager.MAKE_USER_DEMO) != 0
&& UserManager.isDeviceInDemoMode(mContext);
final boolean leaveAllSystemAppsEnabled = (flags & LEAVE_ALL_SYSTEM_APPS_ENABLED) != 0;
+ final int targetSdkVersion;
+
// Create user.
UserHandle user = null;
synchronized (this) {
getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ final int callingUid = mInjector.binderGetCallingUid();
final long id = mInjector.binderClearCallingIdentity();
try {
+ targetSdkVersion = mInjector.getPackageManagerInternal().getUidTargetSdkVersion(
+ callingUid);
+
+ // Return detail error code for checks inside
+ // UserManagerService.createUserInternalUnchecked.
+ DeviceStorageMonitorInternal deviceStorageMonitorInternal =
+ LocalServices.getService(DeviceStorageMonitorInternal.class);
+ if (deviceStorageMonitorInternal.isMemoryLow()) {
+ if (targetSdkVersion >= Build.VERSION_CODES.P) {
+ throw new ServiceSpecificException(
+ UserManager.USER_OPERATION_ERROR_LOW_STORAGE, "low device storage");
+ } else {
+ return null;
+ }
+ }
+ if (!mUserManager.canAddMoreUsers()) {
+ if (targetSdkVersion >= Build.VERSION_CODES.P) {
+ throw new ServiceSpecificException(
+ UserManager.USER_OPERATION_ERROR_MAX_USERS, "user limit reached");
+ } else {
+ return null;
+ }
+ }
+
int userInfoFlags = 0;
if (ephemeral) {
userInfoFlags |= UserInfo.FLAG_EPHEMERAL;
@@ -8903,7 +8932,12 @@
}
}
if (user == null) {
- return null;
+ if (targetSdkVersion >= Build.VERSION_CODES.P) {
+ throw new ServiceSpecificException(UserManager.USER_OPERATION_ERROR_UNKNOWN,
+ "failed to create user");
+ } else {
+ return null;
+ }
}
final int userHandle = user.getIdentifier();
@@ -8949,7 +8983,12 @@
return user;
} catch (Throwable re) {
mUserManager.removeUser(userHandle);
- return null;
+ if (targetSdkVersion >= Build.VERSION_CODES.P) {
+ throw new ServiceSpecificException(UserManager.USER_OPERATION_ERROR_UNKNOWN,
+ re.getMessage());
+ } else {
+ return null;
+ }
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -9030,24 +9069,24 @@
final int userId = userHandle.getIdentifier();
if (isManagedProfile(userId)) {
Log.w(LOG_TAG, "Managed profile cannot be started in background");
- return DevicePolicyManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
+ return UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
}
final long id = mInjector.binderClearCallingIdentity();
try {
if (!mInjector.getActivityManagerInternal().canStartMoreUsers()) {
Log.w(LOG_TAG, "Cannot start more users in background");
- return DevicePolicyManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS;
+ return UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS;
}
if (mInjector.getIActivityManager().startUserInBackground(userId)) {
- return DevicePolicyManager.USER_OPERATION_SUCCESS;
+ return UserManager.USER_OPERATION_SUCCESS;
} else {
- return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN;
+ return UserManager.USER_OPERATION_ERROR_UNKNOWN;
}
} catch (RemoteException e) {
// Same process, should not happen.
- return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN;
+ return UserManager.USER_OPERATION_ERROR_UNKNOWN;
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -9065,7 +9104,7 @@
final int userId = userHandle.getIdentifier();
if (isManagedProfile(userId)) {
Log.w(LOG_TAG, "Managed profile cannot be stopped");
- return DevicePolicyManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
+ return UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
}
return stopUserUnchecked(userId);
@@ -9086,7 +9125,7 @@
if (isManagedProfile(callingUserId)) {
Log.w(LOG_TAG, "Managed profile cannot be logout");
- return DevicePolicyManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
+ return UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
}
final long id = mInjector.binderClearCallingIdentity();
@@ -9094,11 +9133,11 @@
if (!mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM)) {
Log.w(LOG_TAG, "Failed to switch to primary user");
// This should never happen as target user is UserHandle.USER_SYSTEM
- return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN;
+ return UserManager.USER_OPERATION_ERROR_UNKNOWN;
}
} catch (RemoteException e) {
// Same process, should not happen.
- return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN;
+ return UserManager.USER_OPERATION_ERROR_UNKNOWN;
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -9111,15 +9150,15 @@
try {
switch (mInjector.getIActivityManager().stopUser(userId, true /*force*/, null)) {
case ActivityManager.USER_OP_SUCCESS:
- return DevicePolicyManager.USER_OPERATION_SUCCESS;
+ return UserManager.USER_OPERATION_SUCCESS;
case ActivityManager.USER_OP_IS_CURRENT:
- return DevicePolicyManager.USER_OPERATION_ERROR_CURRENT_USER;
+ return UserManager.USER_OPERATION_ERROR_CURRENT_USER;
default:
- return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN;
+ return UserManager.USER_OPERATION_ERROR_UNKNOWN;
}
} catch (RemoteException e) {
// Same process, should not happen.
- return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN;
+ return UserManager.USER_OPERATION_ERROR_UNKNOWN;
} finally {
mInjector.binderRestoreCallingIdentity(id);
}