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.tradefed.log.LogUtil;
19 
20 import java.util.Random;
21 
22 /**
23  * Test for "dumpsys batterystats -c
24  *
25  * Validates reporting of battery stats based on different events
26  */
27 public class BatteryStatsValidationTest extends ProtoDumpTestCase {
28     private static final String TAG = "BatteryStatsValidationTest";
29 
30     private static final String DEVICE_SIDE_TEST_APK = "CtsBatteryStatsApp.apk";
31     private static final String DEVICE_SIDE_TEST_PACKAGE
32             = "com.android.server.cts.device.batterystats";
33     private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT
34             = "com.android.server.cts.device.batterystats/.BatteryStatsBackgroundService";
35     private static final String DEVICE_SIDE_FG_ACTIVITY_COMPONENT
36             = "com.android.server.cts.device.batterystats/.BatteryStatsForegroundActivity";
37     private static final String DEVICE_SIDE_JOB_COMPONENT
38             = "com.android.server.cts.device.batterystats/.SimpleJobService";
39     private static final String DEVICE_SIDE_SYNC_COMPONENT
40             = "com.android.server.cts.device.batterystats.provider/"
41             + "com.android.server.cts.device.batterystats";
42 
43     // These constants are those in PackageManager.
44     public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
45     public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
46     public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
47 
48     private static final int STATE_TIME_TOP_INDEX = 4;
49     private static final int STATE_TIME_FOREGROUND_SERVICE_INDEX = 5;
50     private static final int STATE_TIME_FOREGROUND_INDEX = 6;
51     private static final int STATE_TIME_BACKGROUND_INDEX = 7;
52     private static final int STATE_TIME_CACHED_INDEX = 10;
53 
54     private static final long TIME_SPENT_IN_TOP = 2000;
55     private static final long TIME_SPENT_IN_FOREGROUND = 2000;
56     private static final long TIME_SPENT_IN_BACKGROUND = 2000;
57     private static final long TIME_SPENT_IN_CACHED = 4000;
58     private static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000;
59     private static final long SCREEN_STATE_POLLING_INTERVAL = 500;
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_SLEEP_WHILE_BACKGROUND = "action.sleep_background";
69     public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
70     public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
71 
72     public static final String KEY_REQUEST_CODE = "request_code";
73     public static final String BG_VS_FG_TAG = "BatteryStatsBgVsFgActions";
74 
75     // Constants from BatteryMangager.
76     public static final int BATTERY_STATUS_DISCHARGING = 3;
77 
78     @Override
setUp()79     protected void setUp() throws Exception {
80         super.setUp();
81 
82         // Uninstall to clear the history in case it's still on the device.
83         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
84     }
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 
106     /**
107      * This will send the screen to sleep
108      */
turnScreenOffForReal()109     protected void turnScreenOffForReal() throws Exception {
110         getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
111     }
112 
batteryOnScreenOn()113     protected void batteryOnScreenOn() throws Exception {
114         getDevice().executeShellCommand("dumpsys battery unplug");
115         getDevice().executeShellCommand("dumpsys battery set status " + BATTERY_STATUS_DISCHARGING);
116         getDevice().executeShellCommand("dumpsys batterystats disable pretend-screen-off");
117     }
118 
batteryOnScreenOff()119     protected void batteryOnScreenOff() throws Exception {
120         getDevice().executeShellCommand("dumpsys battery unplug");
121         getDevice().executeShellCommand("dumpsys battery set status " + BATTERY_STATUS_DISCHARGING);
122         getDevice().executeShellCommand("dumpsys batterystats enable pretend-screen-off");
123     }
124 
batteryOffScreenOn()125     protected void batteryOffScreenOn() throws Exception {
126         getDevice().executeShellCommand("dumpsys battery reset");
127         getDevice().executeShellCommand("dumpsys batterystats disable pretend-screen-off");
128     }
129 
forceStop()130     private void forceStop() throws Exception {
131         getDevice().executeShellCommand("am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
132     }
133 
resetBatteryStats()134     private void resetBatteryStats() throws Exception {
135         getDevice().executeShellCommand("dumpsys batterystats --reset");
136     }
137 
testAlarms()138     public void testAlarms() throws Exception {
139         batteryOnScreenOff();
140 
141         installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
142 
143         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsAlarmTest", "testAlarms");
144 
145         assertValueRange("wua", "*walarm*:com.android.server.cts.device.batterystats.ALARM",
146                 5, 3, 3);
147 
148         batteryOffScreenOn();
149     }
150 
testWakeLockDuration()151     public void testWakeLockDuration() throws Exception {
152         batteryOnScreenOff();
153 
154         installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
155 
156         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsWakeLockTests",
157                 "testHoldShortWakeLock");
158 
159         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsWakeLockTests",
160                 "testHoldLongWakeLock");
161 
162         assertValueRange("wl", "BSShortWakeLock", 15, (long) (500 * 0.9), 500 * 2); // partial max duration
163         assertValueRange("wl", "BSLongWakeLock", 15, (long) (3000 * 0.9), 3000 * 2);  // partial max duration
164 
165         batteryOffScreenOn();
166     }
167 
startSimpleActivity()168     private void startSimpleActivity() throws Exception {
169         getDevice().executeShellCommand(
170                 "am start -n com.android.server.cts.device.batterystats/.SimpleActivity");
171     }
172 
testServiceForegroundDuration()173     public void testServiceForegroundDuration() throws Exception {
174         batteryOnScreenOff();
175         installPackage(DEVICE_SIDE_TEST_APK, true);
176 
177         startSimpleActivity();
178         assertValueRange("st", "", STATE_TIME_FOREGROUND_SERVICE_INDEX, 0,
179                 0); // No foreground service time before test
180         final long startTime = System.nanoTime();
181         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsProcessStateTests",
182                 "testForegroundService");
183         assertValueRange("st", "", STATE_TIME_FOREGROUND_SERVICE_INDEX, (long) (2000 * 0.8),
184                 (System.nanoTime() - startTime) / 1000000);
185         batteryOffScreenOn();
186     }
187 
testUidForegroundDuration()188     public void testUidForegroundDuration() throws Exception {
189         batteryOnScreenOff();
190         installPackage(DEVICE_SIDE_TEST_APK, true);
191         // No foreground time before test
192         assertValueRange("st", "", STATE_TIME_FOREGROUND_INDEX, 0, 0);
193         turnScreenOnForReal();
194         assertScreenOn();
195         executeForeground(ACTION_SHOW_APPLICATION_OVERLAY, 2000);
196         Thread.sleep(TIME_SPENT_IN_FOREGROUND); // should be in foreground for about this long
197         assertApproximateTimeInState(STATE_TIME_FOREGROUND_INDEX, TIME_SPENT_IN_FOREGROUND);
198         batteryOffScreenOn();
199     }
200 
testUidBackgroundDuration()201     public void testUidBackgroundDuration() throws Exception {
202         batteryOnScreenOff();
203         installPackage(DEVICE_SIDE_TEST_APK, true);
204         // No background time before test
205         assertValueRange("st", "", STATE_TIME_BACKGROUND_INDEX, 0, 0);
206         executeBackground(ACTION_SLEEP_WHILE_BACKGROUND, 4000);
207         assertApproximateTimeInState(STATE_TIME_BACKGROUND_INDEX, TIME_SPENT_IN_BACKGROUND);
208         batteryOffScreenOn();
209     }
210 
testTopDuration()211     public void testTopDuration() throws Exception {
212         batteryOnScreenOff();
213         installPackage(DEVICE_SIDE_TEST_APK, true);
214         // No top time before test
215         assertValueRange("st", "", STATE_TIME_TOP_INDEX, 0, 0);
216         turnScreenOnForReal();
217         assertScreenOn();
218         executeForeground(ACTION_SLEEP_WHILE_TOP, 4000);
219         assertApproximateTimeInState(STATE_TIME_TOP_INDEX, TIME_SPENT_IN_TOP);
220         batteryOffScreenOn();
221     }
222 
testCachedDuration()223     public void testCachedDuration() throws Exception {
224         batteryOnScreenOff();
225         installPackage(DEVICE_SIDE_TEST_APK, true);
226         // No cached time before test
227         assertValueRange("st", "", STATE_TIME_CACHED_INDEX, 0, 0);
228         startSimpleActivity();
229         Thread.sleep(TIME_SPENT_IN_CACHED); // process should be in cached state for about this long
230         assertApproximateTimeInState(STATE_TIME_CACHED_INDEX, TIME_SPENT_IN_CACHED);
231         batteryOffScreenOn();
232     }
233 
assertScreenOff()234     private void assertScreenOff() throws Exception {
235         final long deadLine = System.currentTimeMillis() + SCREEN_STATE_CHANGE_TIMEOUT;
236         boolean screenAwake = true;
237         do {
238             final String dumpsysPower = getDevice().executeShellCommand("dumpsys power").trim();
239             for (String line : dumpsysPower.split("\n")) {
240                 if (line.contains("Display Power")) {
241                     screenAwake = line.trim().endsWith("ON");
242                     break;
243                 }
244             }
245             Thread.sleep(SCREEN_STATE_POLLING_INTERVAL);
246         } while (screenAwake && System.currentTimeMillis() < deadLine);
247         assertFalse("Screen could not be turned off", screenAwake);
248     }
249 
assertScreenOn()250     private void assertScreenOn() throws Exception {
251         // this also checks that the keyguard is dismissed
252         final long deadLine = System.currentTimeMillis() + SCREEN_STATE_CHANGE_TIMEOUT;
253         boolean screenAwake;
254         do {
255             final String dumpsysWindowPolicy =
256                     getDevice().executeShellCommand("dumpsys window policy").trim();
257             boolean keyguardStateLines = false;
258             screenAwake = true;
259             for (String line : dumpsysWindowPolicy.split("\n")) {
260                 if (line.contains("KeyguardServiceDelegate")) {
261                     keyguardStateLines = true;
262                 } else if (keyguardStateLines && line.contains("showing=")) {
263                     screenAwake &= line.trim().endsWith("false");
264                 } else if (keyguardStateLines && line.contains("screenState=")) {
265                     screenAwake &= line.trim().endsWith("SCREEN_STATE_ON");
266                 }
267             }
268             Thread.sleep(SCREEN_STATE_POLLING_INTERVAL);
269         } while (!screenAwake && System.currentTimeMillis() < deadLine);
270         assertTrue("Screen could not be turned on", screenAwake);
271     }
272 
testBleScans()273     public void testBleScans() throws Exception {
274         if (noBattery() || !hasFeature(FEATURE_BLUETOOTH_LE, true)) {
275             return;
276         }
277 
278         batteryOnScreenOff();
279         installPackage(DEVICE_SIDE_TEST_APK, true);
280         turnScreenOnForReal();
281         assertScreenOn();
282 
283         // Background test.
284         executeBackground(ACTION_BLE_SCAN_UNOPTIMIZED, 40_000);
285         assertValueRange("blem", "", 5, 1, 1); // ble_scan_count
286         assertValueRange("blem", "", 6, 1, 1); // ble_scan_count_bg
287 
288         // Foreground test.
289         executeForeground(ACTION_BLE_SCAN_UNOPTIMIZED, 40_000);
290         assertValueRange("blem", "", 5, 2, 2); // ble_scan_count
291         assertValueRange("blem", "", 6, 1, 1); // ble_scan_count_bg
292 
293         batteryOffScreenOn();
294     }
295 
296 
testUnoptimizedBleScans()297     public void testUnoptimizedBleScans() throws Exception {
298         if (noBattery() || !hasFeature(FEATURE_BLUETOOTH_LE, true)) {
299             return;
300         }
301         batteryOnScreenOff();
302         installPackage(DEVICE_SIDE_TEST_APK, true);
303         turnScreenOnForReal();
304         assertScreenOn();
305         // Ble scan time in BatteryStatsBgVsFgActions is 2 seconds, but be lenient.
306         final int minTime = 1500; // min single scan time in ms
307         final int maxTime = 3000; // max single scan time in ms
308 
309         // Optimized - Background.
310         executeBackground(ACTION_BLE_SCAN_OPTIMIZED, 40_000);
311         assertValueRange("blem", "", 7, 1*minTime, 1*maxTime); // actualTime
312         assertValueRange("blem", "", 8, 1*minTime, 1*maxTime); // actualTimeBg
313         assertValueRange("blem", "", 11, 0, 0); // unoptimizedScanTotalTime
314         assertValueRange("blem", "", 12, 0, 0); // unoptimizedScanTotalTimeBg
315         assertValueRange("blem", "", 13, 0, 0); // unoptimizedScanMaxTime
316         assertValueRange("blem", "", 14, 0, 0); // unoptimizedScanMaxTimeBg
317 
318         // Optimized - Foreground.
319         executeForeground(ACTION_BLE_SCAN_OPTIMIZED, 40_000);
320         assertValueRange("blem", "", 7, 2*minTime, 2*maxTime); // actualTime
321         assertValueRange("blem", "", 8, 1*minTime, 1*maxTime); // actualTimeBg
322         assertValueRange("blem", "", 11, 0, 0); // unoptimizedScanTotalTime
323         assertValueRange("blem", "", 12, 0, 0); // unoptimizedScanTotalTimeBg
324         assertValueRange("blem", "", 13, 0, 0); // unoptimizedScanMaxTime
325         assertValueRange("blem", "", 14, 0, 0); // unoptimizedScanMaxTimeBg
326 
327         // Unoptimized - Background.
328         executeBackground(ACTION_BLE_SCAN_UNOPTIMIZED, 40_000);
329         assertValueRange("blem", "", 7, 3*minTime, 3*maxTime); // actualTime
330         assertValueRange("blem", "", 8, 2*minTime, 2*maxTime); // actualTimeBg
331         assertValueRange("blem", "", 11, 1*minTime, 1*maxTime); // unoptimizedScanTotalTime
332         assertValueRange("blem", "", 12, 1*minTime, 1*maxTime); // unoptimizedScanTotalTimeBg
333         assertValueRange("blem", "", 13, 1*minTime, 1*maxTime); // unoptimizedScanMaxTime
334         assertValueRange("blem", "", 14, 1*minTime, 1*maxTime); // unoptimizedScanMaxTimeBg
335 
336         // Unoptimized - Foreground.
337         executeForeground(ACTION_BLE_SCAN_UNOPTIMIZED, 40_000);
338         assertValueRange("blem", "", 7, 4*minTime, 4*maxTime); // actualTime
339         assertValueRange("blem", "", 8, 2*minTime, 2*maxTime); // actualTimeBg
340         assertValueRange("blem", "", 11, 2*minTime, 2*maxTime); // unoptimizedScanTotalTime
341         assertValueRange("blem", "", 12, 1*minTime, 1*maxTime); // unoptimizedScanTotalTimeBg
342         assertValueRange("blem", "", 13, 1*minTime, 1*maxTime); // unoptimizedScanMaxTime
343         assertValueRange("blem", "", 14, 1*minTime, 1*maxTime); // unoptimizedScanMaxTimeBg
344 
345         batteryOffScreenOn();
346     }
347 
testGpsUpdates()348     public void testGpsUpdates() throws Exception {
349         if (noBattery() || !hasFeature(FEATURE_LOCATION_GPS, true)) {
350             return;
351         }
352 
353         final String gpsSensorNumber = "-10000";
354 
355         batteryOnScreenOff();
356         installPackage(DEVICE_SIDE_TEST_APK, true);
357         // Whitelist this app against background location request throttling
358         String origWhitelist = getDevice().executeShellCommand(
359                 "settings get global location_background_throttle_package_whitelist").trim();
360         getDevice().executeShellCommand(String.format(
361                 "settings put global location_background_throttle_package_whitelist %s",
362                 DEVICE_SIDE_TEST_PACKAGE));
363 
364         try {
365             // Background test.
366             executeBackground(ACTION_GPS, 60_000);
367             assertValueRange("sr", gpsSensorNumber, 6, 1, 1); // count
368             assertValueRange("sr", gpsSensorNumber, 7, 1, 1); // background_count
369 
370             // Foreground test.
371             executeForeground(ACTION_GPS, 60_000);
372             assertValueRange("sr", gpsSensorNumber, 6, 2, 2); // count
373             assertValueRange("sr", gpsSensorNumber, 7, 1, 1); // background_count
374         } finally {
375             if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
376                 getDevice().executeShellCommand(
377                         "settings delete global location_background_throttle_package_whitelist");
378             } else {
379                 getDevice().executeShellCommand(String.format(
380                         "settings put global location_background_throttle_package_whitelist %s",
381                         origWhitelist));
382             }
383             batteryOffScreenOn();
384         }
385     }
386 
testJobBgVsFg()387     public void testJobBgVsFg() throws Exception {
388         if (noBattery()) {
389             return;
390         }
391         batteryOnScreenOff();
392         installPackage(DEVICE_SIDE_TEST_APK, true);
393         turnScreenOnForReal();
394         assertScreenOn();
395         allowImmediateSyncs();
396 
397         // Background test.
398         executeBackground(ACTION_JOB_SCHEDULE, 60_000);
399         assertValueRange("jb", "", 6, 1, 1); // count
400         assertValueRange("jb", "", 8, 1, 1); // background_count
401 
402         // Foreground test.
403         executeForeground(ACTION_JOB_SCHEDULE, 60_000);
404         assertValueRange("jb", "", 6, 2, 2); // count
405         assertValueRange("jb", "", 8, 1, 1); // background_count
406 
407         batteryOffScreenOn();
408     }
409 
testSyncBgVsFg()410     public void testSyncBgVsFg() throws Exception {
411         if (noBattery()) {
412             return;
413         }
414         batteryOnScreenOff();
415         installPackage(DEVICE_SIDE_TEST_APK, true);
416         turnScreenOnForReal();
417         assertScreenOn();
418         allowImmediateSyncs();
419 
420         // Background test.
421         executeBackground(ACTION_SYNC, 60_000);
422         // Allow one or two syncs in this time frame (not just one) due to unpredictable syncs.
423         assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 6, 1, 2); // count
424         assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 8, 1, 2); // background_count
425 
426         // Foreground test.
427         executeForeground(ACTION_SYNC, 60_000);
428         assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 6, 2, 4); // count
429         assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 8, 1, 2); // background_count
430 
431         batteryOffScreenOn();
432     }
433 
434     /**
435      * Tests whether the on-battery realtime and total realtime values
436      * are properly updated in battery stats.
437      */
testRealtime()438     public void testRealtime() throws Exception {
439         batteryOnScreenOff();
440         long startingValueRealtime = getLongValue(0, "bt", "", 7);
441         long startingValueBatteryRealtime = getLongValue(0, "bt", "", 5);
442         // After going on battery
443         Thread.sleep(2000);
444         batteryOffScreenOn();
445         // After going off battery
446         Thread.sleep(2000);
447 
448         long currentValueRealtime = getLongValue(0, "bt", "", 7);
449         long currentValueBatteryRealtime = getLongValue(0, "bt", "", 5);
450 
451         // Total realtime increase should be 4000ms at least
452         assertTrue(currentValueRealtime >= startingValueRealtime + 4000);
453         // But not too much more
454         assertTrue(currentValueRealtime < startingValueRealtime + 6000);
455         // Battery on realtime should be more than 2000 but less than 4000
456         assertTrue(currentValueBatteryRealtime >= startingValueBatteryRealtime + 2000);
457         assertTrue(currentValueBatteryRealtime < startingValueBatteryRealtime + 4000);
458     }
459 
460     /**
461      * Tests the total duration reported for jobs run on the job scheduler.
462      */
463     public void testJobDuration() throws Exception {
464         batteryOnScreenOff();
465 
466         installPackage(DEVICE_SIDE_TEST_APK, true);
467         allowImmediateSyncs();
468 
469         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsJobDurationTests",
470                 "testJobDuration");
471 
472         // Should be approximately 15000 ms (3 x 5000 ms). Use 0.8x and 2x as the lower and upper
473         // bounds to account for possible errors due to thread scheduling and cpu load.
474         assertValueRange("jb", DEVICE_SIDE_JOB_COMPONENT, 5, (long) (15000 * 0.8), 15000 * 2);
475         batteryOffScreenOn();
476     }
477 
478     /**
479      * Tests the total duration and # of syncs reported for sync activities.
480      */
481     public void testSyncs() throws Exception {
482         batteryOnScreenOff();
483 
484         installPackage(DEVICE_SIDE_TEST_APK, true);
485         allowImmediateSyncs();
486 
487         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsSyncTest", "testRunSyncs");
488 
489         // First, check the count, which should be 10.
490         // (It could be 11, if the initial sync actually happened before getting cancelled.)
491         assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 6, 10L, 11L);
492 
493         // Should be approximately, but at least 10 seconds. Use 2x as the upper
494         // bounds to account for possible errors due to thread scheduling and cpu load.
495         assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 5, 10000, 10000 * 2);
496     }
497 
498     private int getUid() throws Exception {
499         String uidLine = getDevice().executeShellCommand("cmd package list packages -U "
500                 + DEVICE_SIDE_TEST_PACKAGE);
501         String[] uidLineParts = uidLine.split(":");
502         // 3rd entry is package uid
503         assertTrue(uidLineParts.length > 2);
504         int uid = Integer.parseInt(uidLineParts[2].trim());
505         assertTrue(uid > 10000);
506         return uid;
507     }
508 
assertApproximateTimeInState(int index, long duration)509     private void assertApproximateTimeInState(int index, long duration) throws Exception {
510         assertValueRange("st", "", index, (long) (0.7 * duration), 2 * duration);
511     }
512 
513     /**
514      * Verifies that the recorded time for the specified tag and name in the test package
515      * is within the specified range.
516      */
assertValueRange(String tag, String optionalAfterTag, int index, long min, long max)517     private void assertValueRange(String tag, String optionalAfterTag,
518             int index, long min, long max) throws Exception {
519         int uid = getUid();
520         long value = getLongValue(uid, tag, optionalAfterTag, index);
521         assertTrue("Value " + value + " is less than min " + min, value >= min);
522         assertTrue("Value " + value + " is greater than max " + max, value <= max);
523     }
524 
525     /**
526      * Returns a particular long value from a line matched by uid, tag and the optionalAfterTag.
527      */
getLongValue(int uid, String tag, String optionalAfterTag, int index)528     private long getLongValue(int uid, String tag, String optionalAfterTag, int index)
529             throws Exception {
530         String dumpsys = getDevice().executeShellCommand("dumpsys batterystats --checkin");
531         String[] lines = dumpsys.split("\n");
532         long value = 0;
533         if (optionalAfterTag == null) {
534             optionalAfterTag = "";
535         }
536         for (int i = lines.length - 1; i >= 0; i--) {
537             String line = lines[i];
538             if (line.contains(uid + ",l," + tag + "," + optionalAfterTag)
539                     || (!optionalAfterTag.equals("") &&
540                         line.contains(uid + ",l," + tag + ",\"" + optionalAfterTag))) {
541                 String[] wlParts = line.split(",");
542                 value = Long.parseLong(wlParts[index]);
543             }
544         }
545         return value;
546     }
547 
548     /**
549      * Runs a (background) service to perform the given action, and waits for
550      * the device to report that the action has finished (via a logcat message) before returning.
551      * @param actionValue one of the constants in BatteryStatsBgVsFgActions indicating the desired
552      *                    action to perform.
553      * @param maxTimeMs max time to wait (in ms) for action to report that it has completed.
554      * @return A string, representing a random integer, assigned to this particular request for the
555      *                     device to perform the given action. This value can be used to receive
556      *                     communications via logcat from the device about this action.
557      */
executeBackground(String actionValue, int maxTimeMs)558     private String executeBackground(String actionValue, int maxTimeMs) throws Exception {
559         String requestCode = executeBackground(actionValue);
560         String searchString = getCompletedActionString(actionValue, requestCode);
561         checkLogcatForText(BG_VS_FG_TAG, searchString, maxTimeMs);
562         return requestCode;
563     }
564 
565     /**
566      * Runs a (background) service to perform the given action.
567      * @param actionValue one of the constants in BatteryStatsBgVsFgActions indicating the desired
568      *                    action to perform.
569      * @return A string, representing a random integer, assigned to this particular request for the
570       *                     device to perform the given action. This value can be used to receive
571       *                     communications via logcat from the device about this action.
572      */
executeBackground(String actionValue)573     private String executeBackground(String actionValue) throws Exception {
574         allowBackgroundServices();
575         String requestCode = Integer.toString(new Random().nextInt());
576         getDevice().executeShellCommand(String.format(
577                 "am startservice -n '%s' -e %s %s -e %s %s",
578                 DEVICE_SIDE_BG_SERVICE_COMPONENT,
579                 KEY_ACTION, actionValue,
580                 KEY_REQUEST_CODE, requestCode));
581         return requestCode;
582     }
583 
584     /** Required to successfully start a background service from adb in O. */
allowBackgroundServices()585     private void allowBackgroundServices() throws Exception {
586         getDevice().executeShellCommand(String.format(
587                 "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
588     }
589 
590     /** Make the test-app standby-active so it can run syncs and jobs immediately. */
allowImmediateSyncs()591     protected void allowImmediateSyncs() throws Exception {
592         getDevice().executeShellCommand("am set-standby-bucket "
593                 + DEVICE_SIDE_TEST_PACKAGE + " active");
594     }
595 
596     /**
597      * Runs an activity (in the foreground) to perform the given action, and waits
598      * for the device to report that the action has finished (via a logcat message) before returning.
599      * @param actionValue one of the constants in BatteryStatsBgVsFgActions indicating the desired
600      *                    action to perform.
601      * @param maxTimeMs max time to wait (in ms) for action to report that it has completed.
602      * @return A string, representing a random integer, assigned to this particular request for the
603      *                     device to perform the given action. This value can be used to receive
604      *                     communications via logcat from the device about this action.
605      */
executeForeground(String actionValue, int maxTimeMs)606     private String executeForeground(String actionValue, int maxTimeMs) throws Exception {
607         String requestCode = executeForeground(actionValue);
608         String searchString = getCompletedActionString(actionValue, requestCode);
609         checkLogcatForText(BG_VS_FG_TAG, searchString, maxTimeMs);
610         return requestCode;
611     }
612 
613     /**
614      * Runs an activity (in the foreground) to perform the given action.
615      * @param actionValue one of the constants in BatteryStatsBgVsFgActions indicating the desired
616      *                    action to perform.
617      * @return A string, representing a random integer, assigned to this particular request for the
618      *                     device to perform the given action. This value can be used to receive
619      *                     communications via logcat from the device about this action.
620      */
executeForeground(String actionValue)621     private String executeForeground(String actionValue) throws Exception {
622         String requestCode = Integer.toString(new Random().nextInt());
623         getDevice().executeShellCommand(String.format(
624                 "am start -n '%s' -e %s %s -e %s %s",
625                 DEVICE_SIDE_FG_ACTIVITY_COMPONENT,
626                 KEY_ACTION, actionValue,
627                 KEY_REQUEST_CODE, requestCode));
628         return requestCode;
629     }
630 
631     /**
632      * The string that will be printed in the logcat when the action completes. This needs to be
633      * identical to {@link com.android.server.cts.device.batterystats.BatteryStatsBgVsFgActions#tellHostActionFinished}.
634      */
getCompletedActionString(String actionValue, String requestCode)635     private String getCompletedActionString(String actionValue, String requestCode) {
636         return String.format("Completed performing %s for request %s", actionValue, requestCode);
637     }
638 
639     /** Determine if device has no battery and is not expected to have proper batterystats. */
noBattery()640     private boolean noBattery() throws Exception {
641         final String batteryinfo = getDevice().executeShellCommand("dumpsys battery");
642         boolean hasBattery = batteryinfo.contains("present: true");
643         if (!hasBattery) {
644             LogUtil.CLog.w("Device does not have a battery");
645         }
646         return !hasBattery;
647     }
648 
649     /**
650      * Determines if the device has the given feature.
651      * Prints a warning if its value differs from requiredAnswer.
652      */
hasFeature(String featureName, boolean requiredAnswer)653     private boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception {
654         final String features = getDevice().executeShellCommand("pm list features");
655         boolean hasIt = features.contains(featureName);
656         if (hasIt != requiredAnswer) {
657             LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature "
658                     + featureName);
659         }
660         return hasIt;
661     }
662 }
663