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