1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.car.cts; 18 19 import static com.google.common.truth.Truth.assertWithMessage; 20 21 import static org.junit.Assume.assumeTrue; 22 23 import com.android.car.power.CarPowerDumpProto; 24 import com.android.compatibility.common.util.PollingCheck; 25 import com.android.compatibility.common.util.ProtoUtils; 26 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 27 28 import org.junit.Before; 29 import org.junit.Test; 30 import org.junit.runner.RunWith; 31 32 import java.util.Objects; 33 import java.util.concurrent.Callable; 34 35 @RunWith(DeviceJUnit4ClassRunner.class) 36 public final class CarPowerHostTest extends CarHostJUnit4TestCase { 37 private static final long TIMEOUT_MS = 5_000; 38 private static final int SUSPEND_SEC = 3; 39 private static final long WAIT_FOR_SUSPEND_MS = SUSPEND_SEC * 2000; 40 private static final String PRODUCT_MODEL_PROPERTY = "ro.product.model"; 41 private static final String GOLDFISH_PROPERTY = "ro.kernel.qemu"; 42 private static final String CUTTLEFISH_DEVICE_NAME_PREFIX = "Cuttlefish"; 43 private static final String POWER_ON = "ON"; 44 private static final String POWER_STATE_PATTERN = 45 "mCurrentState:.*CpmsState=([A-Z_]+)\\(\\d+\\)"; 46 private static final String CMD_DUMPSYS_POWER = 47 "dumpsys car_service --services CarPowerManagementService --proto"; 48 private static final String ANDROID_CLIENT_SERVICE = "android.car.cts.app/.CarPowerTestService"; 49 private static final String TEST_COMMAND_HEADER = 50 "am start-foreground-service -n " + ANDROID_CLIENT_SERVICE + " --es power "; 51 private static final String LISTENER_DUMP_HEADER = "mListener set"; 52 private static final String RESULT_DUMP_HEADER = "mResultBuf"; 53 54 @Before setUp()55 public void setUp() throws Exception { 56 // Clear the previous logs 57 executeCommand("logcat -c"); 58 } 59 60 @Test testPowerStateOnAfterBootUp()61 public void testPowerStateOnAfterBootUp() throws Exception { 62 rebootDevice(); 63 64 PollingCheck.check("Power state is not ON", TIMEOUT_MS, 65 () -> getPowerState().equals(POWER_ON)); 66 } 67 68 @Test testSetListenerWithoutCompletion_suspendToRam()69 public void testSetListenerWithoutCompletion_suspendToRam() throws Exception { 70 // TODO(b/328617252): remove emulator check once ADB reconnection from suspend is stable 71 assumeEmulatorBuild(); 72 testSetListenerInternal(/* suspendType= */ "s2r", 73 /* completionType= */ "without-completion", 74 /* isSuspendAvailable= */ () -> isSuspendToRamAvailable(), 75 /* suspendDevice= */ () -> { 76 try { 77 suspendDeviceToRam(); 78 } catch (Exception e) { 79 throw new RuntimeException(e); 80 } 81 }); 82 } 83 84 @Test testSetListenerWithoutCompletion_suspendToDisk()85 public void testSetListenerWithoutCompletion_suspendToDisk() throws Exception { 86 // TODO(b/328617252): remove emulator check once ADB reconnection from suspend is stable 87 assumeEmulatorBuild(); 88 testSetListenerInternal(/* suspendType= */ "s2d", 89 /* completionType= */ "without-completion", 90 /* isSuspendAvailable= */ () -> isSuspendToDiskAvailable(), 91 /* suspendDevice= */ () -> { 92 try { 93 suspendDeviceToDisk(); 94 } catch (Exception e) { 95 throw new RuntimeException(e); 96 } 97 }); 98 } 99 100 @Test testSetListenerWithCompletion_suspendToRam()101 public void testSetListenerWithCompletion_suspendToRam() throws Exception { 102 // TODO(b/328617252): remove emulator check once ADB reconnection from suspend is stable 103 assumeEmulatorBuild(); 104 testSetListenerInternal(/* suspendType= */ "s2r", 105 /* completionType= */ "with-completion", 106 /* isSuspendAvailable= */ () -> isSuspendToRamAvailable(), 107 /* suspendDevice= */ () -> { 108 try { 109 suspendDeviceToRam(); 110 } catch (Exception e) { 111 throw new RuntimeException(e); 112 } 113 }); 114 } 115 116 @Test testSetListenerWithCompletion_suspendToDisk()117 public void testSetListenerWithCompletion_suspendToDisk() throws Exception { 118 // TODO(b/328617252): remove emulator check once ADB reconnection from suspend is stable 119 assumeEmulatorBuild(); 120 testSetListenerInternal(/* suspendType= */ "s2d", 121 /* completionType= */ "with-completion", 122 /* isSuspendAvailable= */ () -> isSuspendToDiskAvailable(), 123 /* suspendDevice= */ () -> { 124 try { 125 suspendDeviceToDisk(); 126 } catch (Exception e) { 127 throw new RuntimeException(e); 128 } 129 }); 130 } 131 rebootDevice()132 private void rebootDevice() throws Exception { 133 executeCommand("svc power reboot"); 134 getDevice().waitForDeviceAvailable(); 135 } 136 137 @SuppressWarnings("LiteProtoToString") getPowerState()138 private String getPowerState() throws Exception { 139 CarPowerDumpProto carPowerDump = 140 ProtoUtils.getProto(getDevice(), CarPowerDumpProto.parser(), CMD_DUMPSYS_POWER); 141 boolean hasPowerState = carPowerDump.getCurrentState().hasStateName(); 142 if (hasPowerState) { 143 return carPowerDump.getCurrentState().getStateName(); 144 } 145 throw new IllegalStateException( 146 "Proto doesn't have current_state.state_name field\n proto:" + carPowerDump); 147 } 148 testSetListenerInternal(String suspendType, String completionType, Callable<Boolean> isSuspendAvailable, Runnable suspendDevice)149 private void testSetListenerInternal(String suspendType, String completionType, 150 Callable<Boolean> isSuspendAvailable, Runnable suspendDevice) throws Exception { 151 assumeTrue(isSuspendAvailable.call()); 152 clearListener(); 153 setPowerStateListener(completionType, suspendType); 154 suspendDevice.run(); 155 // TODO(b/300515548): Replace sleep with a check that device has resumed from suspend 156 sleep(WAIT_FOR_SUSPEND_MS); 157 158 boolean statesMatchExpected = 159 listenerStatesMatchExpected(completionType, suspendType); 160 161 assertWithMessage("Listener " + completionType + " power states match expected after " 162 + suspendType).that(statesMatchExpected).isTrue(); 163 } 164 waitForPowerListenerSet()165 private void waitForPowerListenerSet() throws Exception { 166 PollingCheck.check("Wait for listener set timed out", TIMEOUT_MS, () -> { 167 String[] lines = fetchServiceDumpsys().split("\n"); 168 for (String line : lines) { 169 if (line.contains(LISTENER_DUMP_HEADER)) { 170 return line.split(":")[1].trim().equals("true"); 171 } 172 } 173 return false; 174 }); 175 } 176 waitForPowerListenerCleared()177 private void waitForPowerListenerCleared() throws Exception { 178 PollingCheck.check("Wait for listener cleared timed out", TIMEOUT_MS, () -> { 179 String[] lines = fetchServiceDumpsys().split("\n"); 180 for (String line : lines) { 181 if (line.contains(LISTENER_DUMP_HEADER)) { 182 return line.split(":")[1].trim().equals("false"); 183 } 184 } 185 return false; 186 }); 187 } 188 189 // TODO(b/328617252): remove this method once ADB reconnection from suspend is stable assumeEmulatorBuild()190 private void assumeEmulatorBuild() throws Exception { 191 String productModel = Objects.requireNonNullElse( 192 getDevice().getProperty(PRODUCT_MODEL_PROPERTY), ""); 193 String goldfishBuildProperty = Objects.requireNonNullElse( 194 getDevice().getProperty(GOLDFISH_PROPERTY), ""); 195 assumeTrue(productModel.startsWith(CUTTLEFISH_DEVICE_NAME_PREFIX) 196 || goldfishBuildProperty.equals("1")); 197 } 198 isSuspendSupported(String suspendType)199 private boolean isSuspendSupported(String suspendType) throws Exception { 200 String suspendDumpHeader; 201 if (suspendType.equals("S2R")) { 202 suspendDumpHeader = "kernel support S2R: "; 203 } else if (suspendType.equals("S2D")) { 204 suspendDumpHeader = "kernel support S2D: "; 205 } else { 206 throw new IllegalArgumentException( 207 "Suspend type %s" + suspendType + " is not supported"); 208 } 209 String cpmsDump = executeCommand(CMD_DUMPSYS_POWER); 210 String[] lines = cpmsDump.split("\n"); 211 for (String line : lines) { 212 if (line.contains(suspendDumpHeader)) { 213 return line.split(":")[1].trim().equals("true"); 214 } 215 } 216 return false; 217 } 218 isSuspendToRamAvailable()219 private boolean isSuspendToRamAvailable() throws Exception { 220 CarPowerDumpProto carPowerDump = 221 ProtoUtils.getProto(getDevice(), CarPowerDumpProto.parser(), CMD_DUMPSYS_POWER); 222 return carPowerDump.getKernelSupportsDeepSleep(); 223 } 224 isSuspendToDiskAvailable()225 private boolean isSuspendToDiskAvailable() throws Exception { 226 CarPowerDumpProto carPowerDump = 227 ProtoUtils.getProto(getDevice(), CarPowerDumpProto.parser(), CMD_DUMPSYS_POWER); 228 return carPowerDump.getKernelSupportsHibernation(); 229 } 230 setPowerStateListener(String completionType, String suspendType)231 private void setPowerStateListener(String completionType, String suspendType) throws Exception { 232 executeCommand("%s set-listener,%s,%s", TEST_COMMAND_HEADER, completionType, 233 suspendType); 234 waitForPowerListenerSet(); 235 } 236 clearListener()237 private void clearListener() throws Exception { 238 executeCommand("%s clear-listener", TEST_COMMAND_HEADER); 239 waitForPowerListenerCleared(); 240 } 241 listenerStatesMatchExpected(String completionType, String suspendType)242 private boolean listenerStatesMatchExpected(String completionType, String suspendType) 243 throws Exception { 244 executeCommand("%s get-listener-states-results,%s,%s", TEST_COMMAND_HEADER, 245 completionType, suspendType); 246 String[] lines = fetchServiceDumpsys().split("\n"); 247 for (String line : lines) { 248 if (line.contains(RESULT_DUMP_HEADER)) { 249 return line.split(":")[1].trim().contains("true"); 250 } 251 } 252 return false; 253 } 254 suspendDeviceToRam()255 private void suspendDeviceToRam() throws Exception { 256 executeCommand("cmd car_service suspend --simulate --skip-garagemode --wakeup-after " 257 + SUSPEND_SEC); 258 } 259 suspendDeviceToDisk()260 private void suspendDeviceToDisk() throws Exception { 261 executeCommand("cmd car_service hibernate --simulate --skip-garagemode --wakeup-after " 262 + SUSPEND_SEC); 263 } 264 fetchServiceDumpsys()265 public String fetchServiceDumpsys() throws Exception { 266 return executeCommand("dumpsys activity service %s", ANDROID_CLIENT_SERVICE); 267 } 268 } 269