• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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