Merge "Implementation of OEM user switch request via User Hal" into rvc-dev
diff --git a/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags b/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
index 60a174d..fb368e6 100644
--- a/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
+++ b/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
@@ -75,6 +75,7 @@
 150109 car_user_svc_get_user_auth_req (uid|1),(user_id|1),(number_types|1)
 150110 car_user_svc_get_user_auth_resp (number_values|1)
 150111 car_user_svc_switch_user_ui_req (user_id|1)
+150112 car_user_svc_switch_user_from_hal_req (request_id|1),(uid|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)
@@ -86,6 +87,7 @@
 150147 car_user_hal_legacy_switch_user_req (request_id|1),(target_user_id|1),(current_user_id|1)
 150148 car_user_hal_set_user_auth_req (int32values|4)
 150149 car_user_hal_set_user_auth_resp (int32values|4),(error_message|3)
+150150 car_user_hal_oem_switch_user_req (request_id|1),(target_user_id|1)
 
 150171 car_user_mgr_add_listener (uid|1)
 150172 car_user_mgr_remove_listener (uid|1)
diff --git a/service/src/com/android/car/hal/UserHalService.java b/service/src/com/android/car/hal/UserHalService.java
index b6e04a8..ce43be4 100644
--- a/service/src/com/android/car/hal/UserHalService.java
+++ b/service/src/com/android/car/hal/UserHalService.java
@@ -53,6 +53,8 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
+import com.android.car.CarLocalServices;
+import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.car.EventLogTags;
@@ -95,7 +97,7 @@
     // This handler handles 2 types of messages:
     // - "Anonymous" messages (what=0) containing runnables.
     // - "Identifiable" messages used to check for timeouts (whose 'what' is the request id).
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Handler mHandler;
 
     /**
      * Value used on the next request.
@@ -110,7 +112,13 @@
     private final SparseArray<PendingRequest<?, ?>> mPendingRequests = new SparseArray<>();
 
     public UserHalService(VehicleHal hal) {
+        this(hal, new Handler(Looper.getMainLooper()));
+    }
+
+    @VisibleForTesting
+    UserHalService(VehicleHal hal, Handler handler) {
         mHal = hal;
+        mHandler = handler;
     }
 
     @Override
@@ -666,6 +674,52 @@
 
     private void handleOnSwitchUserResponse(VehiclePropValue value) {
         int requestId = value.value.int32Values.get(0);
+        int messageType = value.value.int32Values.get(1);
+
+        if (messageType == SwitchUserMessageType.VEHICLE_RESPONSE) {
+            handleVehicleResponse(value);
+            return;
+        }
+
+        if (messageType == SwitchUserMessageType.VEHICLE_REQUEST) {
+            handleVehicleRequest(value);
+            return;
+        }
+
+        Log.e(TAG, "handleOnSwitchUserResponse invalid message type (" + messageType
+                + ") from HAL: " + value);
+
+        // check if a callback exists for the request ID
+        HalCallback<SwitchUserResponse> callback =
+                handleGetPendingCallback(requestId, SwitchUserResponse.class);
+        if (callback != null) {
+            handleRemovePendingRequest(requestId);
+            EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_RESP, requestId,
+                    HalCallback.STATUS_WRONG_HAL_RESPONSE, SwitchUserStatus.FAILURE);
+            callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
+            return;
+        }
+    }
+
+    private void handleVehicleRequest(VehiclePropValue value) {
+        int requestId = value.value.int32Values.get(0);
+        // Index 1 is message type, which is not required in this call.
+        int targetUserId = value.value.int32Values.get(2);
+        EventLog.writeEvent(EventLogTags.CAR_USER_HAL_OEM_SWITCH_USER_REQ, requestId, targetUserId);
+
+        // HAL vehicle request should have negative request ID
+        if (requestId >= 0) {
+            Log.e(TAG, "handleVehicleRequest invalid requestId (" + requestId + ") from HAL: "
+                    + value);
+            return;
+        }
+
+        CarUserService userService = CarLocalServices.getService(CarUserService.class);
+        userService.switchAndroidUserFromHal(requestId, targetUserId);
+    }
+
+    private void handleVehicleResponse(VehiclePropValue value) {
+        int requestId = value.value.int32Values.get(0);
         HalCallback<SwitchUserResponse> callback =
                 handleGetPendingCallback(requestId, SwitchUserResponse.class);
         if (callback == null) {
@@ -678,13 +732,6 @@
         SwitchUserResponse response = new SwitchUserResponse();
         response.requestId = requestId;
         response.messageType = value.value.int32Values.get(1);
-        if (response.messageType != SwitchUserMessageType.VEHICLE_RESPONSE) {
-            EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_RESP, requestId,
-                    HalCallback.STATUS_WRONG_HAL_RESPONSE);
-            Log.e(TAG, "invalid message type (" + response.messageType + ") from HAL: " + value);
-            callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
-            return;
-        }
         response.status = value.value.int32Values.get(2);
         if (response.status == SwitchUserStatus.SUCCESS
                 || response.status == SwitchUserStatus.FAILURE) {
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 2ae56cb..158f4a0 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -931,6 +931,49 @@
         receiver.complete(new UserSwitchResult(status, errorMessage));
     }
 
+    /**
+     * Calls activity manager for user switch.
+     *
+     * <p><b>NOTE</b> This method is meant to be called just by UserHalService.
+     *
+     * @param requestId for the user switch request
+     * @param targetUserId of the target user
+     *
+     * @hide
+     */
+    public void switchAndroidUserFromHal(int requestId, @UserIdInt int targetUserId) {
+        EventLog.writeEvent(EventLogTags.CAR_USER_SVC_SWITCH_USER_FROM_HAL_REQ, requestId,
+                targetUserId);
+        Log.i(TAG_USER, "User hal requested a user switch. Target user id " + targetUserId);
+
+        try {
+            boolean result = mAm.switchUser(targetUserId);
+            if (result) {
+                updateUserSwitchInProcess(requestId, targetUserId);
+            } else {
+                postSwitchHalResponse(requestId, targetUserId);
+            }
+        } catch (RemoteException e) {
+            // ignore
+            Log.w(TAG_USER, "error while switching user " + targetUserId, e);
+        }
+    }
+
+    private void updateUserSwitchInProcess(int requestId, @UserIdInt int targetUserId) {
+        synchronized (mLockUser) {
+            if (mUserIdForUserSwitchInProcess != UserHandle.USER_NULL) {
+                // Some other user switch is in process.
+                if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+                    Log.d(TAG_USER, "User switch for user: " + mUserIdForUserSwitchInProcess
+                            + " is in process. Abandoning it as a new user switch is requested"
+                            + " for the target user: " + targetUserId);
+                }
+            }
+            mUserIdForUserSwitchInProcess = targetUserId;
+            mRequestIdForUserSwitchInProcess = requestId;
+        }
+    }
+
     private void postSwitchHalResponse(int requestId, @UserIdInt int targetUserId) {
         UserInfo targetUser = mUserManager.getUserInfo(targetUserId);
         UsersInfo usersInfo = getUsersInfo();
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
index 033bb9a..67a9b6f 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
@@ -33,9 +33,11 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -60,12 +62,18 @@
 import android.hardware.automotive.vehicle.V2_0.UsersInfo;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.car.CarLocalServices;
+import com.android.car.user.CarUserService;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -116,6 +124,10 @@
 
     @Mock
     private VehicleHal mVehicleHal;
+    @Mock
+    private CarUserService mCarUserService;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
 
     private final UserInfo mUser0 = new UserInfo();
     private final UserInfo mUser10 = new UserInfo();
@@ -127,7 +139,7 @@
 
     @Before
     public void setFixtures() {
-        mUserHalService = spy(new UserHalService(mVehicleHal));
+        mUserHalService = spy(new UserHalService(mVehicleHal, mHandler));
         // Needs at least one property, otherwise isSupported() will return false
         mUserHalService.takeProperties(Arrays.asList(newSubscribableConfig(INITIAL_USER_INFO)));
 
@@ -141,6 +153,13 @@
         mUsersInfo.existingUsers = new ArrayList<>(2);
         mUsersInfo.existingUsers.add(mUser0);
         mUsersInfo.existingUsers.add(mUser10);
+
+        CarLocalServices.addService(CarUserService.class, mCarUserService);
+    }
+
+    @After
+    public void clearFixtures() {
+        CarLocalServices.removeServiceForTest(CarUserService.class);
     }
 
     @Test
@@ -464,7 +483,7 @@
     @Test
     public void testSwitchUser_halReturnedInvalidMessageType() throws Exception {
         VehiclePropValue propResponse = UserHalHelper.createPropRequest(SWITCH_USER,
-                    REQUEST_ID_PLACE_HOLDER, SwitchUserMessageType.VEHICLE_REQUEST);
+                REQUEST_ID_PLACE_HOLDER, SwitchUserMessageType.LEGACY_ANDROID_SWITCH);
         propResponse.value.int32Values.add(SwitchUserStatus.SUCCESS);
 
         AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
@@ -576,6 +595,35 @@
     }
 
     @Test
