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