blob: e20009daad691c3681d44c49872b8fa2edd06de3 [file] [log] [blame]
/*
* Copyright (C) 2018 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 com.android.internal.car;
import static android.car.test.util.UserTestingHelper.getDefaultUserType;
import static android.car.test.util.UserTestingHelper.newGuestUser;
import static android.car.test.util.UserTestingHelper.newSecondaryUser;
import static android.car.test.util.UserTestingHelper.UserInfoBuilder;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
import static org.mockito.AdditionalAnswers.answerVoid;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.car.test.mocks.SyncAnswer;
import android.car.userlib.CarUserManagerHelper;
import android.car.userlib.HalCallback;
import android.car.userlib.CommonConstants.CarUserServiceConstants;
import android.car.userlib.InitialUserSetter;
import android.car.userlib.UserHalHelper;
import android.car.watchdoglib.CarWatchdogDaemonHelper;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.hardware.automotive.vehicle.V2_0.UserFlags;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.sysprop.CarProperties;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.car.ExternalConstants.CarUserManagerConstants;
import com.android.internal.car.ExternalConstants.ICarConstants;
import com.android.internal.os.IResultReceiver;
import com.android.server.SystemService;
import com.android.server.SystemService.TargetUser;
import com.android.server.wm.CarLaunchParamsModifier;
import com.android.server.utils.TimingsTraceAndSlog;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.quality.Strictness;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* This class contains unit tests for the {@link CarServiceHelperService}.
*/
@RunWith(AndroidJUnit4.class)
public class CarHelperServiceTest extends AbstractExtendedMockitoTestCase {
private static final String TAG = CarHelperServiceTest.class.getSimpleName();
private static final int PRE_CREATED_USER_ID = 24;
private static final int PRE_CREATED_GUEST_ID = 25;
private static final int USER_MANAGER_TIMEOUT_MS = 100;
private static final String HAL_USER_NAME = "HAL 9000";
private static final int HAL_USER_ID = 42;
private static final int HAL_USER_FLAGS = 108;
private static final String USER_LOCALES = "LOL";
private static final int HAL_TIMEOUT_MS = 500;
private static final int ADDITIONAL_TIME_MS = 200;
private static final int HAL_NOT_REPLYING_TIMEOUT_MS = HAL_TIMEOUT_MS + ADDITIONAL_TIME_MS;
private static final long POST_HAL_NOT_REPLYING_TIMEOUT_MS = HAL_NOT_REPLYING_TIMEOUT_MS
+ ADDITIONAL_TIME_MS;
// Spy used in tests that need to verify folloing method:
// managePreCreatedUsers, postAsyncPreCreatedUser, preCreateUsers
private CarServiceHelperService mHelper;
@Mock
private Context mMockContext;
@Mock
private PackageManager mPackageManager;
@Mock
private Context mApplicationContext;
@Mock
private CarUserManagerHelper mUserManagerHelper;
@Mock
private UserManager mUserManager;
@Mock
private CarLaunchParamsModifier mCarLaunchParamsModifier;
@Mock
private CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
@Mock
private IBinder mICarBinder;
@Mock
private InitialUserSetter mInitialUserSetter;
@Captor
private ArgumentCaptor<Parcel> mBinderCallData;
private Exception mBinderCallException;
/**
* Initialize objects and setup testing environment.
*/
@Override
protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
session
.spyStatic(CarProperties.class)
.spyStatic(UserManager.class);
}
@Before
public void setUpMocks() {
mHelper = spy(new CarServiceHelperService(
mMockContext,
mUserManagerHelper,
mInitialUserSetter,
mUserManager,
mCarLaunchParamsModifier,
mCarWatchdogDaemonHelper,
/* halEnabled= */ true,
HAL_TIMEOUT_MS));
when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
}
@Test
public void testCarServiceLaunched() throws Exception {
mockRegisterReceiver();
mockBindService();
mockLoadLibrary();
mHelper.onStart();
verifyBindService();
}
@Test
public void testHandleCarServiceCrash() throws Exception {
mockHandleCarServiceCrash();
mockCarServiceException();
mHelper.handleCarServiceConnection(mICarBinder);
verify(mHelper).handleCarServiceCrash();
}
/**
* Test that the {@link CarServiceHelperService} starts up a secondary admin user upon first
* run.
*/
@Test
public void testInitialInfo_noHal() throws Exception {
CarServiceHelperService halLessHelper = new CarServiceHelperService(
mMockContext,
mUserManagerHelper,
mInitialUserSetter,
mUserManager,
mCarLaunchParamsModifier,
mCarWatchdogDaemonHelper,
/* halEnabled= */ false,
HAL_TIMEOUT_MS);
halLessHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
verifyDefaultBootBehavior();
}
@Test
public void testInitialInfo_halReturnedDefault() throws Exception {
bindMockICar();
expectICarGetInitialUserInfo(InitialUserInfoAction.DEFAULT);
mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
assertNoICarCallExceptions();
verifyICarGetInitialUserInfoCalled();
assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
verifyDefaultBootBehavior();
}
@Test
public void testInitialInfo_halReturnedDefault_withLocale() throws Exception {
bindMockICar();
expectICarGetInitialUserInfo(InitialUserInfoAction.DEFAULT_WITH_LOCALE);
mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
assertNoICarCallExceptions();
verifyICarGetInitialUserInfoCalled();
assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
verifyDefaultBootBehaviorWithLocale();
}
@Test
public void testInitialInfo_halServiceNeverReturned() throws Exception {
bindMockICar();
expectICarGetInitialUserInfo(InitialUserInfoAction.DO_NOT_REPLY);
mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
sleep("before asserting DEFAULT behavior", POST_HAL_NOT_REPLYING_TIMEOUT_MS);
assertNoICarCallExceptions();
verifyICarGetInitialUserInfoCalled();
assertThat(mHelper.getHalResponseTime()).isLessThan(0);
verifyDefaultBootBehavior();
}
@Test
public void testInitialInfo_halServiceReturnedTooLate() throws Exception {
bindMockICar();
expectICarGetInitialUserInfo(InitialUserInfoAction.DELAYED_REPLY);
mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
sleep("before asserting DEFAULT behavior", POST_HAL_NOT_REPLYING_TIMEOUT_MS);
assertNoICarCallExceptions();
verifyICarGetInitialUserInfoCalled();
assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
sleep("to make sure not called again", POST_HAL_NOT_REPLYING_TIMEOUT_MS);
verifyDefaultBootBehavior();
}
@Test
public void testInitialInfo_halReturnedNonOkResultCode() throws Exception {
bindMockICar();
expectICarGetInitialUserInfo(InitialUserInfoAction.NON_OK_RESULT_CODE);
mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
assertNoICarCallExceptions();
verifyICarGetInitialUserInfoCalled();
assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
verifyDefaultBootBehavior();
}
@Test
public void testInitialInfo_halReturnedOkWithNoBundle() throws Exception {
bindMockICar();
expectICarGetInitialUserInfo(InitialUserInfoAction.NULL_BUNDLE);
mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
assertNoICarCallExceptions();
verifyICarGetInitialUserInfoCalled();
assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
verifyDefaultBootBehavior();
}
@Test
public void testInitialInfo_halReturnedSwitch_ok() throws Exception {
bindMockICar();
expectICarGetInitialUserInfo(InitialUserInfoAction.SWITCH_OK);
mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
assertNoICarCallExceptions();
verifyICarGetInitialUserInfoCalled();
assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
verifyUserSwitchedByHal();
}
@Test
public void testInitialInfo_halReturnedSwitch_ok_withLocale() throws Exception {
bindMockICar();
expectICarGetInitialUserInfo(InitialUserInfoAction.SWITCH_OK_WITH_LOCALE);
mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
assertNoICarCallExceptions();
verifyICarGetInitialUserInfoCalled();
assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
verifyUserSwitchedByHalWithLocale();
}
@Test
public void testInitialInfo_halReturnedSwitch_switchMissingUserId() throws Exception {
bindMockICar();
expectICarGetInitialUserInfo(InitialUserInfoAction.SWITCH_MISSING_USER_ID);
mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
assertNoICarCallExceptions();
verifyICarGetInitialUserInfoCalled();
assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
verifyUserNotSwitchedByHal();
verifyDefaultBootBehavior();
}
@Test
public void testInitialInfo_halReturnedCreateOk() throws Exception {
bindMockICar();
expectICarGetInitialUserInfo((r) -> sendCreateDefaultHalUserAction(r));
mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
assertNoICarCallExceptions();
verifyICarGetInitialUserInfoCalled();
assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
verifyUserCreatedByHal();
}
@Test
public void testInitialInfo_halReturnedCreateOk_withLocale() throws Exception {
bindMockICar();
expectICarGetInitialUserInfo(
(r) -> sendCreateAction(r, HAL_USER_NAME, HAL_USER_FLAGS, USER_LOCALES));
mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
assertNoICarCallExceptions();
verifyICarGetInitialUserInfoCalled();
assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
verifyUserCreatedByHalWithLocale();
}
@Test
public void testOnUserStarting_notifiesICar() throws Exception {
bindMockICar();
int userId = 10;
expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING,
userId);
mHelper.onUserStarting(newTargetUser(userId));
assertNoICarCallExceptions();
verifyICarOnUserLifecycleEventCalled();
}
@Test
public void testOnUserStarting_preCreatedDoesntNotifyICar() throws Exception {
bindMockICar();
mHelper.onUserStarting(newTargetUser(10, /* preCreated= */ true));
verifyICarOnUserLifecycleEventNeverCalled();
}
@Test
public void testOnUserSwitching_notifiesICar() throws Exception {
bindMockICar();
int currentUserId = 10;
int targetUserId = 11;
expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
currentUserId, targetUserId);
mHelper.onUserSwitching(newTargetUser(currentUserId),
newTargetUser(targetUserId));
assertNoICarCallExceptions();
}
@Test
public void testOnUserSwitching_preCreatedDoesntNotifyICar() throws Exception {
bindMockICar();
mHelper.onUserSwitching(newTargetUser(10), newTargetUser(11, /* preCreated= */ true));
verifyICarOnUserLifecycleEventNeverCalled();
}
@Test
public void testOnUserUnlocking_notifiesICar() throws Exception {
bindMockICar();
int userId = 10;
expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
userId);
mHelper.onUserUnlocking(newTargetUser(userId));
assertNoICarCallExceptions();
}
@Test
public void testOnUserUnlocking_preCreatedDoesntNotifyICar() throws Exception {
bindMockICar();
mHelper.onUserUnlocking(newTargetUser(10, /* preCreated= */ true));
verifyICarOnUserLifecycleEventNeverCalled();
}
@Test
public void testOnUserUnlocked_notifiesICar_systemUserFirst() throws Exception {
bindMockICar();
int systemUserId = UserHandle.USER_SYSTEM;
expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED,
systemUserId);
int firstUserId = 10;
expectICarFirstUserUnlocked(firstUserId);
setHalResponseTime();
mHelper.onUserUnlocked(newTargetUser(systemUserId));
mHelper.onUserUnlocked(newTargetUser(firstUserId));
assertNoICarCallExceptions();
verifyICarOnUserLifecycleEventCalled(); // system user
verifyICarFirstUserUnlockedCalled(); // first user
}
@Test
public void testOnUserUnlocked_notifiesICar_firstUserReportedJustOnce() throws Exception {
bindMockICar();
int firstUserId = 10;
expectICarFirstUserUnlocked(firstUserId);
int secondUserId = 11;
expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED,
secondUserId);
setHalResponseTime();
mHelper.onUserUnlocked(newTargetUser(firstUserId));
mHelper.onUserUnlocked(newTargetUser(secondUserId));
assertNoICarCallExceptions();
verifyICarFirstUserUnlockedCalled(); // first user
verifyICarOnUserLifecycleEventCalled(); // second user
}
@Test
public void testOnUserStopping_notifiesICar() throws Exception {
bindMockICar();
int userId = 10;
expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING,
userId);
mHelper.onUserStopping(newTargetUser(userId));
assertNoICarCallExceptions();
}
@Test
public void testOnUserStopping_preCreatedDoesntNotifyICar() throws Exception {
bindMockICar();
mHelper.onUserStopping(newTargetUser(10, /* preCreated= */ true));
verifyICarOnUserLifecycleEventNeverCalled();
}
@Test
public void testOnUserStopped_notifiesICar() throws Exception {
bindMockICar();
int userId = 10;
expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED,
userId);
mHelper.onUserStopped(newTargetUser(userId));
assertNoICarCallExceptions();
}
@Test
public void testOnUserStopped_preCreatedDoesntNotifyICar() throws Exception {
bindMockICar();
mHelper.onUserStopped(newTargetUser(10, /* preCreated= */ true));
verifyICarOnUserLifecycleEventNeverCalled();
}
@Test
public void testSendSetInitialUserInfoNotifiesICar() throws Exception {
bindMockICar();
UserInfo user = new UserInfo(42, "Dude", UserInfo.FLAG_ADMIN);
expectICarSetInitialUserInfo(user);
mHelper.setInitialUser(user);
verifyICarSetInitialUserCalled();
assertNoICarCallExceptions();
}
@Test
public void testInitialUserInfoRequestType_FirstBoot() throws Exception {
when(mUserManagerHelper.hasInitialUser()).thenReturn(false);
when(mPackageManager.isDeviceUpgrading()).thenReturn(true);
assertThat(mHelper.getInitialUserInfoRequestType())
.isEqualTo(InitialUserInfoRequestType.FIRST_BOOT);
}
@Test
public void testInitialUserInfoRequestType_FirstBootAfterOTA() throws Exception {
when(mUserManagerHelper.hasInitialUser()).thenReturn(true);
when(mPackageManager.isDeviceUpgrading()).thenReturn(true);
assertThat(mHelper.getInitialUserInfoRequestType())
.isEqualTo(InitialUserInfoRequestType.FIRST_BOOT_AFTER_OTA);
}
@Test
public void testInitialUserInfoRequestType_ColdBoot() throws Exception {
when(mUserManagerHelper.hasInitialUser()).thenReturn(true);
when(mPackageManager.isDeviceUpgrading()).thenReturn(false);
assertThat(mHelper.getInitialUserInfoRequestType())
.isEqualTo(InitialUserInfoRequestType.COLD_BOOT);
}
@Test
public void testPreCreatedUsersLessThanRequested() throws Exception {
// Set existing user
expectNoPreCreatedUser();
// Set number of requested user
setNumberRequestedUsersProperty(1);
setNumberRequestedGuestsProperty(0);
mockRunAsync();
SyncAnswer syncUserInfo = mockPreCreateUser(/* isGuest= */ false);
mHelper.managePreCreatedUsers();
syncUserInfo.await(USER_MANAGER_TIMEOUT_MS);
verifyUserCreated(/* isGuest= */ false);
}
@Test
public void testPreCreatedGuestsLessThanRequested() throws Exception {
// Set existing user
expectNoPreCreatedUser();
// Set number of requested user
setNumberRequestedUsersProperty(0);
setNumberRequestedGuestsProperty(1);
mockRunAsync();
SyncAnswer syncUserInfo = mockPreCreateUser(/* isGuest= */ true);
mHelper.managePreCreatedUsers();
syncUserInfo.await(USER_MANAGER_TIMEOUT_MS);
verifyUserCreated(/* isGuest= */ true);
}
@Test
public void testRemovePreCreatedUser() throws Exception {
UserInfo user = expectPreCreatedUser(/* isGuest= */ false,
/* isInitialized= */ true);
setNumberRequestedUsersProperty(0);
setNumberRequestedGuestsProperty(0);
mockRunAsync();
SyncAnswer syncRemoveStatus = mockRemoveUser(PRE_CREATED_USER_ID);
mHelper.managePreCreatedUsers();
syncRemoveStatus.await(USER_MANAGER_TIMEOUT_MS);
verifyUserRemoved(user);
}
@Test
public void testRemovePreCreatedGuest() throws Exception {
UserInfo user = expectPreCreatedUser(/* isGuest= */ true,
/* isInitialized= */ true);
setNumberRequestedUsersProperty(0);
setNumberRequestedGuestsProperty(0);
mockRunAsync();
SyncAnswer syncRemoveStatus = mockRemoveUser(PRE_CREATED_GUEST_ID);
mHelper.managePreCreatedUsers();
syncRemoveStatus.await(USER_MANAGER_TIMEOUT_MS);
verifyUserRemoved(user);
}
@Test
public void testRemoveInvalidPreCreatedUser() throws Exception {
UserInfo user = expectPreCreatedUser(/* isGuest= */ false,
/* isInitialized= */ false);
setNumberRequestedUsersProperty(0);
setNumberRequestedGuestsProperty(0);
mockRunAsync();
SyncAnswer syncRemoveStatus = mockRemoveUser(PRE_CREATED_USER_ID);
mHelper.managePreCreatedUsers();
syncRemoveStatus.await(ADDITIONAL_TIME_MS);
verifyUserRemoved(user);
}
@Test
public void testManagePreCreatedUsersDoNothing() throws Exception {
UserInfo user = expectPreCreatedUser(/* isGuest= */ false,
/* isInitialized= */ true);
setNumberRequestedUsersProperty(1);
setNumberRequestedGuestsProperty(0);
mockPreCreateUser(/* isGuest= */ false);
mockRemoveUser(PRE_CREATED_USER_ID);
mHelper.managePreCreatedUsers();
verifyPostPreCreatedUserSkipped();
}
@Test
public void testManagePreCreatedUsersOnBootCompleted() throws Exception {
mockRunAsync();
mHelper.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
verifyManagePreCreatedUsers();
}
@Test
public void testPreCreateUserExceptionLogged() throws Exception {
SyncAnswer syncException = mockPreCreateUserException();
TimingsTraceAndSlog trace = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
mHelper.preCreateUsers(trace, false);
verifyPostPreCreatedUserException();
assertThat(trace.getUnfinishedTracesForDebug()).isEmpty();
}
private void setHalResponseTime() {
mHelper.setInitialHalResponseTime();
SystemClock.sleep(1); // must sleep at least 1ms so it's not 0
mHelper.setFinalHalResponseTime();
}
/**
* Used in cases where the result of calling HAL for the initial info should be the same as
* not using HAL.
*/
private void verifyDefaultBootBehavior() throws Exception {
verify(mInitialUserSetter).set(argThat((info) -> {
return info.type == InitialUserSetter.TYPE_DEFAULT_BEHAVIOR && info.userLocales == null;
}));
}
private void verifyDefaultBootBehaviorWithLocale() {
verify(mInitialUserSetter).set(argThat((info) -> {
return info.type == InitialUserSetter.TYPE_DEFAULT_BEHAVIOR
&& USER_LOCALES.equals(info.userLocales);
}));
}
private TargetUser newTargetUser(int userId) {
return newTargetUser(userId, /* preCreated= */ false);
}
private TargetUser newTargetUser(int userId, boolean preCreated) {
TargetUser targetUser = mock(TargetUser.class);
when(targetUser.getUserIdentifier()).thenReturn(userId);
UserInfo userInfo = new UserInfo();
userInfo.id = userId;
userInfo.preCreated = preCreated;
when(targetUser.getUserInfo()).thenReturn(userInfo);
return targetUser;
}
private void bindMockICar() throws Exception {
// Must set the binder expectation, otherwise checks for other transactions would fail
expectICarSetCarServiceHelper();
mHelper.handleCarServiceConnection(mICarBinder);
}
private void verifyUserCreatedByHal() throws Exception {
verify(mInitialUserSetter).set(argThat((info) -> {
return info.type == InitialUserSetter.TYPE_CREATE
&& info.newUserName == HAL_USER_NAME
&& info.newUserFlags == HAL_USER_FLAGS
&& info.userLocales == null;
}));
}
private void verifyUserCreatedByHalWithLocale() throws Exception {
verify(mInitialUserSetter).set(argThat((info) -> {
return info.type == InitialUserSetter.TYPE_CREATE
&& info.newUserName == HAL_USER_NAME
&& info.newUserFlags == HAL_USER_FLAGS
&& info.userLocales == USER_LOCALES;
}));
}
private void verifyUserSwitchedByHal() {
verify(mInitialUserSetter).set(argThat((info) -> {
return info.type == InitialUserSetter.TYPE_SWITCH
&& info.switchUserId == HAL_USER_ID
&& info.userLocales == null;
}));
}
private void verifyUserSwitchedByHalWithLocale() {
verify(mInitialUserSetter).set(argThat((info) -> {
return info.type == InitialUserSetter.TYPE_SWITCH
&& info.switchUserId == HAL_USER_ID
&& info.userLocales == USER_LOCALES;
}));
}
private void verifyUserNotSwitchedByHal() {
verify(mInitialUserSetter, never()).set(argThat((info) -> {
return info.type == InitialUserSetter.TYPE_SWITCH;
}));
}
private void verifyBindService () throws Exception {
verify(mMockContext).bindServiceAsUser(
argThat(intent -> intent.getAction().equals(ICarConstants.CAR_SERVICE_INTERFACE)),
any(), eq(Context.BIND_AUTO_CREATE), eq(UserHandle.SYSTEM));
}
private void verifyHandleCarServiceCrash() throws Exception {
verify(mHelper).handleCarServiceCrash();
}
private void mockRegisterReceiver() {
when(mMockContext.registerReceiverForAllUsers(any(), any(), any(), any()))
.thenReturn(new Intent());
}
private void mockBindService() {
when(mMockContext.bindServiceAsUser(any(), any(),
eq(Context.BIND_AUTO_CREATE), eq(UserHandle.SYSTEM)))
.thenReturn(true);
}
private void mockLoadLibrary() {
doNothing().when(mHelper).loadNativeLibrary();
}
private void mockCarServiceException() throws Exception {
when(mICarBinder.transact(anyInt(), notNull(), isNull(), eq(Binder.FLAG_ONEWAY)))
.thenThrow(new RemoteException("mock car service Crash"));
}
private void mockHandleCarServiceCrash() throws Exception {
doNothing().when(mHelper).handleCarServiceCrash();
}
// TODO: create a custom matcher / verifier for binder calls
private void expectICarOnUserLifecycleEvent(int eventType, int expectedUserId)
throws Exception {
expectICarOnUserLifecycleEvent(eventType, UserHandle.USER_NULL, expectedUserId);
}
private void expectICarSetCarServiceHelper() throws Exception {
int txn = IBinder.FIRST_CALL_TRANSACTION
+ ICarConstants.ICAR_CALL_SET_CAR_SERVICE_HELPER;
when(mICarBinder.transact(eq(txn), notNull(), isNull(), eq(Binder.FLAG_ONEWAY)))
.thenReturn(true);
}
private void expectICarOnUserLifecycleEvent(int expectedEventType, int expectedFromUserId,
int expectedToUserId) throws Exception {
int txn = IBinder.FIRST_CALL_TRANSACTION + ICarConstants.ICAR_CALL_ON_USER_LIFECYCLE;
long before = System.currentTimeMillis();
when(mICarBinder.transact(eq(txn), notNull(), isNull(),
eq(Binder.FLAG_ONEWAY))).thenAnswer((invocation) -> {
try {
long after = System.currentTimeMillis();
Log.d(TAG, "Answering txn " + txn);
Parcel data = (Parcel) invocation.getArguments()[1];
data.setDataPosition(0);
data.enforceInterface(ICarConstants.CAR_SERVICE_INTERFACE);
int actualEventType = data.readInt();
long actualTimestamp = data.readLong();
int actualFromUserId = data.readInt();
int actualToUserId = data.readInt();
Log.d(TAG, "Unmarshalled data: eventType=" + actualEventType
+ ", timestamp= " + actualTimestamp
+ ", fromUserId= " + actualFromUserId
+ ", toUserId= " + actualToUserId);
List<String> errors = new ArrayList<>();
assertParcelValueInRange(errors, "timestamp", before, actualTimestamp, after);
assertParcelValue(errors, "eventType", expectedEventType, actualEventType);
assertParcelValue(errors, "fromUserId", expectedFromUserId,
actualFromUserId);
assertParcelValue(errors, "toUserId", expectedToUserId, actualToUserId);
assertNoParcelErrors(errors);
return true;
} catch (Exception e) {
Log.e(TAG, "Exception answering binder call", e);
mBinderCallException = e;
return false;
}
});
}
private void expectICarFirstUserUnlocked(int expectedUserId) throws Exception {
int txn = IBinder.FIRST_CALL_TRANSACTION + ICarConstants.ICAR_CALL_FIRST_USER_UNLOCKED;
long before = System.currentTimeMillis();
long minDuration = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime();
when(mICarBinder.transact(eq(txn), notNull(), isNull(),
eq(Binder.FLAG_ONEWAY))).thenAnswer((invocation) -> {
try {
long after = System.currentTimeMillis();
Log.d(TAG, "Answering txn " + txn);
Parcel data = (Parcel) invocation.getArguments()[1];
data.setDataPosition(0);
data.enforceInterface(ICarConstants.CAR_SERVICE_INTERFACE);
int actualUserId = data.readInt();
long actualTimestamp = data.readLong();
long actualDuration = data.readLong();
int actualHalResponseTime = data.readInt();
Log.d(TAG, "Unmarshalled data: userId= " + actualUserId
+ ", timestamp= " + actualTimestamp
+ ", duration=" + actualDuration
+ ", halResponseTime=" + actualHalResponseTime);
List<String> errors = new ArrayList<>();
assertParcelValue(errors, "userId", expectedUserId, actualUserId);
assertParcelValueInRange(errors, "timestamp", before, actualTimestamp,
after);
assertMinimumParcelValue(errors, "duration", minDuration, actualDuration);
assertMinimumParcelValue(errors, "halResponseTime", 1, actualHalResponseTime);
assertNoParcelErrors(errors);
return true;
} catch (Exception e) {
Log.e(TAG, "Exception answering binder call", e);
mBinderCallException = e;
return false;
}
});
}
enum InitialUserInfoAction {
DEFAULT,
DEFAULT_WITH_LOCALE,
DO_NOT_REPLY,
DELAYED_REPLY,
NON_OK_RESULT_CODE,
NULL_BUNDLE,
SWITCH_OK,
SWITCH_OK_WITH_LOCALE,
SWITCH_MISSING_USER_ID
}
private void expectICarGetInitialUserInfo(InitialUserInfoAction action) throws Exception {
expectICarGetInitialUserInfo((receiver) ->{
switch (action) {
case DEFAULT:
sendDefaultAction(receiver);
break;
case DEFAULT_WITH_LOCALE:
sendDefaultAction(receiver, USER_LOCALES);
break;
case DO_NOT_REPLY:
Log.d(TAG, "NOT replying to bind call");
break;
case DELAYED_REPLY:
sleep("before sending result", HAL_NOT_REPLYING_TIMEOUT_MS);
sendDefaultAction(receiver);
break;
case NON_OK_RESULT_CODE:
Log.d(TAG, "sending bad result code");
receiver.send(-1, null);
break;
case NULL_BUNDLE:
Log.d(TAG, "sending OK without bundle");
receiver.send(HalCallback.STATUS_OK, null);
break;
case SWITCH_OK:
sendValidSwitchAction(receiver, /* userLocales= */ null);
break;
case SWITCH_OK_WITH_LOCALE:
sendValidSwitchAction(receiver, USER_LOCALES);
break;
case SWITCH_MISSING_USER_ID:
Log.d(TAG, "sending Switch without user Id");
sendSwitchAction(receiver, /* id= */ null, /* userLocales= */ null);
break;
default:
throw new IllegalArgumentException("invalid action: " + action);
}
});
}
private void expectICarGetInitialUserInfo(GetInitialUserInfoAction action) throws Exception {
int txn = IBinder.FIRST_CALL_TRANSACTION + ICarConstants.ICAR_CALL_GET_INITIAL_USER_INFO;
when(mICarBinder.transact(eq(txn), notNull(), isNull(),
eq(Binder.FLAG_ONEWAY))).thenAnswer((invocation) -> {
try {
Log.d(TAG, "Answering txn " + txn);
Parcel data = (Parcel) invocation.getArguments()[1];
data.setDataPosition(0);
data.enforceInterface(ICarConstants.CAR_SERVICE_INTERFACE);
int actualRequestType = data.readInt();
int actualTimeoutMs = data.readInt();
IResultReceiver receiver = IResultReceiver.Stub
.asInterface(data.readStrongBinder());
Log.d(TAG, "Unmarshalled data: requestType= " + actualRequestType
+ ", timeout=" + actualTimeoutMs
+ ", receiver =" + receiver);
action.onReceiver(receiver);
return true;
} catch (Exception e) {
Log.e(TAG, "Exception answering binder call", e);
mBinderCallException = e;
return false;
}
});
}
private void expectICarSetInitialUserInfo(UserInfo user) throws RemoteException {
int txn = IBinder.FIRST_CALL_TRANSACTION + ICarConstants.ICAR_CALL_SET_INITIAL_USER;
when(mICarBinder.transact(eq(txn), notNull(), isNull(),
eq(Binder.FLAG_ONEWAY))).thenAnswer((invocation) -> {
try {
Log.d(TAG, "Answering txn " + txn);
Parcel data = (Parcel) invocation.getArguments()[1];
data.setDataPosition(0);
data.enforceInterface(ICarConstants.CAR_SERVICE_INTERFACE);
int actualUserId = data.readInt();
Log.d(TAG, "Unmarshalled data: user= " + actualUserId);
assertThat(actualUserId).isEqualTo(user.id);
return true;
} catch (Exception e) {
Log.e(TAG, "Exception answering binder call", e);
mBinderCallException = e;
return false;
}
});
}
private void expectNoPreCreatedUser() throws Exception {
when(mUserManager.getUsers(/* excludePartial= */ true,
/* excludeDying= */ true, /* excludePreCreated= */ false))
.thenReturn(new ArrayList<UserInfo> ());
}
private UserInfo expectPreCreatedUser(boolean isGuest, boolean isInitialized)
throws Exception {
int userId = isGuest ? PRE_CREATED_GUEST_ID : PRE_CREATED_USER_ID;
UserInfo user = new UserInfoBuilder(userId)
.setGuest(isGuest)
.setPreCreated(true)
.setInitialized(isInitialized)
.build();
when(mUserManager.getUsers(/* excludePartial= */ true,
/* excludeDying= */ true, /* excludePreCreated= */ false))
.thenReturn(Arrays.asList(user));
return user;
}
private interface GetInitialUserInfoAction {
void onReceiver(IResultReceiver receiver) throws Exception;
}
private void sendDefaultAction(IResultReceiver receiver) throws Exception {
sendDefaultAction(receiver, /* userLocales= */ null);
}
private void sendDefaultAction(IResultReceiver receiver, String userLocales) throws Exception {
Log.d(TAG, "Sending DEFAULT action to receiver " + receiver);
Bundle data = new Bundle();
data.putInt(CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION,
InitialUserInfoResponseAction.DEFAULT);
if (userLocales != null) {
data.putString(CarUserServiceConstants.BUNDLE_USER_LOCALES, userLocales);
}
receiver.send(HalCallback.STATUS_OK, data);
}
private void sendValidSwitchAction(IResultReceiver receiver, String userLocales)
throws Exception {
Log.d(TAG, "Sending SWITCH (" + HAL_USER_ID + ") action to receiver " + receiver);
sendSwitchAction(receiver, HAL_USER_ID, userLocales);
}
private void sendSwitchAction(IResultReceiver receiver, Integer id, String userLocales)
throws Exception {
Bundle data = new Bundle();
data.putInt(CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION,
InitialUserInfoResponseAction.SWITCH);
if (id != null) {
data.putInt(CarUserServiceConstants.BUNDLE_USER_ID, id);
}
if (userLocales != null) {
data.putString(CarUserServiceConstants.BUNDLE_USER_LOCALES, userLocales);
}
receiver.send(HalCallback.STATUS_OK, data);
}
private void sendCreateDefaultHalUserAction(IResultReceiver receiver) throws Exception {
sendCreateAction(receiver, HAL_USER_NAME, HAL_USER_FLAGS, /* userLocales= */ null);
}
private void sendCreateAction(IResultReceiver receiver, String name, Integer flags,
String userLocales) throws Exception {
Bundle data = new Bundle();
data.putInt(CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION,
InitialUserInfoResponseAction.CREATE);
if (name != null) {
data.putString(CarUserServiceConstants.BUNDLE_USER_NAME, name);
}
if (flags != null) {
data.putInt(CarUserServiceConstants.BUNDLE_USER_FLAGS, flags);
}
if (userLocales != null) {
data.putString(CarUserServiceConstants.BUNDLE_USER_LOCALES, userLocales);
}
receiver.send(HalCallback.STATUS_OK, data);
}
private void sleep(String reason, long napTimeMs) {
Log.d(TAG, "Sleeping for " + napTimeMs + "ms: " + reason);
SystemClock.sleep(napTimeMs);
Log.d(TAG, "Woke up (from '" + reason + "')");
}
private void verifyICarOnUserLifecycleEventCalled() throws Exception {
verifyICarTxnCalled(ICarConstants.ICAR_CALL_ON_USER_LIFECYCLE);
}
private void verifyICarOnUserLifecycleEventNeverCalled() throws Exception {
verifyICarTxnNeverCalled(ICarConstants.ICAR_CALL_ON_USER_LIFECYCLE);
}
private void verifyICarFirstUserUnlockedCalled() throws Exception {
verifyICarTxnCalled(ICarConstants.ICAR_CALL_FIRST_USER_UNLOCKED);
}
private void verifyICarGetInitialUserInfoCalled() throws Exception {
verifyICarTxnCalled(ICarConstants.ICAR_CALL_GET_INITIAL_USER_INFO);
}
private void verifyICarSetInitialUserCalled() throws Exception {
verifyICarTxnCalled(ICarConstants.ICAR_CALL_SET_INITIAL_USER);
}
private void verifyICarTxnCalled(int txnId) throws Exception {
int txn = IBinder.FIRST_CALL_TRANSACTION + txnId;
verify(mICarBinder).transact(eq(txn), notNull(), isNull(), eq(Binder.FLAG_ONEWAY));
}
private void verifyICarTxnNeverCalled(int txnId) throws Exception {
int txn = IBinder.FIRST_CALL_TRANSACTION + txnId;
verify(mICarBinder, never()).transact(eq(txn), notNull(), isNull(), eq(Binder.FLAG_ONEWAY));
}
private void setNumberRequestedUsersProperty(int numberUser) {
doReturn(Optional.of(numberUser)).when(() -> CarProperties.number_pre_created_users());
}
private void setNumberRequestedGuestsProperty(int numberGuest) {
doReturn(Optional.of(numberGuest)).when(() -> CarProperties.number_pre_created_guests());
}
private void mockRunAsync() {
doAnswer(answerVoid(Runnable::run)).when(mHelper).runAsync(any(Runnable.class));
}
private SyncAnswer mockPreCreateUser(boolean isGuest) {
UserInfo newUser = isGuest ? newGuestUser(PRE_CREATED_GUEST_ID) :
newSecondaryUser(PRE_CREATED_USER_ID);
SyncAnswer<UserInfo> syncUserInfo = SyncAnswer.forReturn(newUser);
when(mUserManager.preCreateUser(getDefaultUserType(isGuest)))
.thenAnswer(syncUserInfo);
return syncUserInfo;
}
private SyncAnswer mockRemoveUser(@UserIdInt int userId) {
SyncAnswer<Boolean> syncRemoveStatus = SyncAnswer.forReturn(true);
when(mUserManager.removeUser(userId)).thenAnswer(syncRemoveStatus);
return syncRemoveStatus;
}
private SyncAnswer mockPreCreateUserException() {
SyncAnswer<UserInfo> syncException = SyncAnswer.forException(new Exception());
when(mUserManager.preCreateUser(anyString()))
.thenAnswer(syncException);
return syncException;
}
private void verifyUserCreated(boolean isGuest) throws Exception {
String userType =
isGuest ? UserManager.USER_TYPE_FULL_GUEST : UserManager.USER_TYPE_FULL_SECONDARY;
verify(mUserManager).preCreateUser(eq(userType));
}
private void verifyUserRemoved(UserInfo user) throws Exception {
verify(mUserManager).removeUser(user.id);
}
private void verifyPostPreCreatedUserSkipped() throws Exception {
verify(mHelper, never()).runAsync(any());
}
private void verifyPostPreCreatedUserException() throws Exception {
verify(mHelper).logPrecreationFailure(anyString(), any());
}
private void verifyManagePreCreatedUsers() throws Exception {
verify(mHelper).managePreCreatedUsers();
}
private void assertParcelValue(List<String> errors, String field, int expected,
int actual) {
if (expected != actual) {
errors.add(String.format("%s mismatch: expected=%d, actual=%d",
field, expected, actual));
}
}
private void assertParcelValueInRange(List<String> errors, String field, long before,
long actual, long after) {
if (actual < before || actual> after) {
errors.add(field + " (" + actual+ ") not in range [" + before + ", " + after + "]");
}
}
private void assertMinimumParcelValue(List<String> errors, String field, long min,
long actual) {
if (actual < min) {
errors.add("Minimum " + field + " should be " + min + " (was " + actual + ")");
}
}
private void assertNoParcelErrors(List<String> errors) {
int size = errors.size();
if (size == 0) return;
StringBuilder msg = new StringBuilder().append(size).append(" errors on parcel: ");
for (String error : errors) {
msg.append("\n\t").append(error);
}
msg.append('\n');
throw new IllegalArgumentException(msg.toString());
}
/**
* Asserts that no exception was thrown when answering to a mocked {@code ICar} binder call.
* <p>
* This method should be called before asserting the expected results of a test, so it makes
* clear why the test failed when the call was not made as expected.
*/
private void assertNoICarCallExceptions() throws Exception {
if (mBinderCallException != null)
throw mBinderCallException;
}
}