Create CarPowerManagerUnitTest

Create unit tests for CarPowerManager

Fixes: 154546484
Test: This is the test
Change-Id: Icd8618239f1e908982c075ee155aebdc60eb1158
diff --git a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java b/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
index af50638..ab772a9 100644
--- a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
+++ b/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
@@ -19,6 +19,7 @@
 import android.util.Log;
 
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -33,7 +34,7 @@
      *
      * @param timeoutMs how long to wait for
      *
-     * @throws IllegalStateException} if it times out.
+     * @throws {@link IllegalStateException} if it times out.
      */
     public static void await(@NonNull CountDownLatch latch, long timeoutMs)
             throws InterruptedException {
@@ -43,6 +44,20 @@
     }
 
     /**
+     * Waits for a semaphore.
+     *
+     * @param timeoutMs how long to wait for
+     *
+     * @throws {@link IllegalStateException} if it times out.
+     */
+    public static void await(@NonNull Semaphore semaphore, long timeoutMs)
+            throws InterruptedException {
+        if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
+            throw new IllegalStateException(semaphore + " not released in " + timeoutMs + " ms");
+        }
+    }
+
+    /**
      * Silently waits for a latch to be counted down, without throwing any exception if it isn't.
      *
      * @param timeoutMs how long to wait for
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index d130c43..7446d35 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -169,7 +169,7 @@
     }
 
     @VisibleForTesting
-    CarPowerManagementService(Context context, Resources resources, PowerHalService powerHal,
+    public CarPowerManagementService(Context context, Resources resources, PowerHalService powerHal,
             SystemInterface systemInterface, UserManager userManager,
             CarUserService carUserService, InitialUserSetter initialUserSetter) {
         mContext = context;
@@ -194,7 +194,7 @@
     }
 
     @VisibleForTesting
-    protected void setShutdownTimersForTest(int pollingIntervalMs, int shutdownTimeoutMs) {
+    public void setShutdownTimersForTest(int pollingIntervalMs, int shutdownTimeoutMs) {
         // Override timers to keep testing time short
         // Passing in '0' resets the value to the default
         synchronized (mLock) {
diff --git a/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
new file mode 100644
index 0000000..29022f5
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2020 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.hardware.power;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.car.Car;
+import android.car.hardware.power.CarPowerManager;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.mocks.JavaMockitoHelper;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReq;
+import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateShutdownParam;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.CarPowerManagementService;
+import com.android.car.MockedPowerHalService;
+import com.android.car.R;
+import com.android.car.hal.PowerHalService;
+import com.android.car.hal.PowerHalService.PowerState;
+import com.android.car.systeminterface.DisplayInterface;
+import com.android.car.systeminterface.SystemInterface;
+import com.android.car.systeminterface.SystemStateInterface;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.time.Duration;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+
+@SmallTest
+public class CarPowerManagerUnitTest extends AbstractExtendedMockitoTestCase {
+    private static final String TAG = CarPowerManagerUnitTest.class.getSimpleName();
+    private static final long WAIT_TIMEOUT_MS = 20;
+    private static final long WAIT_TIMEOUT_LONG_MS = 50;
+
+    private final MockDisplayInterface mDisplayInterface = new MockDisplayInterface();
+    private final MockSystemStateInterface mSystemStateInterface = new MockSystemStateInterface();
+    private final Context mContext =
+            InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+    private MockedPowerHalService mPowerHal;
+    private SystemInterface mSystemInterface;
+    private CarPowerManagementService mService;
+    private CarPowerManager mCarPowerManager;
+
+    @Mock
+    private Resources mResources;
+    @Mock
+    private Car mCar;
+
+    @Before
+    public void setUp() throws Exception {
+        mPowerHal = new MockedPowerHalService(true /*isPowerStateSupported*/,
+                true /*isDeepSleepAllowed*/, true /*isTimedWakeupAllowed*/);
+        mSystemInterface = SystemInterface.Builder.defaultSystemInterface(mContext)
+            .withDisplayInterface(mDisplayInterface)
+            .withSystemStateInterface(mSystemStateInterface)
+            .build();
+        setService();
+        mCarPowerManager = new CarPowerManager(mCar, mService);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mService != null) {
+            mService.release();
+        }
+    }
+
+    @Test
+    public void testRequestShutdownOnNextSuspend_positive() throws Exception {
+        setPowerOn();
+        // Tell it to shutdown
+        mCarPowerManager.requestShutdownOnNextSuspend();
+        // Request suspend
+        setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+                VehicleApPowerStateShutdownParam.CAN_SLEEP);
+        // Verify shutdown
+        assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_SHUTDOWN_START, 0);
+    }
+
+    @Test
+    public void testRequestShutdownOnNextSuspend_negative() throws Exception {
+        setPowerOn();
+
+        // Do not tell it to shutdown
+
+        // Request suspend
+        setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+                VehicleApPowerStateShutdownParam.CAN_SLEEP);
+        // Verify suspend
+        assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
+    }
+
+    @Test
+    public void testScheduleNextWakeupTime() throws Exception {
+        setPowerOn();
+
+        int wakeTime = 1234;
+        mCarPowerManager.scheduleNextWakeupTime(wakeTime);
+
+        setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+                VehicleApPowerStateShutdownParam.CAN_SLEEP);
+
+        // Verify that we suspended with the requested wake-up time
+        assertStateReceivedForShutdownOrSleepWithPostpone(
+                PowerHalService.SET_DEEP_SLEEP_ENTRY, wakeTime);
+    }
+
+    @Test
+    public void testSetListener() throws Exception {
+        setPowerOn();
+
+        WaitablePowerStateListener listener = new WaitablePowerStateListener(2);
+
+        setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+                VehicleApPowerStateShutdownParam.CAN_SLEEP);
+
+        assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
+
+        int finalState = listener.await();
+        assertThat(finalState).isEqualTo(PowerHalService.SET_DEEP_SLEEP_ENTRY);
+    }
+
+    @Test
+    public void testSetListenerWithCompletion() throws Exception {
+        setPowerOn();
+
+        WaitablePowerStateListenerWithCompletion listener =
+                new WaitablePowerStateListenerWithCompletion(2);
+
+        setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+                VehicleApPowerStateShutdownParam.CAN_SLEEP);
+        assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
+
+        int finalState = listener.await();
+        assertThat(finalState).isEqualTo(PowerHalService.SET_DEEP_SLEEP_ENTRY);
+    }
+
+    @Test
+    public void testClearListener() throws Exception {
+        setPowerOn();
+
+        // Set a listener
+        WaitablePowerStateListener listener = new WaitablePowerStateListener(1);
+
+        mCarPowerManager.clearListener();
+
+        setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+                VehicleApPowerStateShutdownParam.CAN_SLEEP);
+
+        assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
+        // Verify that the listener didn't run
+        assertThrows(IllegalStateException.class, () -> listener.await());
+    }
+
+    @Test
+    public void testGetPowerState() throws Exception {
+        setPowerOn();
+        assertThat(mCarPowerManager.getPowerState()).isEqualTo(PowerHalService.SET_ON);
+
+        // Request suspend
+        setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+                        VehicleApPowerStateShutdownParam.CAN_SLEEP);
+        assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
+        assertThat(mCarPowerManager.getPowerState())
+                .isEqualTo(PowerHalService.SET_DEEP_SLEEP_ENTRY);
+    }
+
+    /**
+     * Helper method to create mService and initialize a test case
+     */
+    private void setService() throws Exception {
+        Log.i(TAG, "setService(): overridden overlay properties: "
+                + "config_disableUserSwitchDuringResume="
+                + mResources.getBoolean(R.bool.config_disableUserSwitchDuringResume)
+                + ", maxGarageModeRunningDurationInSecs="
+                + mResources.getInteger(R.integer.maxGarageModeRunningDurationInSecs));
+        mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
+                mSystemInterface, null, null, null);
+        mService.init();
+        mService.setShutdownTimersForTest(0, 0);
+        assertStateReceived(MockedPowerHalService.SET_WAIT_FOR_VHAL, 0);
+    }
+
+    private void assertStateReceived(int expectedState, int expectedParam) throws Exception {
+        int[] state = mPowerHal.waitForSend(WAIT_TIMEOUT_MS);
+        assertThat(state).asList().containsExactly(expectedState, expectedParam).inOrder();
+    }
+
+    /**
+     * Helper method to get the system into ON
+     */
+    private void setPowerOn() throws Exception {
+        setPowerState(VehicleApPowerStateReq.ON, 0);
+        assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isTrue();
+    }
+
+    /**
+     * Helper to set the PowerHal state
+     *
+     * @param stateEnum Requested state enum
+     * @param stateParam Addition state parameter
+     */
+    private void setPowerState(int stateEnum, int stateParam) {
+        mPowerHal.setCurrentPowerState(new PowerState(stateEnum, stateParam));
+    }
+
+    private void assertStateReceivedForShutdownOrSleepWithPostpone(
+            int lastState, int stateParameter) throws Exception {
+        long startTime = System.currentTimeMillis();
+        while (true) {
+            int[] state = mPowerHal.waitForSend(WAIT_TIMEOUT_LONG_MS);
+            if (state[0] == lastState) {
+                assertThat(state[1]).isEqualTo(stateParameter);
+                return;
+            }
+            assertThat(state[0]).isEqualTo(PowerHalService.SET_SHUTDOWN_POSTPONE);
+            assertThat(System.currentTimeMillis() - startTime).isLessThan(WAIT_TIMEOUT_LONG_MS);
+        }
+    }
+
+    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 {
+            JavaMockitoHelper.await(mDisplayStateWait, timeoutMs);
+            return mDisplayOn;
+        }
+
+        @Override
+        public void startDisplayStateMonitoring(CarPowerManagementService service) {}
+
+        @Override
+        public void stopDisplayStateMonitoring() {}
+
+        @Override
+        public void refreshDisplayBrightness() {}
+    }
+
+    /**
+     * Helper class to set a power-state listener,
+     * verify that the listener gets called the
+     * right number of times, and return the final
+     * power state.
+     */
+    private final class WaitablePowerStateListener {
+        private final CountDownLatch mLatch;
+        private int mListenedState = -1;
+        WaitablePowerStateListener(int initialCount) {
+            mLatch = new CountDownLatch(initialCount);
+            mCarPowerManager.setListener(
+                    (state) -> {
+                        mListenedState = state;
+                        mLatch.countDown();
+                    });
+        }
+
+        int await() throws Exception {
+            JavaMockitoHelper.await(mLatch, WAIT_TIMEOUT_MS);
+            return mListenedState;
+        }
+    }
+
+    /**
+     * Helper class to set a power-state listener with completion,
+     * verify that the listener gets called the right number of times,
+     * verify that the CompletableFuture is provided, complete the
+     * CompletableFuture, and return the final power state.
+     */
+    private final class WaitablePowerStateListenerWithCompletion {
+        private final CountDownLatch mLatch;
+        private int mListenedState = -1;
+        WaitablePowerStateListenerWithCompletion(int initialCount) {
+            mLatch = new CountDownLatch(initialCount);
+            mCarPowerManager.setListenerWithCompletion(
+                    (state, future) -> {
+                        mListenedState = state;
+                        if (state == PowerHalService.SET_SHUTDOWN_PREPARE) {
+                            assertThat(future).isNotNull();
+                            future.complete(null);
+                        } else {
+                            assertThat(future).isNull();
+                        }
+                        mLatch.countDown();
+                    });
+        }
+
+        int await() throws Exception {
+            JavaMockitoHelper.await(mLatch, WAIT_TIMEOUT_MS);
+            return mListenedState;
+        }
+    }
+
+    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 {
+            JavaMockitoHelper.await(mShutdownWait, timeoutMs);
+        }
+
+        @Override
+        public boolean enterDeepSleep() {
+            mSleepWait.release();
+            try {
+                mSleepExitWait.acquire();
+            } catch (InterruptedException e) {
+            }
+            return true;
+        }
+
+        public void waitForSleepEntryAndWakeup(long timeoutMs) throws Exception {
+            JavaMockitoHelper.await(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;
+        }
+    }
+}