Add a new API method: CarDevicePolicyManager#startUserInBackground
as a TestApi.
Test: atest CarDevicePolicyManagerTest CarDevicePolicyManagerUnitTest \
CarDevicePolicyManagerPermissionTest CarDevicePolicyServiceTest \
CarUserServiceTest StartUserInBackgroundResultTest
Bug: 181331178
Change-Id: Ifcb231d83e4e8cd4cb1e713e3b34b6161c0b5c16
diff --git a/car-lib/api/test-current.txt b/car-lib/api/test-current.txt
index b82b7b7..3be98c4 100644
--- a/car-lib/api/test-current.txt
+++ b/car-lib/api/test-current.txt
@@ -31,6 +31,7 @@
public final class CarDevicePolicyManager {
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.car.admin.CreateUserResult createUser(@Nullable String, int);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.car.admin.RemoveUserResult removeUser(@NonNull android.os.UserHandle);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.car.admin.StartUserInBackgroundResult startUserInBackground(@NonNull android.os.UserHandle);
field public static final int USER_TYPE_ADMIN = 1; // 0x1
field public static final int USER_TYPE_GUEST = 2; // 0x2
field public static final int USER_TYPE_REGULAR = 0; // 0x0
@@ -57,6 +58,15 @@
field public static final int STATUS_SUCCESS_SET_EPHEMERAL = 3; // 0x3
}
+ public final class StartUserInBackgroundResult {
+ method public int getStatus();
+ method public boolean isSuccess();
+ field public static final int STATUS_FAILURE_GENERIC = 100; // 0x64
+ field public static final int STATUS_FAILURE_USER_DOES_NOT_EXIST = 3; // 0x3
+ field public static final int STATUS_SUCCESS = 1; // 0x1
+ field public static final int STATUS_SUCCESS_CURRENT_USER = 2; // 0x2
+ }
+
}
package android.car.content.pm {
diff --git a/car-lib/src/android/car/admin/CarDevicePolicyManager.java b/car-lib/src/android/car/admin/CarDevicePolicyManager.java
index 4ed2833..e60d60d 100644
--- a/car-lib/src/android/car/admin/CarDevicePolicyManager.java
+++ b/car-lib/src/android/car/admin/CarDevicePolicyManager.java
@@ -28,6 +28,7 @@
import android.car.CarManagerBase;
import android.car.user.UserCreationResult;
import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -203,6 +204,45 @@
}
}
+ /**
+ * Starts a user in the background.
+ *
+ * @param user identification of the user to be started.
+ *
+ * @return whether the user was successfully started.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS})
+ @NonNull
+ public StartUserInBackgroundResult startUserInBackground(@NonNull UserHandle user) {
+ Objects.requireNonNull(user, "user cannot be null");
+
+ int userId = user.getIdentifier();
+ int uid = myUid();
+ EventLog.writeEvent(EventLogTags.CAR_DP_MGR_START_USER_IN_BACKGROUND_REQ, uid, userId);
+ int status = StartUserInBackgroundResult.STATUS_FAILURE_GENERIC;
+ try {
+ AndroidFuture<UserStartResult> future = new AndroidFuture<>();
+ mService.startUserInBackground(userId, future);
+ UserStartResult result = future.get(DEVICE_POLICY_MANAGER_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+ status = result.getStatus();
+ return new StartUserInBackgroundResult(status);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return new StartUserInBackgroundResult(status);
+ } catch (ExecutionException | TimeoutException e) {
+ return new StartUserInBackgroundResult(status);
+ } catch (RemoteException e) {
+ return handleRemoteExceptionFromCarService(e, new StartUserInBackgroundResult(status));
+ } finally {
+ EventLog.writeEvent(EventLogTags.CAR_DP_MGR_START_USER_IN_BACKGROUND_RESP, uid, status);
+ }
+ }
+
/** @hide */
@Override
public void onCarDisconnected() {
diff --git a/car-lib/src/android/car/admin/StartUserInBackgroundResult.java b/car-lib/src/android/car/admin/StartUserInBackgroundResult.java
new file mode 100644
index 0000000..11d276e
--- /dev/null
+++ b/car-lib/src/android/car/admin/StartUserInBackgroundResult.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.admin;
+
+import android.annotation.IntDef;
+import android.annotation.TestApi;
+import android.car.user.UserStartResult;
+import android.util.DebugUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result of a {@link CarDevicePolicyManager#startUserInBackground operation.
+ *
+ * @hide
+ */
+@TestApi
+public final class StartUserInBackgroundResult {
+
+ /**
+ * User was started.
+ */
+ public static final int STATUS_SUCCESS = 1;
+
+ /**
+ * User was the current user.
+ */
+ public static final int STATUS_SUCCESS_CURRENT_USER = 2;
+
+ /**
+ * User was not started because it does not exist.
+ */
+ public static final int STATUS_FAILURE_USER_DOES_NOT_EXIST = 3;
+
+ /**
+ * User was not started for some other reason not described above.
+ */
+ public static final int STATUS_FAILURE_GENERIC = 100;
+
+ /** @hide */
+ @IntDef(prefix = "STATUS_", value = {
+ STATUS_SUCCESS,
+ STATUS_SUCCESS_CURRENT_USER,
+ STATUS_FAILURE_USER_DOES_NOT_EXIST,
+ STATUS_FAILURE_GENERIC
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status {
+ }
+
+ private final @Status int mStatus;
+
+ /** @hide */
+ @VisibleForTesting
+ public StartUserInBackgroundResult(@UserStartResult.Status int status) {
+ switch(status) {
+ case UserStartResult.STATUS_SUCCESSFUL:
+ mStatus = STATUS_SUCCESS;
+ break;
+ case UserStartResult.STATUS_SUCCESSFUL_USER_IS_CURRENT_USER:
+ mStatus = STATUS_SUCCESS_CURRENT_USER;
+ break;
+ case UserStartResult.STATUS_USER_DOES_NOT_EXIST:
+ mStatus = STATUS_FAILURE_USER_DOES_NOT_EXIST;
+ break;
+ default:
+ mStatus = STATUS_FAILURE_GENERIC;
+ }
+ }
+
+ /**
+ * Gets the specific result of the operation.
+ *
+ * @return either {@link StartUserInBackgroundResult#STATUS_SUCCESS},
+ * {@link StartUserInBackgroundResult#STATUS_SUCCESS_CURRENT_USER},
+ * {@link StartUserInBackgroundResult#STATUS_FAILURE_USER_DOES_NOT_EXIST}, or
+ * {@link StartUserInBackgroundResult#STATUS_FAILURE_GENERIC}.
+ */
+ public @Status int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Gets whether the operation was successful or not.
+ */
+ public boolean isSuccess() {
+ return mStatus == STATUS_SUCCESS || mStatus == STATUS_SUCCESS_CURRENT_USER;
+ }
+
+ @Override
+ public String toString() {
+ return "StartUserInBackgroundResult[" + statusToString(mStatus) + "]";
+ }
+
+ private static String statusToString(int status) {
+ return DebugUtils.valueToString(StartUserInBackgroundResult.class, "STATUS_", status);
+ }
+}
diff --git a/car-lib/src/com/android/car/internal/common/EventLogTags.logtags b/car-lib/src/com/android/car/internal/common/EventLogTags.logtags
index 31ed2b1..f781285 100644
--- a/car-lib/src/com/android/car/internal/common/EventLogTags.logtags
+++ b/car-lib/src/com/android/car/internal/common/EventLogTags.logtags
@@ -90,6 +90,8 @@
150122 car_user_svc_notify_internal_lifecycle_listener (listener_name|3),(event_type|1),(from_user_id|1),(to_user_id|1)
150123 car_user_svc_pre_creation_requested (number_users|1),(number_guests|1)
150124 car_user_svc_pre_creation_status (number_existing_users|1),(number_users_to_add|1),(number_users_to_remove|1),(number_existing_guests|1),(number_guests_to_add|1),(number_guests_to_remove|1),(number_invalid_users_to_remove|1)
+150125 car_user_svc_start_user_in_background_req (user_id|1)
+150126 car_user_svc_start_user_in_background_resp (user_id|1),(result|1)
150140 car_user_hal_initial_user_info_req (request_id|1),(request_type|1),(timeout|1)
150141 car_user_hal_initial_user_info_resp (request_id|1),(status|1),(action|1),(user_id|1),(flags|1),(safe_name|3),(user_locales|3)
@@ -127,3 +129,5 @@
150201 car_dp_mgr_remove_user_resp (uid|1),(status|1)
150202 car_dp_mgr_create_user_req (uid|1),(safe_name|3),(flags|1)
150203 car_dp_mgr_create_user_resp (uid|1),(status|1)
+150204 car_dp_mgr_start_user_in_background_req (uid|1),(user_id|1)
+150205 car_dp_mgr_start_user_in_background_resp (uid|1),(status|1)
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 5439236..4e24b56 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -1850,6 +1850,9 @@
*/
public void startUserInBackground(@UserIdInt int userId,
@NonNull AndroidFuture<UserStartResult> receiver) {
+ checkManageOrCreateUsersPermission("startUserInBackground");
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_START_USER_IN_BACKGROUND_REQ, userId);
+
mHandler.post(() -> handleStartUserInBackground(userId, receiver));
}
@@ -1857,20 +1860,21 @@
@NonNull AndroidFuture<UserStartResult> receiver) {
// If the requested user is the current user, do nothing and return success.
if (ActivityManager.getCurrentUser() == userId) {
- sendUserStartResult(UserStartResult.STATUS_SUCCESSFUL_USER_IS_CURRENT_USER, receiver);
+ sendUserStartResult(
+ userId, UserStartResult.STATUS_SUCCESSFUL_USER_IS_CURRENT_USER, receiver);
return;
}
// If requested user does not exist, return error.
if (mUserManager.getUserInfo(userId) == null) {
Slogf.w(TAG, "User %d does not exist", userId);
- sendUserStartResult(UserStartResult.STATUS_USER_DOES_NOT_EXIST, receiver);
+ sendUserStartResult(userId, UserStartResult.STATUS_USER_DOES_NOT_EXIST, receiver);
return;
}
try {
if (!mAm.startUserInBackground(userId)) {
Slogf.w(TAG, "Failed to start user %d in background", userId);
- sendUserStartResult(UserStartResult.STATUS_ANDROID_FAILURE, receiver);
+ sendUserStartResult(userId, UserStartResult.STATUS_ANDROID_FAILURE, receiver);
return;
}
} catch (RemoteException e) {
@@ -1880,12 +1884,13 @@
// TODO(b/181331178): We are not updating mBackgroundUsersToRestart or
// mBackgroundUsersRestartedHere, which were only used for the garage mode. Consider
// renaming them to make it more clear.
- sendUserStartResult(UserStartResult.STATUS_SUCCESSFUL, receiver);
+ sendUserStartResult(userId, UserStartResult.STATUS_SUCCESSFUL, receiver);
}
- private void sendUserStartResult(@UserStartResult.Status int result,
+ private void sendUserStartResult(@UserIdInt int userId, @UserStartResult.Status int result,
@NonNull AndroidFuture<UserStartResult> receiver) {
- // TODO(b/181331178): Add event log calls.
+ EventLog.writeEvent(
+ EventLogTags.CAR_USER_SVC_START_USER_IN_BACKGROUND_RESP, userId, result);
receiver.complete(new UserStartResult(result));
}
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/admin/CarDevicePolicyManagerPermissionTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/admin/CarDevicePolicyManagerPermissionTest.java
index 51c1ea3..f1d4b52 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/admin/CarDevicePolicyManagerPermissionTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/admin/CarDevicePolicyManagerPermissionTest.java
@@ -74,4 +74,11 @@
assertThat(e.getMessage()).contains(CREATE_USERS);
assertThat(e.getMessage()).contains(MANAGE_USERS);
}
+ @Test
+ public void testStartUserInBackgrounPermission() throws Exception {
+ Exception e = expectThrows(SecurityException.class,
+ () -> mManager.startUserInBackground(UserHandle.of(100)));
+ assertThat(e.getMessage()).contains(CREATE_USERS);
+ assertThat(e.getMessage()).contains(MANAGE_USERS);
+ }
}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java
index b3973aa..8ce4c27 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java
@@ -27,6 +27,7 @@
import android.car.admin.CarDevicePolicyManager;
import android.car.admin.CreateUserResult;
import android.car.admin.RemoveUserResult;
+import android.car.admin.StartUserInBackgroundResult;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.PowerManager;
@@ -63,11 +64,10 @@
UserInfo user = createUser();
Log.d(TAG, "removing user " + user.toFullString());
-
RemoveUserResult result = mCarDpm.removeUser(user.getUserHandle());
Log.d(TAG, "result: " + result);
- assertWithMessage("Failed to remove user%s: %s", user.toFullString(), result)
+ assertWithMessage("Result of removeUser %s: %s", user.toFullString(), result)
.that(result.isSuccess()).isTrue();
}
@@ -122,7 +122,6 @@
String name = "CarDevicePolicyManagerTest.testCreateUser";
int type = CarDevicePolicyManager.USER_TYPE_REGULAR;
Log.d(TAG, "creating new user with name " + name + " and type " + type);
-
CreateUserResult result = mCarDpm.createUser(name, type);
Log.d(TAG, "result: " + result);
UserHandle user = result.getUserHandle();
@@ -138,6 +137,25 @@
}
@Test
+ public void testStartUserInBackground() throws Exception {
+ assertInitialUserIsAdmin();
+
+ UserInfo user = createUser();
+ Log.d(TAG, "starting user in background " + user.toFullString());
+ StartUserInBackgroundResult result = mCarDpm.startUserInBackground(user.getUserHandle());
+ Log.d(TAG, "result: " + result);
+
+ try {
+ assertWithMessage("Result of startUserInBackground %s: %s", user.toFullString(), result)
+ .that(result.isSuccess()).isTrue();
+ } finally {
+ // Clean up the created user.
+ removeUser(user.id);
+ waitForUserRemoval(user.id);
+ }
+ }
+
+ @Test
public void testLockNow_safe() throws Exception {
lockNowTest(/* safe= */ true);
}
diff --git a/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java b/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java
index 19ffc0b..230dda9 100644
--- a/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java
@@ -32,6 +32,7 @@
import android.car.test.util.UserTestingHelper.UserInfoBuilder;
import android.car.user.UserCreationResult;
import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
import android.content.pm.UserInfo;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -125,6 +126,34 @@
assertThrows(SecurityException.class, () -> mMgr.createUser("TheDude", 100));
}
+ @Test
+ public void testStartUserInBackground_success() throws Exception {
+ mockStartUserInBackground(100, UserStartResult.STATUS_SUCCESSFUL);
+
+ StartUserInBackgroundResult result = mMgr.startUserInBackground(UserHandle.of(100));
+
+ assertThat(result.isSuccess()).isTrue();
+ assertThat(result.getStatus()).isEqualTo(StartUserInBackgroundResult.STATUS_SUCCESS);
+ }
+
+ @Test
+ public void testStartUserInBackground_remoteException() throws Exception {
+ doThrow(new RemoteException("D'OH!"))
+ .when(mService).startUserInBackground(eq(100), notNull());
+ mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
+
+ StartUserInBackgroundResult result = mMgr.startUserInBackground(UserHandle.of(100));
+
+ assertThat(result.isSuccess()).isFalse();
+ assertThat(result.getStatus())
+ .isEqualTo(StartUserInBackgroundResult.STATUS_FAILURE_GENERIC);
+ }
+
+ @Test
+ public void testStartUserInBackground_nullUser() {
+ assertThrows(NullPointerException.class, () -> mMgr.startUserInBackground(null));
+ }
+
private void mockRemoveUser(@UserIdInt int userId, int status) throws Exception {
doAnswer((invocation) -> {
@SuppressWarnings("unchecked")
@@ -144,4 +173,14 @@
return null;
}).when(mService).createUser(eq(name), eq(user.id), notNull());
}
+
+ private void mockStartUserInBackground(@UserIdInt int userId, int status) throws Exception {
+ doAnswer((invocation) -> {
+ @SuppressWarnings("unchecked")
+ AndroidFuture<UserStartResult> future =
+ (AndroidFuture<UserStartResult>) invocation.getArguments()[1];
+ future.complete(new UserStartResult(status));
+ return null;
+ }).when(mService).startUserInBackground(eq(userId), notNull());
+ }
}
diff --git a/tests/carservice_unit_test/src/android/car/admin/StartUserInBackgroundResultTest.java b/tests/carservice_unit_test/src/android/car/admin/StartUserInBackgroundResultTest.java
new file mode 100644
index 0000000..595687b
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/admin/StartUserInBackgroundResultTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.admin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.user.UserStartResult;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public final class StartUserInBackgroundResultTest {
+ private final int mUserStartStatus;
+ private final int mStartUserStatus;
+ private final boolean mIsSuccess;
+
+ public StartUserInBackgroundResultTest(
+ int userStartStatus, int startUserStatus, boolean isSuccess) {
+ mUserStartStatus = userStartStatus;
+ mStartUserStatus = startUserStatus;
+ mIsSuccess = isSuccess;
+ }
+
+ @Parameterized.Parameters
+ public static Collection provideParams() {
+ return Arrays.asList(
+ new Object[][] {
+ {UserStartResult.STATUS_SUCCESSFUL,
+ StartUserInBackgroundResult.STATUS_SUCCESS, true},
+ {UserStartResult.STATUS_SUCCESSFUL_USER_IS_CURRENT_USER,
+ StartUserInBackgroundResult.STATUS_SUCCESS_CURRENT_USER, true},
+ {UserStartResult.STATUS_ANDROID_FAILURE,
+ StartUserInBackgroundResult.STATUS_FAILURE_GENERIC, false},
+ {UserStartResult.STATUS_USER_DOES_NOT_EXIST,
+ StartUserInBackgroundResult.STATUS_FAILURE_USER_DOES_NOT_EXIST,
+ false}
+ });
+ }
+
+ @Test
+ public void testStatus() {
+ StartUserInBackgroundResult result = new StartUserInBackgroundResult(mUserStartStatus);
+
+ assertThat(result.getStatus()).isEqualTo(mStartUserStatus);
+ assertThat(result.isSuccess()).isEqualTo(mIsSuccess);
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index 4620c81..fcdc6ef 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -1861,6 +1861,17 @@
}
@Test
+ public void testStartUserInBackground_permissionDenied() throws Exception {
+ int userId = 101;
+ mockManageUsersPermission(android.Manifest.permission.MANAGE_USERS, false);
+ mockManageUsersPermission(android.Manifest.permission.CREATE_USERS, false);
+
+ AndroidFuture<UserStartResult> userStartResult = new AndroidFuture<>();
+ assertThrows(SecurityException.class,
+ () -> mCarUserService.startUserInBackground(userId, userStartResult));
+ }
+
+ @Test
public void testStartUserInBackground_fail() throws Exception {
int userId = 101;
UserInfo userInfo = new UserInfo(userId, "user1", NO_USER_INFO_FLAGS);