blob: 55ae88d3f082567b653d094dd8f47d08597b5c73 [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 android.car.hardware.power.CarPowerManager.CarPowerStateListener;
import android.car.hardware.power.ICarPowerStateListener;
import android.os.RemoteException;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
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 java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@SmallTest
public class CarPowerManagementServiceTest extends AndroidTestCase {
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 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 MockedPowerHalService mPowerHal;
private SystemInterface mSystemInterface;
private CarPowerManagementService mService;
private CompletableFuture<Void> mFuture;
@Override
protected void setUp() throws Exception {
super.setUp();
mPowerHal = new MockedPowerHalService(true /*isPowerStateSupported*/,
true /*isDeepSleepAllowed*/, true /*isTimedWakeupAllowed*/);
mSystemInterface = SystemInterface.Builder.defaultSystemInterface(getContext())
.withDisplayInterface(mDisplayInterface)
.withSystemStateInterface(mSystemStateInterface)
.withWakeLockInterface(mWakeLockInterface)
.withIOInterface(mIOInterface).build();
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
if (mService != null) {
mService.release();
}
mIOInterface.tearDown();
}
/**
* Helper method to create mService and initialize a test case
*/
private void initTest(int wakeupTime) throws Exception {
mService = new CarPowerManagementService(getContext(), mPowerHal, mSystemInterface);
mService.init();
mPowerHal.setSignalListener(mPowerSignalListener);
if (wakeupTime > 0) {
registerListenerToService();
mService.scheduleNextWakeupTime(wakeupTime);
}
assertStateReceived(MockedPowerHalService.SET_BOOT_COMPLETE, 0);
}
public void testBootComplete() throws Exception {
initTest(0);
}
public void testDisplayOff() throws Exception {
initTest(0);
// it will call display on for initial state
assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
mPowerHal.setCurrentPowerState(new PowerState(PowerHalService.STATE_ON_DISP_OFF, 0));
assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
}
public void testDisplayOn() throws Exception {
// start with display off
mSystemInterface.setDisplayState(false);
mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS);
initTest(0);
// display should be turned on as it started with off state.
assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
}
public void testShutdown() throws Exception {
initTest(0);
assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
mPowerHal.setCurrentPowerState(
new PowerState(
PowerHalService.STATE_SHUTDOWN_PREPARE,
PowerHalService.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);
assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
mPowerSignalListener.waitForShutdown(WAIT_TIMEOUT_MS);
mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS);
}
public void testShutdownWithProcessing() throws Exception {
final int wakeupTime = 100;
initTest(wakeupTime);
assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
mPowerHal.setCurrentPowerState(new PowerState(PowerHalService.STATE_SHUTDOWN_PREPARE, 0));
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_SHUTDOWN_START, WAIT_TIMEOUT_LONG_MS, wakeupTime);
assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
mPowerSignalListener.waitForShutdown(WAIT_TIMEOUT_MS);
mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS);
}
public void testSleepEntryAndWakeup() throws Exception {
final int wakeupTime = 100;
initTest(wakeupTime);
assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
mPowerHal.setCurrentPowerState(new PowerState(PowerHalService.STATE_SHUTDOWN_PREPARE,
PowerHalService.SHUTDOWN_CAN_SLEEP));
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, 0);
assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
int wakeupTimeReceived = mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
assertEquals(wakeupTime, wakeupTimeReceived);
assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
mPowerSignalListener.waitForSleepExit(WAIT_TIMEOUT_MS);
}
public void testSleepEntryAndPowerOnWithProcessing() throws Exception {
final int wakeupTime = 100;
initTest(wakeupTime);
assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
mPowerHal.setCurrentPowerState(new PowerState(PowerHalService.STATE_SHUTDOWN_PREPARE,
PowerHalService.SHUTDOWN_CAN_SLEEP));
assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, 0);
mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
// set power on here without notification. PowerManager should check the state after sleep
// exit
mPowerHal.setCurrentPowerState(new PowerState(PowerHalService.STATE_ON_DISP_OFF, 0), false);
int wakeupTimeReceived = mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
assertEquals(wakeupTime, wakeupTimeReceived);
assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
mPowerSignalListener.waitForSleepExit(WAIT_TIMEOUT_MS);
}
public void testSleepEntryAndWakeUpForProcessing() throws Exception {
final int wakeupTime = 100;
initTest(wakeupTime);
assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
mPowerHal.setCurrentPowerState(new PowerState(PowerHalService.STATE_SHUTDOWN_PREPARE,
PowerHalService.SHUTDOWN_CAN_SLEEP));
assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, 0);
mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
mSystemStateInterface.setWakeupCausedByTimer(true);
int wakeupTimeReceived = mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
assertEquals(wakeupTime, wakeupTimeReceived);
assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
mPowerSignalListener.waitForSleepExit(WAIT_TIMEOUT_MS);
mService.scheduleNextWakeupTime(wakeupTime);
// second processing after wakeup
assertFalse(mDisplayInterface.getDisplayState());
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, 0);
mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
// PM will shutdown system as it was not woken-up due to timer and it is not power on.
mSystemStateInterface.setWakeupCausedByTimer(false);
wakeupTimeReceived = mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
assertEquals(wakeupTime, wakeupTimeReceived);
assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
// Since we just woke up from shutdown, wake up time will be 0
assertStateReceived(PowerHalService.SET_SHUTDOWN_START, 0);
mPowerSignalListener.waitForShutdown(WAIT_TIMEOUT_MS);
mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS);
assertFalse(mDisplayInterface.getDisplayState());
}
private void registerListenerToService() {
ICarPowerStateListener listenerToService = new ICarPowerStateListener.Stub() {
@Override
public void onStateChanged(int state, int token) throws RemoteException {
if (state == CarPowerStateListener.SHUTDOWN_ENTER
|| state == CarPowerStateListener.SUSPEND_ENTER) {
mFuture = new CompletableFuture<>();
mFuture.whenComplete((res, ex) -> {
if (ex == null) {
mService.finished(this, token);
}
});
} else {
mFuture = null;
}
}
};
mService.registerListener(listenerToService);
}
private void assertStateReceived(int expectedState, int expectedParam) throws Exception {
int[] state = mPowerHal.waitForSend(WAIT_TIMEOUT_MS);
assertEquals(expectedState, state[0]);
assertEquals(expectedParam, state[1]);
}
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) {
assertEquals(expectedParamForShutdownOrSuspend, state[1]);
return;
}
}
}
private static void waitForSemaphore(Semaphore semaphore, long timeoutMs)
throws InterruptedException {
if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
throw new IllegalStateException("timeout");
}
}
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() {}
}
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 int mWakeupTime;
private boolean mWakeupCausedByTimer = false;
@Override
public void shutdown() {
mShutdownWait.release();
}
public void waitForShutdown(long timeoutMs) throws Exception {
waitForSemaphore(mShutdownWait, timeoutMs);
}
@Override
public boolean enterDeepSleep(int wakeupTimeSec) {
mWakeupTime = wakeupTimeSec;
mSleepWait.release();
try {
mSleepExitWait.acquire();
} catch (InterruptedException e) {
}
return true;
}
public int waitForSleepEntryAndWakeup(long timeoutMs) throws Exception {
waitForSemaphore(mSleepWait, timeoutMs);
mSleepExitWait.release();
return mWakeupTime;
}
@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 getFilesDir() {
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;
}
}
}
}