1 /*
2  * Copyright (C) 2017 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 package com.android.server.cts;
17 
18 import com.android.ddmlib.IShellOutputReceiver;
19 import com.android.tradefed.log.LogUtil;
20 
21 import com.google.common.base.Charsets;
22 
23 import java.util.Arrays;
24 import java.util.Random;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.atomic.AtomicBoolean;
27 
28 /**
29  * Test for "dumpsys batterystats -c
30  *
31  * Validates reporting of battery stats based on different events
32  */
33 public class BatteryStatsValidationTest extends ProtoDumpTestCase {
34     private static final String TAG = "BatteryStatsValidationTest";
35 
36     private static final String DEVICE_SIDE_TEST_APK = "CtsBatteryStatsApp.apk";
37     private static final String DEVICE_SIDE_TEST_PACKAGE
38             = "com.android.server.cts.device.batterystats";
39     private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT
40             = "com.android.server.cts.device.batterystats/.BatteryStatsBackgroundService";
41     private static final String DEVICE_SIDE_FG_ACTIVITY_COMPONENT
42             = "com.android.server.cts.device.batterystats/.BatteryStatsForegroundActivity";
43     private static final String DEVICE_SIDE_JOB_COMPONENT
44             = "com.android.server.cts.device.batterystats/.SimpleJobService";
45     private static final String DEVICE_SIDE_SYNC_COMPONENT
46             = "com.android.server.cts.device.batterystats.provider/"
47             + "com.android.server.cts.device.batterystats";
48 
49     // These constants are those in PackageManager.
50     public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
51     public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
52     public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
53     public static final String FEATURE_WIFI = "android.hardware.wifi";
54 
55 
56     // Low end of packet size. TODO: Get exact packet size
57     private static final int LOW_MTU = 1500;
58     // High end of packet size. TODO: Get exact packet size
59     private static final int HIGH_MTU = 2500;
60 
61     // Constants from BatteryStatsBgVsFgActions.java (not directly accessible here).
62     public static final String KEY_ACTION = "action";
63     public static final String ACTION_BLE_SCAN_OPTIMIZED = "action.ble_scan_optimized";
64     public static final String ACTION_BLE_SCAN_UNOPTIMIZED = "action.ble_scan_unoptimized";
65     public static final String ACTION_GPS = "action.gps";
66     public static final String ACTION_JOB_SCHEDULE = "action.jobs";
67     public static final String ACTION_SYNC = "action.sync";
68     public static final String ACTION_WIFI_SCAN = "action.wifi_scan";
69     public static final String ACTION_WIFI_DOWNLOAD = "action.wifi_download";
70     public static final String ACTION_WIFI_UPLOAD = "action.wifi_upload";
71 
72     public static final String KEY_REQUEST_CODE = "request_code";
73     public static final String BG_VS_FG_TAG = "BatteryStatsBgVsFgActions";
74 
75     @Override
setUp()76     protected void setUp() throws Exception {
77         super.setUp();
78 
79         // Uninstall to clear the history in case it's still on the device.
80         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
81     }
82 
83     /** Smallest possible HTTP header. */
84     private static final int MIN_HTTP_HEADER_BYTES = 26;
85 
86     @Override
tearDown()87     protected void tearDown() throws Exception {
88         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
89 
90         batteryOffScreenOn();
91         super.tearDown();
92     }
93 
screenOff()94     protected void screenOff() throws Exception {
95         getDevice().executeShellCommand("dumpsys batterystats enable pretend-screen-off");
96     }
97 
98     /**
99      * This will turn the screen on for real, not just disabling pretend-screen-off
100      */
turnScreenOnForReal()101     protected void turnScreenOnForReal() throws Exception {
102         getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
103         getDevice().executeShellCommand("wm dismiss-keyguard");
104     }
105 
batteryOnScreenOn()106     protected void batteryOnScreenOn() throws Exception {
107         getDevice().executeShellCommand("dumpsys battery unplug");
108         getDevice().executeShellCommand("dumpsys batterystats disable pretend-screen-off");
109     }
110 
batteryOnScreenOff()111     protected void batteryOnScreenOff() throws Exception {
112         getDevice().executeShellCommand("dumpsys battery unplug");
113         getDevice().executeShellCommand("dumpsys batterystats enable pretend-screen-off");
114     }
115 
batteryOffScreenOn()116     protected void batteryOffScreenOn() throws Exception {
117         getDevice().executeShellCommand("dumpsys battery reset");
118         getDevice().executeShellCommand("dumpsys batterystats disable pretend-screen-off");
119     }
120 
forceStop()121     private void forceStop() throws Exception {
122         getDevice().executeShellCommand("am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
123     }
124 
resetBatteryStats()125     private void resetBatteryStats() throws Exception {
126         getDevice().executeShellCommand("dumpsys batterystats --reset");
127     }
128 
testAlarms()129     public void testAlarms() throws Exception {
130         batteryOnScreenOff();
131 
132         installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
133 
134         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsAlarmTest", "testAlarms");
135 
136         assertValueRange("wua", "*walarm*:com.android.server.cts.device.batterystats.ALARM",
137                 5, 3, 3);
138 
139         batteryOffScreenOn();
140     }
141 
testWakeLockDuration()142     public void testWakeLockDuration() throws Exception {
143         batteryOnScreenOff();
144 
145         installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
146 
147         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsWakeLockTests",
148                 "testHoldShortWakeLock");
149 
150         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsWakeLockTests",
151                 "testHoldLongWakeLock");
152 
153         assertValueRange("wl", "BSShortWakeLock", 15, (long) (500 * 0.9), 500 * 2); // partial max duration
154         assertValueRange("wl", "BSLongWakeLock", 15, (long) (3000 * 0.9), 3000 * 2);  // partial max duration
155 
156         batteryOffScreenOn();
157     }
158 
testServiceForegroundDuration()159     public void testServiceForegroundDuration() throws Exception {
160         batteryOnScreenOff();
161         installPackage(DEVICE_SIDE_TEST_APK, true);
162 
163         getDevice().executeShellCommand(
164                 "am start -n com.android.server.cts.device.batterystats/.SimpleActivity");
165         assertValueRange("st", "", 5, 0, 0); // No foreground service time before test
166         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsProcessStateTests",
167                 "testForegroundService");
168         assertValueRange("st", "", 5, (long) (2000 * 0.8), 4000);
169 
170         batteryOffScreenOn();
171     }
172 
testBleScans()173     public void testBleScans() throws Exception {
174         if (isTV() || !hasFeature(FEATURE_BLUETOOTH_LE, true)) {
175             return;
176         }
177 
178         batteryOnScreenOff();
179         installPackage(DEVICE_SIDE_TEST_APK, true);
180 
181         // Background test.
182         executeBackground(ACTION_BLE_SCAN_UNOPTIMIZED, 30_000);
183         assertValueRange("blem", "", 5, 1, 1); // ble_scan_count
184         assertValueRange("blem", "", 6, 1, 1); // ble_scan_count_bg
185 
186         // Foreground test.
187         executeForeground(ACTION_BLE_SCAN_UNOPTIMIZED, 30_000);
188         assertValueRange("blem", "", 5, 2, 2); // ble_scan_count
189         assertValueRange("blem", "", 6, 1, 1); // ble_scan_count_bg
190 
191         batteryOffScreenOn();
192     }
193 
194 
testUnoptimizedBleScans()195     public void testUnoptimizedBleScans() throws Exception {
196         if (isTV()) {
197             return;
198         }
199         batteryOnScreenOff();
200         installPackage(DEVICE_SIDE_TEST_APK, true);
201 
202         // Ble scan time in BatteryStatsBgVsFgActions is 2 seconds, but be lenient.
203         final int minTime = 1500; // min single scan time in ms
204         final int maxTime = 3000; // max single scan time in ms
205 
206         // Optimized - Background.
207         executeBackground(ACTION_BLE_SCAN_OPTIMIZED, 30_000);
208         assertValueRange("blem", "", 7, 1*minTime, 1*maxTime); // actualTime
209         assertValueRange("blem", "", 8, 1*minTime, 1*maxTime); // actualTimeBg
210         assertValueRange("blem", "", 11, 0, 0); // unoptimizedScanTotalTime
211         assertValueRange("blem", "", 12, 0, 0); // unoptimizedScanTotalTimeBg
212         assertValueRange("blem", "", 13, 0, 0); // unoptimizedScanMaxTime
213         assertValueRange("blem", "", 14, 0, 0); // unoptimizedScanMaxTimeBg
214 
215         // Optimized - Foreground.
216         executeForeground(ACTION_BLE_SCAN_OPTIMIZED, 30_000);
217         assertValueRange("blem", "", 7, 2*minTime, 2*maxTime); // actualTime
218         assertValueRange("blem", "", 8, 1*minTime, 1*maxTime); // actualTimeBg
219         assertValueRange("blem", "", 11, 0, 0); // unoptimizedScanTotalTime
220         assertValueRange("blem", "", 12, 0, 0); // unoptimizedScanTotalTimeBg
221         assertValueRange("blem", "", 13, 0, 0); // unoptimizedScanMaxTime
222         assertValueRange("blem", "", 14, 0, 0); // unoptimizedScanMaxTimeBg
223 
224         // Unoptimized - Background.
225         executeBackground(ACTION_BLE_SCAN_UNOPTIMIZED, 30_000);
226         assertValueRange("blem", "", 7, 3*minTime, 3*maxTime); // actualTime
227         assertValueRange("blem", "", 8, 2*minTime, 2*maxTime); // actualTimeBg
228         assertValueRange("blem", "", 11, 1*minTime, 1*maxTime); // unoptimizedScanTotalTime
229         assertValueRange("blem", "", 12, 1*minTime, 1*maxTime); // unoptimizedScanTotalTimeBg
230         assertValueRange("blem", "", 13, 1*minTime, 1*maxTime); // unoptimizedScanMaxTime
231         assertValueRange("blem", "", 14, 1*minTime, 1*maxTime); // unoptimizedScanMaxTimeBg
232 
233         // Unoptimized - Foreground.
234         executeForeground(ACTION_BLE_SCAN_UNOPTIMIZED, 30_000);
235         assertValueRange("blem", "", 7, 4*minTime, 4*maxTime); // actualTime
236         assertValueRange("blem", "", 8, 2*minTime, 2*maxTime); // actualTimeBg
237         assertValueRange("blem", "", 11, 2*minTime, 2*maxTime); // unoptimizedScanTotalTime
238         assertValueRange("blem", "", 12, 1*minTime, 1*maxTime); // unoptimizedScanTotalTimeBg
239         assertValueRange("blem", "", 13, 1*minTime, 1*maxTime); // unoptimizedScanMaxTime
240         assertValueRange("blem", "", 14, 1*minTime, 1*maxTime); // unoptimizedScanMaxTimeBg
241 
242         batteryOffScreenOn();
243     }
244 
testGpsUpdates()245     public void testGpsUpdates() throws Exception {
246         if (isTV() || !hasFeature(FEATURE_LOCATION_GPS, true)) {
247             return;
248         }
249 
250         final String gpsSensorNumber = "-10000";
251 
252         batteryOnScreenOff();
253         installPackage(DEVICE_SIDE_TEST_APK, true);
254         // Whitelist this app against background location request throttling
255         getDevice().executeShellCommand(String.format(
256                 "settings put global location_background_throttle_package_whitelist %s",
257                 DEVICE_SIDE_TEST_PACKAGE));
258 
259         // Background test.
260         executeBackground(ACTION_GPS, 60_000);
261         assertValueRange("sr", gpsSensorNumber, 6, 1, 1); // count
262         assertValueRange("sr", gpsSensorNumber, 7, 1, 1); // background_count
263 
264         // Foreground test.
265         executeForeground(ACTION_GPS, 60_000);
266         assertValueRange("sr", gpsSensorNumber, 6, 2, 2); // count
267         assertValueRange("sr", gpsSensorNumber, 7, 1, 1); // background_count
268 
269         batteryOffScreenOn();
270     }
271 
testJobBgVsFg()272     public void testJobBgVsFg() throws Exception {
273         if (isTV()) {
274             return;
275         }
276         batteryOnScreenOff();
277         installPackage(DEVICE_SIDE_TEST_APK, true);
278 
279         // Background test.
280         executeBackground(ACTION_JOB_SCHEDULE, 60_000);
281         assertValueRange("jb", "", 6, 1, 1); // count
282         assertValueRange("jb", "", 8, 1, 1); // background_count
283 
284         // Foreground test.
285         executeForeground(ACTION_JOB_SCHEDULE, 60_000);
286         assertValueRange("jb", "", 6, 2, 2); // count
287         assertValueRange("jb", "", 8, 1, 1); // background_count
288 
289         batteryOffScreenOn();
290     }
291 
testSyncBgVsFg()292     public void testSyncBgVsFg() throws Exception {
293         if (isTV()) {
294             return;
295         }
296         batteryOnScreenOff();
297         installPackage(DEVICE_SIDE_TEST_APK, true);
298 
299         // Background test.
300         executeBackground(ACTION_SYNC, 60_000);
301         // Allow one or two syncs in this time frame (not just one) due to unpredictable syncs.
302         assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 6, 1, 2); // count
303         assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 8, 1, 2); // background_count
304 
305         // Foreground test.
306         executeForeground(ACTION_SYNC, 60_000);
307         assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 6, 2, 4); // count
308         assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 8, 1, 2); // background_count
309 
310         batteryOffScreenOn();
311     }
312 
testWifiScans()313     public void testWifiScans() throws Exception {
314         if (isTV() || !hasFeature(FEATURE_WIFI, true)) {
315             return;
316         }
317 
318         batteryOnScreenOff();
319         installPackage(DEVICE_SIDE_TEST_APK, true);
320         // Whitelist this app against background wifi scan throttling
321         getDevice().executeShellCommand(String.format(
322                 "settings put global wifi_scan_background_throttle_package_whitelist %s",
323                 DEVICE_SIDE_TEST_PACKAGE));
324 
325         // Background count test.
326         executeBackground(ACTION_WIFI_SCAN, 120_000);
327         // Allow one or two scans because we try scanning twice and because we allow for the
328         // possibility that, when the test is started, a scan from a different uid was already being
329         // performed (causing the test to 'miss' a scan).
330         assertValueRange("wfl", "", 7, 1, 2); // scan_count
331         assertValueRange("wfl", "", 11, 1, 2); // scan_count_bg
332 
333         // Foreground count test.
334         executeForeground(ACTION_WIFI_SCAN, 120_000);
335         assertValueRange("wfl", "", 7, 2, 4); // scan_count
336         assertValueRange("wfl", "", 11, 1, 2); // scan_count_bg
337 
338         batteryOffScreenOn();
339     }
340 
341     /**
342      * Tests whether the on-battery realtime and total realtime values
343      * are properly updated in battery stats.
344      */
testRealtime()345     public void testRealtime() throws Exception {
346         batteryOnScreenOff();
347         long startingValueRealtime = getLongValue(0, "bt", "", 7);
348         long startingValueBatteryRealtime = getLongValue(0, "bt", "", 5);
349         // After going on battery
350         Thread.sleep(2000);
351         batteryOffScreenOn();
352         // After going off battery
353         Thread.sleep(2000);
354 
355         long currentValueRealtime = getLongValue(0, "bt", "", 7);
356         long currentValueBatteryRealtime = getLongValue(0, "bt", "", 5);
357 
358         // Total realtime increase should be 4000ms at least
359         assertTrue(currentValueRealtime >= startingValueRealtime + 4000);
360         // But not too much more
361         assertTrue(currentValueRealtime < startingValueRealtime + 6000);
362         // Battery on realtime should be more than 2000 but less than 4000
363         assertTrue(currentValueBatteryRealtime >= startingValueBatteryRealtime + 2000);
364         assertTrue(currentValueBatteryRealtime < startingValueBatteryRealtime + 4000);
365     }
366 
367     /**
368      * Tests the total duration reported for jobs run on the job scheduler.
369      */
370     public void testJobDuration() throws Exception {
371         batteryOnScreenOff();
372 
373         installPackage(DEVICE_SIDE_TEST_APK, true);
374 
375         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsJobDurationTests",
376                 "testJobDuration");
377 
378         // Should be approximately 3000 ms. Use 0.8x and 2x as the lower and upper
379         // bounds to account for possible errors due to thread scheduling and cpu load.
380         assertValueRange("jb", DEVICE_SIDE_JOB_COMPONENT, 5, (long) (3000 * 0.8), 3000 * 2);
381         batteryOffScreenOn();
382     }
383 
384     /**
385      * Tests the total duration and # of syncs reported for sync activities.
386      */
387     public void testSyncs() throws Exception {
388         batteryOnScreenOff();
389 
390         installPackage(DEVICE_SIDE_TEST_APK, true);
391 
392         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsSyncTest", "testRunSyncs");
393 
394         // First, check the count, which should be 10.
395         // (It could be 11, if the initial sync actually happened before getting cancelled.)
396         assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 6, 10L, 11L);
397 
398         // Should be approximately, but at least 10 seconds. Use 2x as the upper
399         // bounds to account for possible errors due to thread scheduling and cpu load.
400         assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 5, 10000, 10000 * 2);
401     }
402 
403     /**
404      * Tests the total bytes reported for downloading over wifi.
405      */
406     public void testWifiDownload() throws Exception {
407         if (isTV() || !hasFeature(FEATURE_WIFI, true)) {
408             return;
409         }
410 
411         batteryOnScreenOff();
412         installPackage(DEVICE_SIDE_TEST_APK, true);
413 
414         final long FUZZ = 50 * 1024;
415 
416         long prevBytes = getLongValue(getUid(), "nt", "", 6);
417 
418         String requestCode = executeForeground(ACTION_WIFI_DOWNLOAD, 60_000);
419         long downloadedBytes = getDownloadedBytes(requestCode);
420         assertTrue(downloadedBytes > 0);
421         long min = prevBytes + downloadedBytes + MIN_HTTP_HEADER_BYTES;
422         long max = prevBytes + downloadedBytes + FUZZ; // Add some fuzzing.
423         assertValueRange("nt", "", 6, min, max); // wifi_bytes_rx
424         assertValueRange("nt", "", 10, min / HIGH_MTU, max / LOW_MTU); // wifi_packets_rx
425 
426         // Do the background download
427         long prevBgBytes = getLongValue(getUid(), "nt", "", 20);
428         requestCode = executeBackground(ACTION_WIFI_DOWNLOAD, 60_000);
429         downloadedBytes = getDownloadedBytes(requestCode);
430 
431         long minBg = prevBgBytes + downloadedBytes + MIN_HTTP_HEADER_BYTES;
432         long maxBg = prevBgBytes + downloadedBytes + FUZZ;
433         assertValueRange("nt", "", 20, minBg, maxBg); // wifi_bytes_bg_rx
434         assertValueRange("nt", "", 24, minBg / HIGH_MTU, maxBg / LOW_MTU); // wifi_packets_bg_rx
435 
436         // Also increases total wifi counts.
437         min += downloadedBytes + MIN_HTTP_HEADER_BYTES;
438         max += downloadedBytes + FUZZ;
439         assertValueRange("nt", "", 6, min, max); // wifi_bytes_rx
440         assertValueRange("nt", "", 10, min / HIGH_MTU, max / LOW_MTU); // wifi_packets_rx
441 
442         batteryOffScreenOn();
443     }
444 
445     /**
446      * Tests the total bytes reported for uploading over wifi.
447      */
testWifiUpload()448     public void testWifiUpload() throws Exception {
449         if (isTV() || !hasFeature(FEATURE_WIFI, true)) {
450             return;
451         }
452 
453         batteryOnScreenOff();
454         installPackage(DEVICE_SIDE_TEST_APK, true);
455 
456         executeBackground(ACTION_WIFI_UPLOAD, 60_000);
457         int min = MIN_HTTP_HEADER_BYTES + (2 * 1024);
458         int max = min + (6 * 1024); // Add some fuzzing.
459         assertValueRange("nt", "", 21, min, max); // wifi_bytes_bg_tx
460 
461         executeForeground(ACTION_WIFI_UPLOAD, 60_000);
462         assertValueRange("nt", "", 7, min * 2, max * 2); // wifi_bytes_tx
463 
464         batteryOffScreenOn();
465     }
466 
getUid()467     private int getUid() throws Exception {
468         String uidLine = getDevice().executeShellCommand("cmd package list packages -U "
469                 + DEVICE_SIDE_TEST_PACKAGE);
470         String[] uidLineParts = uidLine.split(":");
471         // 3rd entry is package uid
472         assertTrue(uidLineParts.length > 2);
473         int uid = Integer.parseInt(uidLineParts[2].trim());
474         assertTrue(uid > 10000);
475         return uid;
476     }
477 
478     /**
479      * Verifies that the recorded time for the specified tag and name in the test package
480      * is within the specified range.
481      */
assertValueRange(String tag, String optionalAfterTag, int index, long min, long max)482     private void assertValueRange(String tag, String optionalAfterTag,
483             int index, long min, long max) throws Exception {
484         int uid = getUid();
485         long value = getLongValue(uid, tag, optionalAfterTag, index);
486 
487         assertTrue("Value " + value + " is less than min " + min, value >= min);
488         assertTrue("Value " + value + " is greater than max " + max, value <= max);
489     }
490 
491     /**
492      * Returns a particular long value from a line matched by uid, tag and the optionalAfterTag.
493      */
getLongValue(int uid, String tag, String optionalAfterTag, int index)494     private long getLongValue(int uid, String tag, String optionalAfterTag, int index)
495             throws Exception {
496         String dumpsys = getDevice().executeShellCommand("dumpsys batterystats --checkin");
497         String[] lines = dumpsys.split("\n");
498         long value = 0;
499         if (optionalAfterTag == null) {
500             optionalAfterTag = "";
501         }
502         for (int i = lines.length - 1; i >= 0; i--) {
503             String line = lines[i];
504             if (line.contains(uid + ",l," + tag + "," + optionalAfterTag)
505                     || (!optionalAfterTag.equals("") &&
506                         line.contains(uid + ",l," + tag + ",\"" + optionalAfterTag))) {
507                 String[] wlParts = line.split(",");
508                 value = Long.parseLong(wlParts[index]);
509             }
510         }
511         return value;
512     }
513 
514     /**
515      * Runs a (background) service to perform the given action, and waits for
516      * the device to report that the action has finished (via a logcat message) before returning.
517      * @param actionValue one of the constants in BatteryStatsBgVsFgActions indicating the desired
518      *                    action to perform.
519      * @param maxTimeMs max time to wait (in ms) for action to report that it has completed.
520      * @return A string, representing a random integer, assigned to this particular request for the
521      *                     device to perform the given action. This value can be used to receive
522      *                     communications via logcat from the device about this action.
523      */
executeBackground(String actionValue, int maxTimeMs)524     private String executeBackground(String actionValue, int maxTimeMs) throws Exception {
525         String requestCode = executeBackground(actionValue);
526         String searchString = getCompletedActionString(actionValue, requestCode);
527         checkLogcatForText(BG_VS_FG_TAG, searchString, maxTimeMs);
528         return requestCode;
529     }
530 
531     /**
532      * Runs a (background) service to perform the given action.
533      * @param actionValue one of the constants in BatteryStatsBgVsFgActions indicating the desired
534      *                    action to perform.
535      * @return A string, representing a random integer, assigned to this particular request for the
536       *                     device to perform the given action. This value can be used to receive
537       *                     communications via logcat from the device about this action.
538      */
executeBackground(String actionValue)539     private String executeBackground(String actionValue) throws Exception {
540         allowBackgroundServices();
541         String requestCode = Integer.toString(new Random().nextInt());
542         getDevice().executeShellCommand(String.format(
543                 "am startservice -n '%s' -e %s %s -e %s %s",
544                 DEVICE_SIDE_BG_SERVICE_COMPONENT,
545                 KEY_ACTION, actionValue,
546                 KEY_REQUEST_CODE, requestCode));
547         return requestCode;
548     }
549 
550     /** Required to successfully start a background service from adb in O. */
allowBackgroundServices()551     private void allowBackgroundServices() throws Exception {
552         getDevice().executeShellCommand(String.format(
553                 "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
554     }
555 
556     /**
557      * Runs an activity (in the foreground) to perform the given action, and waits
558      * for the device to report that the action has finished (via a logcat message) before returning.
559      * @param actionValue one of the constants in BatteryStatsBgVsFgActions indicating the desired
560      *                    action to perform.
561      * @param maxTimeMs max time to wait (in ms) for action to report that it has completed.
562      * @return A string, representing a random integer, assigned to this particular request for the
563      *                     device to perform the given action. This value can be used to receive
564      *                     communications via logcat from the device about this action.
565      */
executeForeground(String actionValue, int maxTimeMs)566     private String executeForeground(String actionValue, int maxTimeMs) throws Exception {
567         String requestCode = executeForeground(actionValue);
568         String searchString = getCompletedActionString(actionValue, requestCode);
569         checkLogcatForText(BG_VS_FG_TAG, searchString, maxTimeMs);
570         return requestCode;
571     }
572 
573     /**
574      * Runs an activity (in the foreground) to perform the given action.
575      * @param actionValue one of the constants in BatteryStatsBgVsFgActions indicating the desired
576      *                    action to perform.
577      * @return A string, representing a random integer, assigned to this particular request for the
578      *                     device to perform the given action. This value can be used to receive
579      *                     communications via logcat from the device about this action.
580      */
executeForeground(String actionValue)581     private String executeForeground(String actionValue) throws Exception {
582         String requestCode = Integer.toString(new Random().nextInt());
583         getDevice().executeShellCommand(String.format(
584                 "am start -n '%s' -e %s %s -e %s %s",
585                 DEVICE_SIDE_FG_ACTIVITY_COMPONENT,
586                 KEY_ACTION, actionValue,
587                 KEY_REQUEST_CODE, requestCode));
588         return requestCode;
589     }
590 
591     /**
592      * The string that will be printed in the logcat when the action completes. This needs to be
593      * identical to {@link com.android.server.cts.device.batterystats.BatteryStatsBgVsFgActions#tellHostActionFinished}.
594      */
getCompletedActionString(String actionValue, String requestCode)595     private String getCompletedActionString(String actionValue, String requestCode) {
596         return String.format("Completed performing %s for request %s", actionValue, requestCode);
597     }
598 
599     /**
600     * Runs logcat and waits (for a maximumum of maxTimeMs) until the desired text is displayed with
601     * the given tag.
602     * Logcat is not cleared, so make sure that text is unique (won't get false hits from old data).
603     * Note that, in practice, the actual max wait time seems to be about 10s longer than maxTimeMs.
604     */
checkLogcatForText(String logcatTag, String text, int maxTimeMs)605     private void checkLogcatForText(String logcatTag, String text, int maxTimeMs) {
606         IShellOutputReceiver receiver = new IShellOutputReceiver() {
607             private final StringBuilder mOutputBuffer = new StringBuilder();
608             private final AtomicBoolean mIsCanceled = new AtomicBoolean(false);
609 
610             @Override
611             public void addOutput(byte[] data, int offset, int length) {
612                 if (!isCancelled()) {
613                     synchronized (mOutputBuffer) {
614                         String s = new String(data, offset, length, Charsets.UTF_8);
615                         mOutputBuffer.append(s);
616                         if (checkBufferForText()) {
617                             mIsCanceled.set(true);
618                         }
619                     }
620                 }
621             }
622 
623             private boolean checkBufferForText() {
624                 if (mOutputBuffer.indexOf(text) > -1) {
625                     return true;
626                 } else {
627                     // delete all old data (except the last few chars) since they don't contain text
628                     // (presumably large chunks of data will be added at a time, so this is
629                     // sufficiently efficient.)
630                     int newStart = mOutputBuffer.length() - text.length();
631                     if (newStart > 0) {
632                         mOutputBuffer.delete(0, newStart);
633                     }
634                     return false;
635                 }
636             }
637 
638             @Override
639             public boolean isCancelled() {
640                 return mIsCanceled.get();
641             }
642 
643             @Override
644             public void flush() {
645             }
646         };
647 
648         try {
649             // Wait for at most maxTimeMs for logcat to display the desired text.
650             getDevice().executeShellCommand(String.format("logcat -s %s -e '%s'", logcatTag, text),
651                     receiver, maxTimeMs, TimeUnit.MILLISECONDS, 0);
652         } catch (com.android.tradefed.device.DeviceNotAvailableException e) {
653             System.err.println(e);
654         }
655     }
656 
657     /**
658      * Returns the bytes downloaded for the wifi transfer download tests.
659      * @param requestCode the output of executeForeground() or executeBackground() to identify in
660      *                    the logcat the line associated with the desired download information
661      */
getDownloadedBytes(String requestCode)662     private long getDownloadedBytes(String requestCode) throws Exception {
663         String log = getDevice().executeShellCommand(
664                 String.format("logcat -d -s BatteryStatsWifiTransferTests -e 'request %s d=\\d+'",
665                         requestCode));
666         String[] lines = log.split("\n");
667         long size = 0;
668         for (int i = lines.length - 1; i >= 0; i--) {
669             String[] parts = lines[i].split("d=");
670             String num = parts[parts.length - 1].trim();
671             if (num.matches("\\d+")) {
672                 size = Integer.parseInt(num);
673             }
674         }
675         return size;
676     }
677 
678     /** Determine if device is just a TV and is not expected to have proper batterystats. */
isTV()679     private boolean isTV() throws Exception {
680         return hasFeature(FEATURE_LEANBACK_ONLY, false);
681     }
682 
683     /**
684      * Determines if the device has the given feature.
685      * Prints a warning if its value differs from requiredAnswer.
686      */
hasFeature(String featureName, boolean requiredAnswer)687     private boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception {
688         final String features = getDevice().executeShellCommand("pm list features");
689         boolean hasIt = features.contains(featureName);
690         if (hasIt != requiredAnswer) {
691             LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature "
692                     + featureName);
693         }
694         return hasIt;
695     }
696 }
697