blob: d851753450dc5657e9039f15dd3ac9c9a48240ef [file] [log] [blame]
/*
* Copyright (C) 2016 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.car;
import static android.content.pm.UserInfo.FLAG_EPHEMERAL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserManager.USER_TYPE_FULL_GUEST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
import android.car.hardware.power.ICarPowerStateListener;
import android.car.userlib.CarUserManagerHelper;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReq;
import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateShutdownParam;
import android.os.RemoteException;
import android.os.UserManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import androidx.test.filters.FlakyTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.car.hal.PowerHalService;
import com.android.car.hal.PowerHalService.PowerState;
import com.android.car.systeminterface.DisplayInterface;
import com.android.car.systeminterface.IOInterface;
import com.android.car.systeminterface.SystemInterface;
import com.android.car.systeminterface.SystemStateInterface;
import com.android.car.systeminterface.WakeLockInterface;
import com.android.car.test.utils.TemporaryDirectory;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.quality.Strictness;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(MockitoJUnitRunner.class)
public class CarPowerManagementServiceTest {
private static final String TAG = CarPowerManagementServiceTest.class.getSimpleName();
private static final long WAIT_TIMEOUT_MS = 2000;
private static final long WAIT_TIMEOUT_LONG_MS = 5000;
private static final int NO_USER_INFO_FLAGS = 0;
private static final String NEW_GUEST_NAME = "NewestGuestInTheBlock";
private final MockDisplayInterface mDisplayInterface = new MockDisplayInterface();
private final MockSystemStateInterface mSystemStateInterface = new MockSystemStateInterface();
private final MockWakeLockInterface mWakeLockInterface = new MockWakeLockInterface();
private final MockIOInterface mIOInterface = new MockIOInterface();
private final PowerSignalListener mPowerSignalListener = new PowerSignalListener();
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
private MockitoSession mSession;
private MockedPowerHalService mPowerHal;
private SystemInterface mSystemInterface;
private CarPowerManagementService mService;
private CompletableFuture<Void> mFuture;
@Mock
private CarUserManagerHelper mCarUserManagerHelper;
@Mock
private UserManager mUserManager;
@Mock
private Resources mResources;
// Wakeup time for the test; it's automatically set based on @WakeupTime annotation
private int mWakeupTime;
// Value used to set config_disableUserSwitchDuringResume - must be defined before initTest();
private boolean mDisableUserSwitchDuringResume;
// Tracks Log.wtf() calls made during code execution / used on verifyWtfNeverLogged()
// TODO: move mechanism to common code / custom Rule
private final List<UnsupportedOperationException> mWtfs = new ArrayList<>();
@Rule
public final TestRule setWakeupTimeRule = new TestWatcher() {
protected void starting(Description description) {
final String testName = description.getMethodName();
try {
Method testMethod = CarPowerManagementServiceTest.class.getMethod(testName);
WakeupTime wakeupAnnotation = testMethod.getAnnotation(WakeupTime.class);
if (wakeupAnnotation != null) {
mWakeupTime = wakeupAnnotation.value();
Log.d(TAG, "Using annotated wakeup time: " + mWakeupTime);
}
} catch (Exception e) {
Log.e(TAG, "Could not infer wakeupTime for " + testName, e);
}
}
};
@Before
public void setUp() throws Exception {
mSession = mockitoSession()
.strictness(Strictness.LENIENT)
.spyStatic(ActivityManager.class)
.spyStatic(Log.class)
.startMocking();
mPowerHal = new MockedPowerHalService(true /*isPowerStateSupported*/,
true /*isDeepSleepAllowed*/, true /*isTimedWakeupAllowed*/);
mSystemInterface = SystemInterface.Builder.defaultSystemInterface(mContext)
.withDisplayInterface(mDisplayInterface)
.withSystemStateInterface(mSystemStateInterface)
.withWakeLockInterface(mWakeLockInterface)
.withIOInterface(mIOInterface).build();
doAnswer((invocation) -> {
return addWtf(invocation);
}).when(() -> Log.wtf(anyString(), anyString()));
doAnswer((invocation) -> {
return addWtf(invocation);
}).when(() -> Log.wtf(anyString(), anyString(), notNull()));
}
@After
public void tearDown() throws Exception {
if (mService != null) {
mService.release();
}
mIOInterface.tearDown();
mSession.finishMocking();
}
private Object addWtf(InvocationOnMock invocation) {
String message = "Called " + invocation;
Log.d(TAG, message); // Log always, as some test expect it
mWtfs.add(new UnsupportedOperationException(message));
return null;
}
/**
* Helper method to create mService and initialize a test case
*/
private void initTest() throws Exception {
when(mResources.getInteger(R.integer.maxGarageModeRunningDurationInSecs))
.thenReturn(900);
when(mResources.getBoolean(R.bool.config_disableUserSwitchDuringResume))
.thenReturn(mDisableUserSwitchDuringResume);
Log.i(TAG, "initTest(): overridden overlay properties: "
+ "config_disableUserSwitchDuringResume="
+ mResources.getBoolean(R.bool.config_disableUserSwitchDuringResume)
+ ", maxGarageModeRunningDurationInSecs="
+ mResources.getInteger(R.integer.maxGarageModeRunningDurationInSecs));
mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
mSystemInterface, mCarUserManagerHelper, mUserManager, NEW_GUEST_NAME);
mService.init();
mService.setShutdownTimersForTest(0, 0);
mPowerHal.setSignalListener(mPowerSignalListener);
if (mWakeupTime > 0) {
registerListenerToService();
mService.scheduleNextWakeupTime(mWakeupTime);
}
assertStateReceived(MockedPowerHalService.SET_WAIT_FOR_VHAL, 0);
}
/**
* Same as {@link #initTest()}, but it also assumes the current and initial users are user 10.
*/
private void initTestForUser10() throws Exception {
initTest();
setUserInfo(10, NO_USER_INFO_FLAGS);
setCurrentUser(10);
setInitialUser(10);
}
@Test
public void testBootComplete() throws Exception {
initTest();
verifyWtfNeverLogged();
}
@Test
public void testDisplayOn() throws Exception {
// start with display off
mSystemInterface.setDisplayState(false);
mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS);
initTest();
// Transition to ON state
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
// display should be turned on as it started with off state.
assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isTrue();
verifyWtfNeverLogged();
}
@Test
public void testShutdown() throws Exception {
initTestForUser10();
// Transition to ON state
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isTrue();
mPowerHal.setCurrentPowerState(
new PowerState(
VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.SHUTDOWN_IMMEDIATELY));
// Since modules have to manually schedule next wakeup, we should not schedule next wakeup
// To test module behavior, we need to actually implement mock listener module.
assertStateReceived(PowerHalService.SET_SHUTDOWN_START, 0);
assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isFalse();
mPowerSignalListener.waitForShutdown(WAIT_TIMEOUT_MS);
mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS);
verifyWtfNeverLogged();
}
@Test
public void testSuspend() throws Exception {
initTestForUser10();
// Start in the ON state
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isTrue();
// Request suspend
mPowerHal.setCurrentPowerState(
new PowerState(
VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP));
// Verify suspend
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, mWakeupTime);
verifyWtfNeverLogged();
}
@Test
public void testShutdownOnSuspend() throws Exception {
initTestForUser10();
// Start in the ON state
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isTrue();
// Tell it to shutdown
mService.requestShutdownOnNextSuspend();
// Request suspend
mPowerHal.setCurrentPowerState(
new PowerState(
VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP));
// Verify shutdown
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_SHUTDOWN_START, WAIT_TIMEOUT_LONG_MS, mWakeupTime);
mPowerSignalListener.waitForShutdown(WAIT_TIMEOUT_MS);
// Send the finished signal
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.FINISHED, 0));
mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS);
// Cancel the shutdown
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.CANCEL_SHUTDOWN, 0));
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_SHUTDOWN_CANCELLED, WAIT_TIMEOUT_LONG_MS, 0);
// Request suspend again
mPowerHal.setCurrentPowerState(
new PowerState(
VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP));
// Verify suspend
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, mWakeupTime);
verifyWtfNeverLogged();
}
@Test
public void testShutdownCancel() throws Exception {
initTestForUser10();
// Start in the ON state
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isTrue();
// Start shutting down
mPowerHal.setCurrentPowerState(
new PowerState(
VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.SHUTDOWN_IMMEDIATELY));
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_SHUTDOWN_START, WAIT_TIMEOUT_LONG_MS, 0);
// Cancel the shutdown
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.CANCEL_SHUTDOWN, 0));
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_SHUTDOWN_CANCELLED, WAIT_TIMEOUT_LONG_MS, 0);
// Go to suspend
mPowerHal.setCurrentPowerState(
new PowerState(
VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP));
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, mWakeupTime);
verifyWtfNeverLogged();
}
@Test
public void testSleepImmediately() throws Exception {
initTestForUser10();
// Transition to ON state
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isTrue();
mPowerHal.setCurrentPowerState(
new PowerState(
VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.SLEEP_IMMEDIATELY));
// Since modules have to manually schedule next wakeup, we should not schedule next wakeup
// To test module behavior, we need to actually implement mock listener module.
assertStateReceived(PowerHalService.SET_SHUTDOWN_START, 0);
assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isFalse();
mPowerSignalListener.waitForShutdown(WAIT_TIMEOUT_MS);
mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS);
verifyWtfNeverLogged();
}
@Test
@WakeupTime(100)
@FlakyTest
public void testShutdownWithProcessing() throws Exception {
initTestForUser10();
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE, 0));
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_SHUTDOWN_START, WAIT_TIMEOUT_LONG_MS, mWakeupTime);
mPowerSignalListener.waitForShutdown(WAIT_TIMEOUT_MS);
// Send the finished signal
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.FINISHED, 0));
mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS);
verifyWtfNeverLogged();
}
@Test
@WakeupTime(100)
public void testSleepEntryAndWakeup() throws Exception {
initTest();
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP));
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, mWakeupTime);
mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
// Send the finished signal from HAL to CPMS
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.FINISHED, 0));
mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
mPowerSignalListener.waitForSleepExit(WAIT_TIMEOUT_MS);
verifyWtfNeverLogged();
}
@Test
@FlakyTest
public void testUserSwitchingOnResume_differentUser() throws Exception {
initTest();
setUserInfo(10, NO_USER_INFO_FLAGS);
setUserInfo(11, NO_USER_INFO_FLAGS);
setCurrentUser(10);
setInitialUser(11);
suspendAndResumeForUserSwitchingTests();
verifyUserSwitched(11);
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_sameUser() throws Exception {
initTest();
setUserInfo(10, NO_USER_INFO_FLAGS);
setInitialUser(10);
setCurrentUser(10);
suspendAndResumeForUserSwitchingTests();
verifyUserNotSwitched();
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_differentEphemeralUser() throws Exception {
initTest();
setUserInfo(10, NO_USER_INFO_FLAGS);
setUserInfo(11, FLAG_EPHEMERAL);
setCurrentUser(10);
setInitialUser(11);
suspendAndResumeForUserSwitchingTests();
verifyUserSwitched(11);
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_sameGuest() throws Exception {
initTest();
setUserInfo(10, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
setInitialUser(10);
setCurrentUser(10);
expectGuestMarkedForDeletionOk(10);
expectNewGuestCreated(11);
suspendAndResumeForUserSwitchingTests();
verifyUserRemoved(10);
verifyUserSwitched(11);
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_differentGuest() throws Exception {
initTest();
setUserInfo(11, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
setInitialUser(11);
setCurrentUser(10);
expectGuestMarkedForDeletionOk(11);
expectNewGuestCreated(12);
suspendAndResumeForUserSwitchingTests();
verifyUserRemoved(11);
verifyUserSwitched(12);
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_guestCreationFailed() throws Exception {
initTest();
setUserInfo(10, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
setInitialUser(10);
setCurrentUser(10);
expectGuestMarkedForDeletionOk(10);
expectNewGuestCreationFailed("ElGuesto");
suspendAndResumeForUserSwitchingTests();
verifyUserNotSwitched();
verifyUserNotRemoved(10);
// expects WTF
}
@Test
public void testUserSwitchingOnResume_differentPersistentGuest() throws Exception {
initTest();
setUserInfo(11, "ElGuesto", USER_TYPE_FULL_GUEST, NO_USER_INFO_FLAGS);
setInitialUser(11);
setCurrentUser(10);
expectGuestMarkedForDeletionOk(11);
expectNewGuestCreated(12);
suspendAndResumeForUserSwitchingTests();
verifyUserRemoved(11);
verifyUserSwitched(12);
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_preDeleteGuestFail() throws Exception {
initTest();
setUserInfo(10, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
setInitialUser(10);
setCurrentUser(10);
expectGuestMarkedForDeletionFail(10);
suspendAndResumeForUserSwitchingTests();
verifyUserNotSwitched();
verifyNoGuestCreated();
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_systemUser() throws Exception {
initTest();
setInitialUser(USER_SYSTEM);
setCurrentUser(10);
suspendAndResumeForUserSwitchingTests();
verifyUserNotSwitched();
// expects WTF
}
@Test
public void testUserSwitchingOnResume_disabledByOEM_differentUser() throws Exception {
disableUserSwitchingDuringResume();
initTest();
setUserInfo(10, NO_USER_INFO_FLAGS);
setUserInfo(11, NO_USER_INFO_FLAGS);
setCurrentUser(10);
setInitialUser(11);
suspendAndResumeForUserSwitchingTests();
verifyUserNotSwitched();
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_disabledByOEM_sameUser() throws Exception {
disableUserSwitchingDuringResume();
initTest();
setUserInfo(10, NO_USER_INFO_FLAGS);
setInitialUser(10);
setCurrentUser(10);
suspendAndResumeForUserSwitchingTests();
verifyUserNotSwitched();
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_disabledByOEM_differentEphemeralUser() throws Exception {
disableUserSwitchingDuringResume();
initTest();
setUserInfo(10, NO_USER_INFO_FLAGS);
setUserInfo(11, FLAG_EPHEMERAL);
setCurrentUser(10);
setInitialUser(11);
suspendAndResumeForUserSwitchingTests();
verifyUserNotSwitched();
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_disabledByOEM_sameGuest() throws Exception {
disableUserSwitchingDuringResume();
initTest();
setUserInfo(10, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
setInitialUser(10);
setCurrentUser(10);
expectGuestMarkedForDeletionOk(10);
expectNewGuestCreated(11);
suspendAndResumeForUserSwitchingTests();
verifyUserRemoved(10);
verifyUserSwitched(11);
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_disabledByOEM_differentGuest() throws Exception {
disableUserSwitchingDuringResume();
initTest();
setUserInfo(11, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
setInitialUser(11);
setCurrentUser(10);
expectGuestMarkedForDeletionOk(11);
expectNewGuestCreated(12);
suspendAndResumeForUserSwitchingTests();
verifyUserRemoved(11);
verifyUserSwitched(12);
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_disabledByOEM_guestCreationFailed() throws Exception {
disableUserSwitchingDuringResume();
initTest();
setUserInfo(10, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
setInitialUser(10);
setCurrentUser(10);
expectGuestMarkedForDeletionOk(10);
expectNewGuestCreationFailed("ElGuesto");
suspendAndResumeForUserSwitchingTests();
verifyUserNotSwitched();
verifyUserNotRemoved(10);
// expects WTF
}
@Test
public void testUserSwitchingOnResume_disabledByOEM_differentPersistentGuest()
throws Exception {
disableUserSwitchingDuringResume();
initTest();
setUserInfo(11, "ElGuesto", USER_TYPE_FULL_GUEST, NO_USER_INFO_FLAGS);
setInitialUser(11);
setCurrentUser(10);
expectGuestMarkedForDeletionOk(11);
expectNewGuestCreated(12);
suspendAndResumeForUserSwitchingTests();
verifyUserRemoved(11);
verifyUserSwitched(12);
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_disabledByOEM_preDeleteGuestFail() throws Exception {
disableUserSwitchingDuringResume();
initTest();
setUserInfo(10, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
setInitialUser(10);
setCurrentUser(10);
expectGuestMarkedForDeletionFail(10);
suspendAndResumeForUserSwitchingTests();
verifyUserNotSwitched();
verifyNoGuestCreated();
verifyWtfNeverLogged();
}
@Test
public void testUserSwitchingOnResume_disabledByOEM_systemUser() throws Exception {
disableUserSwitchingDuringResume();
initTest();
setInitialUser(USER_SYSTEM);
setCurrentUser(10);
suspendAndResumeForUserSwitchingTests();
verifyUserNotSwitched();
// expects WTF
}
private void suspendAndResumeForUserSwitchingTests() throws Exception {
Log.d(TAG, "suspend()");
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP));
assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isFalse();
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, mWakeupTime);
mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
// Send the finished signal
Log.d(TAG, "resume()");
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.FINISHED, 0));
mSystemStateInterface.setWakeupCausedByTimer(true);
mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
mPowerSignalListener.waitForSleepExit(WAIT_TIMEOUT_MS);
mService.scheduleNextWakeupTime(mWakeupTime);
// second processing after wakeup
assertThat(mDisplayInterface.getDisplayState()).isFalse();
mService.setStateForTesting(/* isBooting= */ false, /* isResuming= */ true);
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isTrue();
// Should wait until Handler has finished ON processing.
CarServiceUtils.runOnLooperSync(mService.getHandlerThread().getLooper(), () -> { });
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP));
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, mWakeupTime);
mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.FINISHED, 0));
// PM will shutdown system as it was not woken-up due timer and it is not power on.
mSystemStateInterface.setWakeupCausedByTimer(false);
mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
// Since we just woke up from shutdown, wake up time will be 0
assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
assertThat(mDisplayInterface.getDisplayState()).isFalse();
}
private void registerListenerToService() {
ICarPowerStateListener listenerToService = new ICarPowerStateListener.Stub() {
@Override
public void onStateChanged(int state) throws RemoteException {
if (state == CarPowerStateListener.SHUTDOWN_ENTER
|| state == CarPowerStateListener.SUSPEND_ENTER) {
mFuture = new CompletableFuture<>();
mFuture.whenComplete((res, ex) -> {
if (ex == null) {
mService.finished(this);
}
});
} else {
mFuture = null;
}
}
};
mService.registerListener(listenerToService);
}
private void assertStateReceived(int expectedState, int expectedParam) throws Exception {
int[] state = mPowerHal.waitForSend(WAIT_TIMEOUT_MS);
assertThat(state[0]).isEqualTo(expectedState);
assertThat(state[1]).isEqualTo(expectedParam);
}
private void assertStateReceivedForShutdownOrSleepWithPostpone(
int lastState, long timeoutMs, int expectedParamForShutdownOrSuspend) throws Exception {
while (true) {
if (mFuture != null && !mFuture.isDone()) {
mFuture.complete(null);
}
int[] state = mPowerHal.waitForSend(timeoutMs);
if (state[0] == PowerHalService.SET_SHUTDOWN_POSTPONE) {
continue;
}
if (state[0] == lastState) {
assertThat(state[1]).isEqualTo(expectedParamForShutdownOrSuspend);
return;
}
}
}
// TODO: should be part of @After, but then it would hide the real test failure (if any). We'd
// need a custom rule (like CTS's SafeCleaner) for it...
private void verifyWtfNeverLogged() {
int size = mWtfs.size();
switch (size) {
case 0:
return;
case 1:
throw mWtfs.get(0);
default:
StringBuilder msg = new StringBuilder("wtf called ").append(size).append(" times")
.append(": ").append(mWtfs);
fail(msg.toString());
}
}
private static void waitForSemaphore(Semaphore semaphore, long timeoutMs)
throws InterruptedException {
if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
throw new IllegalStateException("timeout");
}
}
private void setInitialUser(int userId) {
when(mCarUserManagerHelper.getInitialUser()).thenReturn(userId);
}
private void setCurrentUser(int userId) {
when(ActivityManager.getCurrentUser()).thenReturn(userId);
}
private void setUserInfo(int userId, int flags) {
setUserInfo(userId, /* name= */ null, /* userType= */ null, flags);
}
private void setUserInfo(int userId, @Nullable String name, @Nullable String userType,
int flags) {
final UserInfo userInfo = new UserInfo();
userInfo.id = userId;
userInfo.name = name;
userInfo.flags = flags;
if (userType != null) {
userInfo.userType = userType;
}
Log.v(TAG, "UM.getUserInfo(" + userId + ") will return " + userInfo.toFullString());
when(mUserManager.getUserInfo(userId)).thenReturn(userInfo);
}
private void verifyUserNotSwitched() {
verify(mCarUserManagerHelper, never()).switchToUserId(anyInt());
}
private void verifyUserSwitched(int userId) {
verify(mCarUserManagerHelper, times(1)).switchToUserId(userId);
}
private void verifyNoGuestCreated() {
verify(mUserManager, never()).createGuest(notNull(), anyString());
}
private void expectGuestMarkedForDeletionOk(int userId) {
when(mUserManager.markGuestForDeletion(userId)).thenReturn(true);
}
private void expectGuestMarkedForDeletionFail(int userId) {
when(mUserManager.markGuestForDeletion(userId)).thenReturn(false);
}
private void expectNewGuestCreated(int userId) {
final UserInfo userInfo = new UserInfo();
userInfo.id = userId;
userInfo.name = NEW_GUEST_NAME;
when(mUserManager.createGuest(notNull(), eq(NEW_GUEST_NAME))).thenReturn(userInfo);
}
private void expectNewGuestCreationFailed(String name) {
when(mUserManager.createGuest(notNull(), eq(name))).thenReturn(null);
}
private void verifyUserRemoved(int userId) {
verify(mUserManager, times(1)).removeUser(userId);
}
private void verifyUserNotRemoved(int userId) {
verify(mUserManager, never()).removeUser(userId);
}
private void disableUserSwitchingDuringResume() {
mDisableUserSwitchDuringResume = true;
}
private static final class MockDisplayInterface implements DisplayInterface {
private boolean mDisplayOn = true;
private final Semaphore mDisplayStateWait = new Semaphore(0);
@Override
public void setDisplayBrightness(int brightness) {}
@Override
public synchronized void setDisplayState(boolean on) {
mDisplayOn = on;
mDisplayStateWait.release();
}
public synchronized boolean getDisplayState() {
return mDisplayOn;
}
public boolean waitForDisplayStateChange(long timeoutMs) throws Exception {
waitForSemaphore(mDisplayStateWait, timeoutMs);
return mDisplayOn;
}
@Override
public void startDisplayStateMonitoring(CarPowerManagementService service) {}
@Override
public void stopDisplayStateMonitoring() {}
@Override
public void refreshDisplayBrightness() {}
}
private static final class MockSystemStateInterface implements SystemStateInterface {
private final Semaphore mShutdownWait = new Semaphore(0);
private final Semaphore mSleepWait = new Semaphore(0);
private final Semaphore mSleepExitWait = new Semaphore(0);
private boolean mWakeupCausedByTimer = false;
@Override
public void shutdown() {
mShutdownWait.release();
}
public void waitForShutdown(long timeoutMs) throws Exception {
waitForSemaphore(mShutdownWait, timeoutMs);
}
@Override
public boolean enterDeepSleep() {
mSleepWait.release();
try {
mSleepExitWait.acquire();
} catch (InterruptedException e) {
}
return true;
}
public void waitForSleepEntryAndWakeup(long timeoutMs) throws Exception {
waitForSemaphore(mSleepWait, timeoutMs);
mSleepExitWait.release();
}
@Override
public void scheduleActionForBootCompleted(Runnable action, Duration delay) {}
@Override
public boolean isWakeupCausedByTimer() {
Log.i(TAG, "isWakeupCausedByTimer:" + mWakeupCausedByTimer);
return mWakeupCausedByTimer;
}
public synchronized void setWakeupCausedByTimer(boolean set) {
mWakeupCausedByTimer = set;
}
@Override
public boolean isSystemSupportingDeepSleep() {
return true;
}
}
private static final class MockWakeLockInterface implements WakeLockInterface {
@Override
public void releaseAllWakeLocks() {}
@Override
public void switchToPartialWakeLock() {}
@Override
public void switchToFullWakeLock() {}
}
private static final class MockIOInterface implements IOInterface {
private TemporaryDirectory mFilesDir;
@Override
public File getSystemCarDir() {
if (mFilesDir == null) {
try {
mFilesDir = new TemporaryDirectory(TAG);
} catch (IOException e) {
Log.e(TAG, "failed to create temporary directory", e);
fail("failed to create temporary directory. exception was: " + e);
}
}
return mFilesDir.getDirectory();
}
public void tearDown() {
if (mFilesDir != null) {
try {
mFilesDir.close();
} catch (Exception e) {
Log.w(TAG, "could not remove temporary directory", e);
}
}
}
}
private class PowerSignalListener implements MockedPowerHalService.SignalListener {
private final Semaphore mShutdownWait = new Semaphore(0);
private final Semaphore mSleepEntryWait = new Semaphore(0);
private final Semaphore mSleepExitWait = new Semaphore(0);
public void waitForSleepExit(long timeoutMs) throws Exception {
waitForSemaphore(mSleepExitWait, timeoutMs);
}
public void waitForShutdown(long timeoutMs) throws Exception {
waitForSemaphore(mShutdownWait, timeoutMs);
}
public void waitForSleepEntry(long timeoutMs) throws Exception {
waitForSemaphore(mSleepEntryWait, timeoutMs);
}
@Override
public void sendingSignal(int signal) {
if (signal == PowerHalService.SET_SHUTDOWN_START) {
mShutdownWait.release();
return;
}
if (signal == PowerHalService.SET_DEEP_SLEEP_ENTRY) {
mSleepEntryWait.release();
return;
}
if (signal == PowerHalService.SET_DEEP_SLEEP_EXIT) {
mSleepExitWait.release();
return;
}
}
}
@Retention(RUNTIME)
@Target({METHOD})
public static @interface WakeupTime {
int value();
}
}