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