1 /*
2  * Copyright (C) 2020 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.cts.statsdatom.lib;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import android.service.battery.BatteryServiceDumpProto;
23 
24 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
25 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
26 import com.android.ddmlib.testrunner.TestResult.TestStatus;
27 import com.android.tradefed.build.IBuildInfo;
28 import com.android.tradefed.device.CollectingByteOutputReceiver;
29 import com.android.tradefed.device.DeviceNotAvailableException;
30 import com.android.tradefed.device.ITestDevice;
31 import com.android.tradefed.log.LogUtil;
32 import com.android.tradefed.log.LogUtil.CLog;
33 import com.android.tradefed.result.CollectingTestListener;
34 import com.android.tradefed.result.ITestInvocationListener;
35 import com.android.tradefed.result.ResultForwarder;
36 import com.android.tradefed.result.TestDescription;
37 import com.android.tradefed.result.TestResult;
38 import com.android.tradefed.result.TestRunResult;
39 import com.android.tradefed.util.Pair;
40 import com.android.tradefed.util.RunUtil;
41 
42 import com.google.protobuf.ExtensionRegistry;
43 import com.google.protobuf.InvalidProtocolBufferException;
44 import com.google.protobuf.MessageLite;
45 import com.google.protobuf.Parser;
46 
47 import java.io.FileNotFoundException;
48 import java.util.Map;
49 import java.util.Objects;
50 import java.util.StringTokenizer;
51 
52 import javax.annotation.Nonnull;
53 import javax.annotation.Nullable;
54 
55 /**
56  * Contains utility functions for interacting with the device.
57  * Largely copied from incident's ProtoDumpTestCase.
58  */
59 public final class DeviceUtils {
60     public static final String STATSD_ATOM_TEST_APK = "CtsStatsdAtomApp.apk";
61     public static final String STATSD_ATOM_TEST_PKG = "com.android.server.cts.device.statsdatom";
62 
63     private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
64 
65     private static final String KEY_ACTION = "action";
66 
67     // feature names
68     public static final String FEATURE_WATCH = "android.hardware.type.watch";
69 
70     public static final String DUMP_BATTERY_CMD = "dumpsys battery";
71 
72     /**
73      * Runs device side tests.
74      *
75      * @param device Can be retrieved by running getDevice() in a class that extends DeviceTestCase
76      * @param pkgName Test package name, such as "com.android.server.cts.statsdatom"
77      * @param testClassName Test class name which can either be a fully qualified name or "." + a
78      *     class name; if null, all test in the package will be run
79      * @param testMethodName Test method name; if null, all tests in class or package will be run
80      * @return {@link TestRunResult} of this invocation
81      * @throws DeviceNotAvailableException
82      */
runDeviceTests(ITestDevice device, String pkgName, @Nullable String testClassName, @Nullable String testMethodName)83     public static @Nonnull TestRunResult runDeviceTests(ITestDevice device, String pkgName,
84             @Nullable String testClassName, @Nullable String testMethodName)
85             throws DeviceNotAvailableException {
86         return internalRunDeviceTests(device, pkgName, testClassName, testMethodName, null);
87     }
88 
89     /**
90      * Runs device side tests.
91      *
92      * @param device Can be retrieved by running getDevice() in a class that extends DeviceTestCase
93      * @param pkgName Test package name, such as "com.android.server.cts.statsdatom"
94      * @param testClassName Test class name which can either be a fully qualified name or "." + a
95      *     class name; if null, all test in the package will be run
96      * @param testMethodName Test method name; if null, all tests in class or package will be run
97      * @param listener Listener for test results from the test invocation.
98      * @return {@link TestRunResult} of this invocation
99      * @throws DeviceNotAvailableException
100      */
runDeviceTests(ITestDevice device, String pkgName, @Nullable String testClassName, @Nullable String testMethodName, ITestInvocationListener listener)101     public static @Nonnull TestRunResult runDeviceTests(ITestDevice device, String pkgName,
102             @Nullable String testClassName, @Nullable String testMethodName,
103             ITestInvocationListener listener)
104             throws DeviceNotAvailableException {
105         return internalRunDeviceTests(device, pkgName, testClassName, testMethodName, listener);
106     }
107 
internalRunDeviceTests(ITestDevice device, String pkgName, @Nullable String testClassName, @Nullable String testMethodName, @Nullable ITestInvocationListener otherListener)108     private static @Nonnull TestRunResult internalRunDeviceTests(ITestDevice device, String pkgName,
109             @Nullable String testClassName, @Nullable String testMethodName,
110             @Nullable ITestInvocationListener otherListener)
111             throws DeviceNotAvailableException {
112         if (testClassName != null && testClassName.startsWith(".")) {
113             testClassName = pkgName + testClassName;
114         }
115 
116         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
117                 pkgName, TEST_RUNNER, device.getIDevice());
118         if (testClassName != null && testMethodName != null) {
119             testRunner.setMethodName(testClassName, testMethodName);
120         } else if (testClassName != null) {
121             testRunner.setClassName(testClassName);
122         }
123 
124         CollectingTestListener collectingTestListener = new CollectingTestListener();
125         ITestInvocationListener combinedLister;
126         if (otherListener != null) {
127             combinedLister = new ResultForwarder(otherListener, collectingTestListener);
128         } else {
129             combinedLister = collectingTestListener;
130         }
131 
132         assertThat(device.runInstrumentationTests(testRunner, combinedLister)).isTrue();
133 
134         final TestRunResult result = collectingTestListener.getCurrentRunResults();
135         if (result.isRunFailure()) {
136             throw new Error("Failed to successfully run device tests for "
137                     + result.getName() + ": " + result.getRunFailureMessage());
138         }
139         if (result.getNumTests() == 0) {
140             throw new Error("No tests were run on the device");
141         }
142         if (result.hasFailedTests()) {
143             StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
144             for (Map.Entry<TestDescription, TestResult> resultEntry :
145                     result.getTestResults().entrySet()) {
146                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
147                     errorBuilder.append(resultEntry.getKey().toString());
148                     errorBuilder.append(":\n");
149                     errorBuilder.append(resultEntry.getValue().getStackTrace());
150                 }
151             }
152             throw new AssertionError(errorBuilder.toString());
153         }
154         return result;
155     }
156 
157     /**
158      * Runs device side tests from the com.android.server.cts.device.statsdatom package.
159      */
runDeviceTestsOnStatsdApp(ITestDevice device, @Nullable String testClassName, @Nullable String testMethodName)160     public static @Nonnull TestRunResult runDeviceTestsOnStatsdApp(ITestDevice device,
161             @Nullable String testClassName, @Nullable String testMethodName)
162             throws DeviceNotAvailableException {
163         return runDeviceTests(device, STATSD_ATOM_TEST_PKG, testClassName, testMethodName);
164     }
165 
166     /**
167      * Install the statsdatom CTS app to the device.
168      */
installStatsdTestApp(ITestDevice device, IBuildInfo ctsBuildInfo)169     public static void installStatsdTestApp(ITestDevice device, IBuildInfo ctsBuildInfo)
170             throws FileNotFoundException, DeviceNotAvailableException {
171         installTestApp(device, STATSD_ATOM_TEST_APK, STATSD_ATOM_TEST_PKG, ctsBuildInfo);
172     }
173 
174     /**
175      * Install a test app to the device.
176      */
installTestApp(ITestDevice device, String apkName, String pkgName, IBuildInfo ctsBuildInfo)177     public static void installTestApp(ITestDevice device, String apkName, String pkgName,
178             IBuildInfo ctsBuildInfo) throws FileNotFoundException, DeviceNotAvailableException {
179         CLog.d("Installing app " + apkName);
180         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(ctsBuildInfo);
181         final String result = device.installPackage(
182                 buildHelper.getTestFile(apkName), /*reinstall=*/true, /*grantPermissions=*/true);
183         assertWithMessage("Failed to install " + apkName + ": " + result).that(result).isNull();
184         allowBackgroundServices(device, pkgName);
185     }
186 
187     /**
188      * Required to successfully start a background service from adb, starting in O.
189      */
allowBackgroundServices(ITestDevice device, String pkgName)190     private static void allowBackgroundServices(ITestDevice device, String pkgName)
191             throws DeviceNotAvailableException {
192         String cmd = "cmd deviceidle tempwhitelist " + pkgName;
193         device.executeShellCommand(cmd);
194     }
195 
196     /**
197      * Uninstall the statsdatom CTS app from the device.
198      */
uninstallStatsdTestApp(ITestDevice device)199     public static void uninstallStatsdTestApp(ITestDevice device) throws Exception {
200         uninstallTestApp(device, STATSD_ATOM_TEST_PKG);
201     }
202 
203     /**
204      * Uninstall the test app from the device.
205      */
uninstallTestApp(ITestDevice device, String pkgName)206     public static void uninstallTestApp(ITestDevice device, String pkgName) throws Exception {
207         device.uninstallPackage(pkgName);
208     }
209 
210     /**
211      * Run an adb shell command on device and parse the results as a proto of a given type.
212      *
213      * @param device Device to run cmd on
214      * @param parser Protobuf parser object, which can be retrieved by running MyProto.parser()
215      * @param extensionRegistry ExtensionRegistry containing extensions that should be parsed
216      * @param cmd The adb shell command to run (e.g. "cmd stats update config")
217      *
218      * @throws DeviceNotAvailableException
219      * @throws InvalidProtocolBufferException Occurs if there was an error parsing the proto. Note
220      *     that a 0 length buffer is not necessarily an error.
221      * @return Proto of specified type
222      */
getShellCommandOutput(@onnull ITestDevice device, Parser<T> parser, ExtensionRegistry extensionRegistry, String cmd)223     public static <T extends MessageLite> T getShellCommandOutput(@Nonnull ITestDevice device,
224             Parser<T> parser, ExtensionRegistry extensionRegistry, String cmd)
225             throws DeviceNotAvailableException, InvalidProtocolBufferException {
226         final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
227         device.executeShellCommand(cmd, receiver);
228         try {
229             return parser.parseFrom(receiver.getOutput(), extensionRegistry);
230         } catch (Exception ex) {
231             CLog.d("Error parsing " + parser.getClass().getCanonicalName() + " for cmd " + cmd);
232             throw ex;
233         }
234     }
235 
getShellCommandOutput( @onnull ITestDevice device, Parser<T> parser, String cmd)236     public static <T extends MessageLite> T getShellCommandOutput(
237             @Nonnull ITestDevice device, Parser<T> parser, String cmd)
238             throws DeviceNotAvailableException, InvalidProtocolBufferException {
239         return getShellCommandOutput(device, parser, ExtensionRegistry.getEmptyRegistry(), cmd);
240     }
241 
242     /**
243      * Returns the UID of the host, which should always either be AID_SHELL (2000) or AID_ROOT (0).
244      */
getHostUid(ITestDevice device)245     public static int getHostUid(ITestDevice device) throws DeviceNotAvailableException {
246         String uidString = "";
247         try {
248             uidString = device.executeShellCommand("id -u");
249             return Integer.parseInt(uidString.trim());
250         } catch (NumberFormatException ex) {
251             CLog.e("Failed to get host's uid via shell command. Found " + uidString);
252             // Fall back to alternative method...
253             if (device.isAdbRoot()) {
254                 return 0;
255             } else {
256                 return 2000; // SHELL
257             }
258         }
259     }
260 
261     /**
262      * Returns the UID of the statsdatom CTS test app.
263      */
getStatsdTestAppUid(ITestDevice device)264     public static int getStatsdTestAppUid(ITestDevice device) throws DeviceNotAvailableException {
265         return getAppUid(device, STATSD_ATOM_TEST_PKG);
266     }
267 
268     /**
269      * Returns the UID of the test app for the current user.
270      */
getAppUid(ITestDevice device, String pkgName)271     public static int getAppUid(ITestDevice device, String pkgName)
272             throws DeviceNotAvailableException {
273         int currentUser = device.getCurrentUser();
274         return getAppUidForUser(device, pkgName, currentUser);
275     }
276 
277     /**
278      * Returns the UID of the test app for the given user.
279      */
getAppUidForUser(ITestDevice device, String pkgName, int userId)280     public static int getAppUidForUser(ITestDevice device, String pkgName, int userId)
281             throws DeviceNotAvailableException {
282         String uidLine = device.executeShellCommand("cmd package list packages -U --user "
283                 + userId + " " + pkgName);
284 
285         // Split package list by lines
286         // Sample packages response:
287         // package:com.android.server.cts.device.statsd.host uid:1010033
288         // package:com.android.server.cts.device.statsd uid:1010034
289         final String[] lines = uidLine.split("\\R+");
290         for (final String line : lines) {
291             if (line.startsWith("package:" + pkgName + " ")) {
292                 final int uidIndex = line.lastIndexOf(":") + 1;
293                 final int uid = Integer.parseInt(line.substring(uidIndex).trim());
294                 assertThat(uid).isGreaterThan(10_000);
295                 return uid;
296             }
297         }
298         throw new Error(
299                 String.format("Could not find installed package: %s", pkgName));
300     }
301 
302     /**
303      * Determines if the device has the given features.
304      *
305      * @param feature name of the feature (e.g. "android.hardware.bluetooth")
306      */
hasFeature(ITestDevice device, String feature)307     public static boolean hasFeature(ITestDevice device, String feature) throws Exception {
308         final String features = device.executeShellCommand("pm list features");
309         StringTokenizer featureToken = new StringTokenizer(features, "\n");
310 
311         while(featureToken.hasMoreTokens()) {
312             if (("feature:" + feature).equals(featureToken.nextToken())) {
313                 return true;
314             }
315         }
316 
317         return false;
318     }
319 
320     /**
321      * Runs an activity in a particular app.
322      */
runActivity(ITestDevice device, String pkgName, String activity, @Nullable String actionKey, @Nullable String actionValue)323     public static void runActivity(ITestDevice device, String pkgName, String activity,
324             @Nullable String actionKey, @Nullable String actionValue) throws Exception {
325         runActivity(device, pkgName, activity, actionKey, actionValue,
326                 AtomTestUtils.WAIT_TIME_LONG);
327     }
328 
329     /**
330      * Runs an activity in a particular app for a certain period of time.
331      *
332      * @param pkgName name of package that contains the Activity
333      * @param activity name of the Activity class
334      * @param actionKey key of extra data that is passed to the Activity via an Intent
335      * @param actionValue value of extra data that is passed to the Activity via an Intent
336      * @param waitTimeMs duration that the activity runs for
337      */
runActivity(ITestDevice device, String pkgName, String activity, @Nullable String actionKey, @Nullable String actionValue, long waitTimeMs)338     public static void runActivity(ITestDevice device, String pkgName, String activity,
339             @Nullable String actionKey, @Nullable String actionValue, long waitTimeMs)
340             throws Exception {
341         try (AutoCloseable a = withActivity(device, pkgName, activity, actionKey, actionValue)) {
342             RunUtil.getDefault().sleep(waitTimeMs);
343         }
344     }
345 
346     /**
347      * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
348      * when closed.
349      *
350      * <p>Example usage:
351      * <pre>
352      *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
353      *         doStuff();
354      *     }
355      * </pre>
356      */
withActivity(ITestDevice device, String pkgName, String activity, @Nullable String actionKey, @Nullable String actionValue)357     public static AutoCloseable withActivity(ITestDevice device, String pkgName, String activity,
358             @Nullable String actionKey, @Nullable String actionValue) throws Exception {
359         String intentString;
360         if (actionKey != null && actionValue != null) {
361             intentString = actionKey + " " + actionValue;
362         } else {
363             intentString = null;
364         }
365 
366         String cmd = "am start -n " + pkgName + "/." + activity;
367         if (intentString != null) {
368             cmd += " -e " + intentString;
369         }
370         device.executeShellCommand(cmd);
371 
372         return () -> {
373             device.executeShellCommand("am force-stop " + pkgName);
374             RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
375         };
376     }
377 
setChargingState(ITestDevice device, int state)378     public static void setChargingState(ITestDevice device, int state) throws Exception {
379         device.executeShellCommand("cmd battery set status " + state);
380     }
381 
unplugDevice(ITestDevice device)382     public static void unplugDevice(ITestDevice device) throws Exception {
383         // On batteryless devices on Android P or above, the 'unplug' command
384         // alone does not simulate the really unplugged state.
385         //
386         // This is because charging state is left as "unknown". Unless a valid
387         // state like 3 = BatteryManager.BATTERY_STATUS_DISCHARGING is set,
388         // framework does not consider the device as running on battery.
389         setChargingState(device, 3);
390         device.executeShellCommand("cmd battery unplug");
391     }
392 
plugInAc(ITestDevice device)393     public static void plugInAc(ITestDevice device) throws Exception {
394         device.executeShellCommand("cmd battery set ac 1");
395     }
396 
turnScreenOn(ITestDevice device)397     public static void turnScreenOn(ITestDevice device) throws Exception {
398         device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
399         device.executeShellCommand("wm dismiss-keyguard");
400     }
401 
turnScreenOff(ITestDevice device)402     public static void turnScreenOff(ITestDevice device) throws Exception {
403         device.executeShellCommand("input keyevent KEYCODE_SLEEP");
404     }
405 
turnBatteryStatsAutoResetOn(ITestDevice device)406     public static void turnBatteryStatsAutoResetOn(ITestDevice device) throws Exception {
407         device.executeShellCommand("dumpsys batterystats enable no-auto-reset");
408     }
409 
turnBatteryStatsAutoResetOff(ITestDevice device)410     public static void turnBatteryStatsAutoResetOff(ITestDevice device) throws Exception {
411         device.executeShellCommand("dumpsys batterystats enable no-auto-reset");
412     }
413 
flushBatteryStatsHandlers(ITestDevice device)414     public static void flushBatteryStatsHandlers(ITestDevice device) throws Exception {
415         // Dumping batterystats will flush everything in the batterystats handler threads.
416         device.executeShellCommand("dumpsys batterystats");
417     }
418 
hasBattery(ITestDevice device)419     public static boolean hasBattery(ITestDevice device) throws Exception {
420         try {
421             BatteryServiceDumpProto batteryProto = getShellCommandOutput(device, BatteryServiceDumpProto.parser(),
422                     String.join(" ", DUMP_BATTERY_CMD, "--proto"));
423             LogUtil.CLog.d("Got battery service dump:\n " + batteryProto.toString());
424             return batteryProto.getIsPresent();
425         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
426             LogUtil.CLog.e("Failed to dump batteryservice proto");
427             throw (e);
428         }
429     }
430 
resetBatteryStatus(ITestDevice device)431     public static void resetBatteryStatus(ITestDevice device) throws Exception {
432         device.executeShellCommand("cmd battery reset");
433     }
434 
getProperty(ITestDevice device, String prop)435     public static String getProperty(ITestDevice device, String prop) throws Exception {
436         return device.executeShellCommand("getprop " + prop).replace("\n", "");
437     }
438 
getDeviceConfigFeature(ITestDevice device, String namespace, String key)439     public static String getDeviceConfigFeature(ITestDevice device, String namespace,
440             String key) throws Exception {
441         return device.executeShellCommand(
442                 "device_config get " + namespace + " " + key).replace("\n", "");
443     }
444 
putDeviceConfigFeature(ITestDevice device, String namespace, String key, String value)445     public static String putDeviceConfigFeature(ITestDevice device, String namespace,
446             String key, String value) throws Exception {
447         return device.executeShellCommand(
448                 "device_config put " + namespace + " " + key + " " + value).replace("\n", "");
449     }
450 
deleteDeviceConfigFeature(ITestDevice device, String namespace, String key)451     public static String deleteDeviceConfigFeature(ITestDevice device, String namespace,
452             String key) throws Exception {
453         return device.executeShellCommand(
454                 "device_config delete " + namespace + " " + key).replace("\n", "");
455     }
456 
isDebuggable(ITestDevice device)457     public static boolean isDebuggable(ITestDevice device) throws Exception {
458         return Integer.parseInt(getProperty(device, "ro.debuggable")) == 1;
459     }
460 
checkDeviceFor(ITestDevice device, String methodName)461     public static boolean checkDeviceFor(ITestDevice device, String methodName) throws Exception {
462         try {
463             runDeviceTestsOnStatsdApp(device, ".Checkers", methodName);
464             // Test passes, meaning that the answer is true.
465             LogUtil.CLog.d(methodName + "() indicates true.");
466             return true;
467         } catch (AssertionError e) {
468             // Method is designed to fail if the answer is false.
469             LogUtil.CLog.d(methodName + "() indicates false.");
470             return false;
471         }
472     }
473 
474     /** Make the test app standby-active so it can run syncs and jobs immediately. */
allowImmediateSyncs(ITestDevice device)475     public static void allowImmediateSyncs(ITestDevice device) throws Exception {
476         device.executeShellCommand("am set-standby-bucket "
477                 + DeviceUtils.STATSD_ATOM_TEST_PKG + " active");
478     }
479 
480     /**
481      * Runs a (background) service to perform the given action.
482      * @param testPackage the test package that contains the background service.
483      * @param service the service name
484      * @param actionValue the action code constants indicating the desired action to perform.
485      */
executeBackgroundService(ITestDevice device, String testPackage, String service, String actionValue)486     public static void executeBackgroundService(ITestDevice device, String testPackage,
487             String service, String actionValue) throws Exception {
488         executeServiceAction(device, testPackage, service, actionValue);
489     }
490 
491 
492     /**
493      * Runs a (background) service to perform the given action.
494      * @param actionValue the action code constants indicating the desired action to perform.
495      */
executeBackgroundService(ITestDevice device, String actionValue)496     public static void executeBackgroundService(ITestDevice device, String actionValue)
497             throws Exception {
498         executeServiceAction(device, STATSD_ATOM_TEST_PKG,
499                 "StatsdCtsBackgroundService", actionValue);
500     }
501 
502     /**
503      * Runs the specified statsd package service to perform the given action.
504      * @param actionValue the action code constants indicating the desired action to perform.
505      */
executeServiceAction(ITestDevice device, String testPackage, String service, String actionValue)506     public static void executeServiceAction(ITestDevice device, String testPackage,
507             String service, String actionValue) throws Exception {
508         allowBackgroundServices(device);
509         device.executeShellCommand(String.format(
510                 "am startservice -n '%s/.%s' -e %s %s",
511                 testPackage, service,
512                 KEY_ACTION, actionValue));
513     }
514 
rebootDeviceAndWaitUntilReady(ITestDevice device)515     public static void rebootDeviceAndWaitUntilReady(ITestDevice device) throws Exception {
516         device.rebootUntilOnline();
517         // Wait for 3 mins.
518         assertWithMessage("Device failed to boot")
519             .that(device.waitForBootComplete(180_000)).isTrue();
520         assertWithMessage("Stats service failed to start")
521             .that(waitForStatsServiceStart(device, 60_000)).isTrue();
522         RunUtil.getDefault().sleep(2_000);
523     }
524 
waitForStatsServiceStart(ITestDevice device, long waitTime)525     private static boolean waitForStatsServiceStart(ITestDevice device, long waitTime)
526             throws Exception {
527         LogUtil.CLog.i("Waiting %d ms for stats service to start", waitTime);
528         int counter = 1;
529         long startTime = System.currentTimeMillis();
530         while ((System.currentTimeMillis() - startTime) < waitTime) {
531             if ("running".equals(getProperty(device, "init.svc.statsd"))) {
532                 return true;
533             }
534             RunUtil.getDefault().sleep(Math.min(200 * counter, 2_000));
535             counter++;
536         }
537         LogUtil.CLog.w("Stats service did not start after %d ms", waitTime);
538         return false;
539     }
540 
541     /**
542      * Required to successfully start a background service from adb in Android O.
543      */
allowBackgroundServices(ITestDevice device)544     private static void allowBackgroundServices(ITestDevice device) throws Exception {
545         device.executeShellCommand(String.format(
546                 "cmd deviceidle tempwhitelist %s", STATSD_ATOM_TEST_PKG));
547     }
548 
549     /**
550      * Returns the kernel major version as a pair of ints.
551      */
getKernelVersion(ITestDevice device)552     public static Pair<Integer, Integer> getKernelVersion(ITestDevice device)
553             throws Exception {
554         String[] version = device.executeShellCommand("uname -r").split("\\.");
555         if (version.length < 2) {
556               throw new RuntimeException("Could not parse kernel version");
557         }
558         return Pair.create(Integer.parseInt(version[0]), Integer.parseInt(version[1]));
559     }
560 
561     /** Returns if the device kernel version >= input kernel version. */
isKernelGreaterEqual(ITestDevice device, Pair<Integer, Integer> version)562     public static boolean isKernelGreaterEqual(ITestDevice device, Pair<Integer, Integer> version)
563             throws Exception {
564         Pair<Integer, Integer> kernelVersion = getKernelVersion(device);
565         return kernelVersion.first > version.first
566                 || (Objects.equals(kernelVersion.first, version.first)
567                 && kernelVersion.second >= version.second);
568     }
569 
570     // Gets whether "Always on Display" setting is enabled.
571     // In rare cases, this is different from whether the device can enter SCREEN_STATE_DOZE.
getAodState(ITestDevice device)572     public static String getAodState(ITestDevice device) throws Exception {
573         return device.executeShellCommand("settings get secure doze_always_on");
574     }
575 
setAodState(ITestDevice device, String state)576     public static void setAodState(ITestDevice device, String state) throws Exception {
577         device.executeShellCommand("settings put secure doze_always_on " + state);
578     }
579 
DeviceUtils()580     private DeviceUtils() {}
581 }