+    public void testUserSwitch_OEMRequest_success() throws Exception {
+        int requestId = -4;
+        int targetUserId = 11;
+        VehiclePropValue propResponse = UserHalHelper.createPropRequest(SWITCH_USER,
+                requestId, SwitchUserMessageType.VEHICLE_REQUEST);
+
+        propResponse.value.int32Values.add(targetUserId);
+
+        mUserHalService.onHalEvents(Arrays.asList(propResponse));
+        waitForHandler();
+
+        verify(mCarUserService).switchAndroidUserFromHal(requestId, targetUserId);
+    }
+
+    @Test
+    public void testUserSwitch_OEMRequest_failure_positiveRequestId() throws Exception {
+        int requestId = 4;
+        int targetUserId = 11;
+        VehiclePropValue propResponse = UserHalHelper.createPropRequest(SWITCH_USER,
+                requestId, SwitchUserMessageType.VEHICLE_REQUEST);
+        propResponse.value.int32Values.add(targetUserId);
+
+        mUserHalService.onHalEvents(Arrays.asList(propResponse));
+        waitForHandler();
+
+        verify(mCarUserService, never()).switchAndroidUserFromHal(anyInt(), anyInt());
+    }
+
+    @Test
     public void testPostSwitchResponse_noUsersInfo() {
         assertThrows(NullPointerException.class,
                 () -> mUserHalService.postSwitchResponse(42, mUser10, null));
@@ -1052,6 +1100,13 @@
         return request;
     }
 
