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);