• 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 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         String[] uidLineArr = uidLine.split(":");
285 
286         // Package uid is located at index 2.
287         assertThat(uidLineArr.length).isGreaterThan(2);
288         int appUid = Integer.parseInt(uidLineArr[2].trim());
289         assertThat(appUid).isGreaterThan(10000);
290         return appUid;
291     }
292 
293     /**
294      * Determines if the device has the given features.
295      *
296      * @param feature name of the feature (e.g. "android.hardware.bluetooth")
297      */
hasFeature(ITestDevice device, String feature)298     public static boolean hasFeature(ITestDevice device, String feature) throws Exception {
299         final String features = device.executeShellCommand("pm list features");
300         StringTokenizer featureToken = new StringTokenizer(features, "\n");
301 
302         while(featureToken.hasMoreTokens()) {
303             if (("feature:" + feature).equals(featureToken.nextToken())) {
304                 return true;
305             }
306         }
307 
308         return false;
309     }
310 
311     /**
312      * Runs an activity in a particular app.
313      */
runActivity(ITestDevice device, String pkgName, String activity, @Nullable String actionKey, @Nullable String actionValue)314     public static void runActivity(ITestDevice device, String pkgName, String activity,
315             @Nullable String actionKey, @Nullable String actionValue) throws Exception {
316         runActivity(device, pkgName, activity, actionKey, actionValue,
317                 AtomTestUtils.WAIT_TIME_LONG);
318     }
319 
320     /**
321      * Runs an activity in a particular app for a certain period of time.
322      *
323      * @param pkgName name of package that contains the Activity
324      * @param activity name of the Activity class
325      * @param actionKey key of extra data that is passed to the Activity via an Intent
326      * @param actionValue value of extra data that is passed to the Activity via an Intent
327      * @param waitTimeMs duration that the activity runs for
328      */
runActivity(ITestDevice device, String pkgName, String activity, @Nullable String actionKey, @Nullable String actionValue, long waitTimeMs)329     public static void runActivity(ITestDevice device, String pkgName, String activity,
330             @Nullable String actionKey, @Nullable String actionValue, long waitTimeMs)
331             throws Exception {
332         try (AutoCloseable a = withActivity(device, pkgName, activity, actionKey, actionValue)) {
333             RunUtil.getDefault().sleep(waitTimeMs);
334         }
335     }
336 
337     /**
338      * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
339      * when closed.
340      *
341      * <p>Example usage:
342      * <pre>
343      *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
344      *         doStuff();
345      *     }
346      * </pre>
347      */
withActivity(ITestDevice device, String pkgName, String activity, @Nullable String actionKey, @Nullable String actionValue)348     public static AutoCloseable withActivity(ITestDevice device, String pkgName, String activity,
349             @Nullable String actionKey, @Nullable String actionValue) throws Exception {
350         String intentString;
351         if (actionKey != null && actionValue != null) {
352             intentString = actionKey + " " + actionValue;
353         } else {
354             intentString = null;
355         }
356 
357         String cmd = "am start -n " + pkgName + "/." + activity;
358         if (intentString != null) {
359             cmd += " -e " + intentString;
360         }
361         device.executeShellCommand(cmd);
362 
363         return () -> {
364             device.executeShellCommand("am force-stop " + pkgName);
365             RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
366         };
367     }
368 
setChargingState(ITestDevice device, int state)369     public static void setChargingState(ITestDevice device, int state) throws Exception {
370         device.executeShellCommand("cmd battery set status " + state);
371     }
372 
unplugDevice(ITestDevice device)373     public static void unplugDevice(ITestDevice device) throws Exception {
374         // On batteryless devices on Android P or above, the 'unplug' command
375         // alone does not simulate the really unplugged state.
376         //
377         // This is because charging state is left as "unknown". Unless a valid
378         // state like 3 = BatteryManager.BATTERY_STATUS_DISCHARGING is set,
379         // framework does not consider the device as running on battery.
380         setChargingState(device, 3);
381         device.executeShellCommand("cmd battery unplug");
382     }
383 
plugInAc(ITestDevice device)384     public static void plugInAc(ITestDevice device) throws Exception {
385         device.executeShellCommand("cmd battery set ac 1");
386     }
387 
turnScreenOn(ITestDevice device)388     public static void turnScreenOn(ITestDevice device) throws Exception {
389         device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
390         device.executeShellCommand("wm dismiss-keyguard");
391     }
392 
turnScreenOff(ITestDevice device)393     public static void turnScreenOff(ITestDevice device) throws Exception {
394         device.executeShellCommand("input keyevent KEYCODE_SLEEP");
395     }
396 
turnBatteryStatsAutoResetOn(ITestDevice device)397     public static void turnBatteryStatsAutoResetOn(ITestDevice device) throws Exception {
398         device.executeShellCommand("dumpsys batterystats enable no-auto-reset");
399     }
400 
turnBatteryStatsAutoResetOff(ITestDevice device)401     public static void turnBatteryStatsAutoResetOff(ITestDevice device) throws Exception {
402         device.executeShellCommand("dumpsys batterystats enable no-auto-reset");
403     }
404 
flushBatteryStatsHandlers(ITestDevice device)405     public static void flushBatteryStatsHandlers(ITestDevice device) throws Exception {
406         // Dumping batterystats will flush everything in the batterystats handler threads.
407         device.executeShellCommand("dumpsys batterystats");
408     }
409 
hasBattery(ITestDevice device)410     public static boolean hasBattery(ITestDevice device) throws Exception {
411         try {
412             BatteryServiceDumpProto batteryProto = getShellCommandOutput(device, BatteryServiceDumpProto.parser(),
413                     String.join(" ", DUMP_BATTERY_CMD, "--proto"));
414             LogUtil.CLog.d("Got battery service dump:\n " + batteryProto.toString());
415             return batteryProto.getIsPresent();
416         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
417             LogUtil.CLog.e("Failed to dump batteryservice proto");
418             throw (e);
419         }
420     }
421 
resetBatteryStatus(ITestDevice device)422     public static void resetBatteryStatus(ITestDevice device) throws Exception {
423         device.executeShellCommand("cmd battery reset");
424     }
425 
getProperty(ITestDevice device, String prop)426     public static String getProperty(ITestDevice device, String prop) throws Exception {
427         return device.executeShellCommand("getprop " + prop).replace("\n", "");
428     }
429 
getDeviceConfigFeature(ITestDevice device, String namespace, String key)430     public static String getDeviceConfigFeature(ITestDevice device, String namespace,
431             String key) throws Exception {
432         return device.executeShellCommand(
433                 "device_config get " + namespace + " " + key).replace("\n", "");
434     }
435 
putDeviceConfigFeature(ITestDevice device, String namespace, String key, String value)436     public static String putDeviceConfigFeature(ITestDevice device, String namespace,
437             String key, String value) throws Exception {
438         return device.executeShellCommand(
439                 "device_config put " + namespace + " " + key + " " + value).replace("\n", "");
440     }
441 
deleteDeviceConfigFeature(ITestDevice device, String namespace, String key)442     public static String deleteDeviceConfigFeature(ITestDevice device, String namespace,
443             String key) throws Exception {
444         return device.executeShellCommand(
445                 "device_config delete " + namespace + " " + key).replace("\n", "");
446     }
447 
isDebuggable(ITestDevice device)448     public static boolean isDebuggable(ITestDevice device) throws Exception {
449         return Integer.parseInt(getProperty(device, "ro.debuggable")) == 1;
450     }
451 
checkDeviceFor(ITestDevice device, String methodName)452     public static boolean checkDeviceFor(ITestDevice device, String methodName) throws Exception {
453         try {
454             runDeviceTestsOnStatsdApp(device, ".Checkers", methodName);
455             // Test passes, meaning that the answer is true.
456             LogUtil.CLog.d(methodName + "() indicates true.");
457             return true;
458         } catch (AssertionError e) {
459             // Method is designed to fail if the answer is false.
460             LogUtil.CLog.d(methodName + "() indicates false.");
461             return false;
462         }
463     }
464 
465     /** Make the test app standby-active so it can run syncs and jobs immediately. */
allowImmediateSyncs(ITestDevice device)466     public static void allowImmediateSyncs(ITestDevice device) throws Exception {
467         device.executeShellCommand("am set-standby-bucket "
468                 + DeviceUtils.STATSD_ATOM_TEST_PKG + " active");
469     }
470 
471     /**
472      * Runs a (background) service to perform the given action.
473      * @param actionValue the action code constants indicating the desired action to perform.
474      */
executeBackgroundService(ITestDevice device, String actionValue)475     public static void executeBackgroundService(ITestDevice device, String actionValue)
476             throws Exception {
477         executeServiceAction(device, "StatsdCtsBackgroundService", actionValue);
478     }
479 
480     /**
481      * Runs the specified statsd package service to perform the given action.
482      * @param actionValue the action code constants indicating the desired action to perform.
483      */
executeServiceAction(ITestDevice device, String service, String actionValue)484     public static void executeServiceAction(ITestDevice device, String service, String actionValue)
485             throws Exception {
486         allowBackgroundServices(device);
487         device.executeShellCommand(String.format(
488                 "am startservice -n '%s/.%s' -e %s %s",
489                 STATSD_ATOM_TEST_PKG, service,
490                 KEY_ACTION, actionValue));
491     }
492 
493     /**
494      * Required to successfully start a background service from adb in Android O.
495      */
allowBackgroundServices(ITestDevice device)496     private static void allowBackgroundServices(ITestDevice device) throws Exception {
497         device.executeShellCommand(String.format(
498                 "cmd deviceidle tempwhitelist %s", STATSD_ATOM_TEST_PKG));
499     }
500 
501     /**
502      * Returns the kernel major version as a pair of ints.
503      */
getKernelVersion(ITestDevice device)504     public static Pair<Integer, Integer> getKernelVersion(ITestDevice device)
505             throws Exception {
506         String[] version = device.executeShellCommand("uname -r").split("\\.");
507         if (version.length < 2) {
508               throw new RuntimeException("Could not parse kernel version");
509         }
510         return Pair.create(Integer.parseInt(version[0]), Integer.parseInt(version[1]));
511     }
512 
513     /** Returns if the device kernel version >= input kernel version. */
isKernelGreaterEqual(ITestDevice device, Pair<Integer, Integer> version)514     public static boolean isKernelGreaterEqual(ITestDevice device, Pair<Integer, Integer> version)
515             throws Exception {
516         Pair<Integer, Integer> kernelVersion = getKernelVersion(device);
517         return kernelVersion.first > version.first
518                 || (Objects.equals(kernelVersion.first, version.first)
519                 && kernelVersion.second >= version.second);
520     }
521 
DeviceUtils()522     private DeviceUtils() {}
523 }
524