+    /**
+     * Run empty runnable to make sure that all posted handlers are done.
+     */
+    private void waitForHandler() {
+        mHandler.runWithScissors(() -> { }, /* Default timeout */ CALLBACK_TIMEOUT_TIMEOUT);
+    }
+
     private void mockNextRequestId(int requestId) {
         doReturn(requestId).when(mUserHalService).getNextRequestId();
     }
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 f52e48d..bea777c 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
@@ -953,6 +953,30 @@
     }
 
     @Test
+    public void testSwitchUser_OEMRequest_success() throws Exception {
+        mockExistingUsersAndCurrentUser(mAdminUser);
+        mockAmSwitchUser(mRegularUser, true);
+        int requestId = -1;
+
+        mCarUserService.switchAndroidUserFromHal(requestId, mRegularUser.id);
+        mockCurrentUser(mRegularUser);
+        sendUserUnlockedEvent(mRegularUser.id);
+
+        assertPostSwitch(requestId, mRegularUser.id, mRegularUser.id);
+    }
+
+    @Test
+    public void testSwitchUser_OEMRequest_failure() throws Exception {
+        mockExistingUsersAndCurrentUser(mAdminUser);
+        mockAmSwitchUser(mRegularUser, false);
+        int requestId = -1;
+
+        mCarUserService.switchAndroidUserFromHal(requestId, mRegularUser.id);
+
+        assertPostSwitch(requestId, mAdminUser.id, mRegularUser.id);
+    }
+
+    @Test
     public void testGetUserInfo_nullReceiver() throws Exception {
         assertThrows(NullPointerException.class, () -> mCarUserService
                 .getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs, null));