| /* |
| * 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 com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; |
| |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.app.ActivityManager; |
| import android.car.hardware.power.CarPowerManager.CarPowerStateListener; |
| import android.car.hardware.power.ICarPowerStateListener; |
| import android.car.userlib.CarUserManagerHelper; |
| import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReq; |
| import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateShutdownParam; |
| 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 org.mockito.MockitoSession; |
| import org.mockito.quality.Strictness; |
| |
| 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 MockitoSession mSession; |
| private CarUserManagerHelper mCarUserManagerHelper; |
| private MockedPowerHalService mPowerHal; |
| private SystemInterface mSystemInterface; |
| private CarPowerManagementService mService; |
| private CompletableFuture<Void> mFuture; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| mSession = mockitoSession() |
| .strictness(Strictness.LENIENT) |
| .spyStatic(ActivityManager.class) |
| .startMocking(); |
| mPowerHal = new MockedPowerHalService(true /*isPowerStateSupported*/, |
| true /*isDeepSleepAllowed*/, true /*isTimedWakeupAllowed*/); |
| mSystemInterface = SystemInterface.Builder.defaultSystemInterface(getContext()) |
| .withDisplayInterface(mDisplayInterface) |
| .withSystemStateInterface(mSystemStateInterface) |
| .withWakeLockInterface(mWakeLockInterface) |
| .withIOInterface(mIOInterface).build(); |
| mCarUserManagerHelper = mock(CarUserManagerHelper.class); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| super.tearDown(); |
| if (mService != null) { |
| mService.release(); |
| } |
| mIOInterface.tearDown(); |
| mSession.finishMocking(); |
| } |
| |
| /** |
| * Helper method to create mService and initialize a test case |
| */ |
| private void initTest(int wakeupTime) throws Exception { |
| mService = new CarPowerManagementService(getContext(), mPowerHal, mSystemInterface, |
| mCarUserManagerHelper); |
| mService.init(); |
| CarPowerManagementService.setShutdownPrepareTimeout(0); |
| mPowerHal.setSignalListener(mPowerSignalListener); |
| if (wakeupTime > 0) { |
| registerListenerToService(); |
| mService.scheduleNextWakeupTime(wakeupTime); |
| } |
| assertStateReceived(MockedPowerHalService.SET_WAIT_FOR_VHAL, 0); |
| } |
| |
| public void testBootComplete() throws Exception { |
| initTest(0); |
| } |
| |
| public void testDisplayOn() throws Exception { |
| // start with display off |
| mSystemInterface.setDisplayState(false); |
| mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS); |
| initTest(0); |
| // Transition to ON state |
| mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 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); |
| |
| // Transition to ON state |
| mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0)); |
| assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)); |
| |
| 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); |
| assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)); |
| mPowerSignalListener.waitForShutdown(WAIT_TIMEOUT_MS); |
| mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS); |
| } |
| |
| public void testSleepImmediately() throws Exception { |
| initTest(0); |
| |
| // Transition to ON state |
| mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0)); |
| assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)); |
| |
| 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); |
| 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); |
| mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE, 0)); |
| assertStateReceivedForShutdownOrSleepWithPostpone( |
| PowerHalService.SET_SHUTDOWN_START, WAIT_TIMEOUT_LONG_MS, wakeupTime); |
| mPowerSignalListener.waitForShutdown(WAIT_TIMEOUT_MS); |
| // Send the finished signal |
| mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.FINISHED, 0)); |
| mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS); |
| } |
| |
| public void testSleepEntryAndWakeup() throws Exception { |
| final int wakeupTime = 100; |
| initTest(wakeupTime); |
| mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE, |
| VehicleApPowerStateShutdownParam.CAN_SLEEP)); |
| assertStateReceivedForShutdownOrSleepWithPostpone( |
| PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, wakeupTime); |
| 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); |
| } |
| |
| public void testSleepEntryAndWakeUpForProcessing() throws Exception { |
| final int wakeupTime = 100; |
| initTest(wakeupTime); |
| |
| // set up for user switching after display on |
| final int currentUserId = 10; |
| final int newUserId = 11; |
| when(mCarUserManagerHelper.getInitialUser()).thenReturn(newUserId); |
| when(ActivityManager.getCurrentUser()).thenReturn(currentUserId); |
| |
| mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0)); |
| assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)); |
| mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE, |
| VehicleApPowerStateShutdownParam.CAN_SLEEP)); |
| assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)); |
| assertStateReceivedForShutdownOrSleepWithPostpone( |
| PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, wakeupTime); |
| mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS); |
| // Send the finished signal |
| 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(wakeupTime); |
| // second processing after wakeup |
| assertFalse(mDisplayInterface.getDisplayState()); |
| // do not skip user switching part. |
| mService.clearIsBooting(); |
| mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0)); |
| assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)); |
| // user switching should have been requested. |
| verify(mCarUserManagerHelper, times(1)).switchToUserId(newUserId); |
| mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE, |
| VehicleApPowerStateShutdownParam.CAN_SLEEP)); |
| assertStateReceivedForShutdownOrSleepWithPostpone( |
| PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, wakeupTime); |
| mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS); |
| mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.FINISHED, 0)); |
| // PM will shutdown system as it was not woken-up due to 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); |
| assertFalse(mDisplayInterface.getDisplayState()); |
| } |
| |
| 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); |
| 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() {} |
| |
| @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; |
| } |
| } |
| } |
| } |