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 
17 package android.alarmmanager.cts;
18 
19 import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
20 
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assume.assumeTrue;
24 
25 import android.alarmmanager.alarmtestapp.cts.TestAlarmReceiver;
26 import android.alarmmanager.alarmtestapp.cts.TestAlarmScheduler;
27 import android.app.AlarmManager;
28 import android.content.BroadcastReceiver;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.os.BatteryManager;
34 import android.os.SystemClock;
35 import android.platform.test.annotations.AppModeFull;
36 import android.support.test.uiautomator.UiDevice;
37 import android.util.Log;
38 import android.util.LongArray;
39 
40 import androidx.test.InstrumentationRegistry;
41 import androidx.test.filters.LargeTest;
42 import androidx.test.runner.AndroidJUnit4;
43 
44 import com.android.compatibility.common.util.AppStandbyUtils;
45 
46 import org.junit.After;
47 import org.junit.AfterClass;
48 import org.junit.Before;
49 import org.junit.BeforeClass;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 
53 import java.io.IOException;
54 import java.util.concurrent.atomic.AtomicInteger;
55 
56 /**
57  * Tests that app standby imposes the appropriate restrictions on alarms
58  */
59 @AppModeFull
60 @LargeTest
61 @RunWith(AndroidJUnit4.class)
62 public class AppStandbyTests {
63     private static final String TAG = AppStandbyTests.class.getSimpleName();
64     private static final String TEST_APP_PACKAGE = "android.alarmmanager.alarmtestapp.cts";
65     private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestAlarmScheduler";
66 
67     private static final long DEFAULT_WAIT = 2_000;
68     private static final long POLL_INTERVAL = 200;
69 
70     // Tweaked alarm manager constants to facilitate testing
71     private static final long ALLOW_WHILE_IDLE_SHORT_TIME = 10_000;
72     private static final long MIN_FUTURITY = 1_000;
73 
74     // Not touching ACTIVE and RARE parameters for this test
75     private static final int WORKING_INDEX = 0;
76     private static final int FREQUENT_INDEX = 1;
77     private static final int RARE_INDEX = 2;
78     private static final String[] APP_BUCKET_TAGS = {
79             "working_set",
80             "frequent",
81             "rare",
82     };
83     private static final String[] APP_BUCKET_DELAY_KEYS = {
84             "standby_working_delay",
85             "standby_frequent_delay",
86             "standby_rare_delay",
87     };
88     private static final long[] APP_STANDBY_DELAYS = {
89             5_000,   // Working set
90             10_000,   // Frequent
91             15_000,  // Rare
92     };
93     private static final long APP_STANDBY_WINDOW = 10_000;
94     private static final String[] APP_BUCKET_QUOTA_KEYS = {
95             "standby_working_quota",
96             "standby_frequent_quota",
97             "standby_rare_quota",
98     };
99     private static final int[] APP_STANDBY_QUOTAS = {
100             5,  // Working set
101             3,  // Frequent
102             1,  // Rare
103     };
104 
105     // Settings common for all tests
106     private static final String COMMON_SETTINGS;
107 
108     static {
109         final StringBuilder settings = new StringBuilder();
110         settings.append("min_futurity=");
111         settings.append(MIN_FUTURITY);
112         settings.append(",allow_while_idle_short_time=");
113         settings.append(ALLOW_WHILE_IDLE_SHORT_TIME);
114         for (int i = 0; i < APP_STANDBY_DELAYS.length; i++) {
115             settings.append(",");
116             settings.append(APP_BUCKET_DELAY_KEYS[i]);
117             settings.append("=");
118             settings.append(APP_STANDBY_DELAYS[i]);
119         }
120         settings.append(",app_standby_window=");
121         settings.append(APP_STANDBY_WINDOW);
122         for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) {
123             settings.append(",");
124             settings.append(APP_BUCKET_QUOTA_KEYS[i]);
125             settings.append("=");
126             settings.append(APP_STANDBY_QUOTAS[i]);
127         }
128         COMMON_SETTINGS = settings.toString();
129     }
130 
131     // Save the state before running tests to restore it after we finish testing.
132     private static boolean sOrigAppStandbyEnabled;
133     // Test app's alarm history to help predict when a subsequent alarm is going to get deferred.
134     private static TestAlarmHistory sAlarmHistory;
135 
136     private Context mContext;
137     private ComponentName mAlarmScheduler;
138     private UiDevice mUiDevice;
139     private AtomicInteger mAlarmCount;
140 
141     private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() {
142         @Override
143         public void onReceive(Context context, Intent intent) {
144             mAlarmCount.getAndAdd(intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1));
145             final long nowElapsed = SystemClock.elapsedRealtime();
146             sAlarmHistory.addTime(nowElapsed);
147             Log.d(TAG, "No. of expirations: " + mAlarmCount + " elapsed: " + nowElapsed);
148         }
149     };
150 
151     @BeforeClass
setUpTests()152     public static void setUpTests() throws Exception {
153         sAlarmHistory = new TestAlarmHistory();
154         sOrigAppStandbyEnabled = AppStandbyUtils.isAppStandbyEnabledAtRuntime();
155         if (!sOrigAppStandbyEnabled) {
156             AppStandbyUtils.setAppStandbyEnabledAtRuntime(true);
157 
158             // Give system sometime to initialize itself.
159             Thread.sleep(100);
160         }
161     }
162 
163     @Before
setUp()164     public void setUp() throws Exception {
165         mContext = InstrumentationRegistry.getTargetContext();
166         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
167         mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER);
168         mAlarmCount = new AtomicInteger(0);
169         updateAlarmManagerConstants(true);
170         setBatteryCharging(false);
171         final IntentFilter intentFilter = new IntentFilter();
172         intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
173         mContext.registerReceiver(mAlarmStateReceiver, intentFilter);
174         assumeTrue("App Standby not enabled on device", AppStandbyUtils.isAppStandbyEnabled());
175     }
176 
scheduleAlarm(long triggerMillis, boolean allowWhileIdle, long interval)177     private void scheduleAlarm(long triggerMillis, boolean allowWhileIdle, long interval) {
178         final Intent setAlarmIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM);
179         setAlarmIntent.setComponent(mAlarmScheduler);
180         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TYPE, ELAPSED_REALTIME_WAKEUP);
181         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TRIGGER_TIME, triggerMillis);
182         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_REPEAT_INTERVAL, interval);
183         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_ALLOW_WHILE_IDLE, allowWhileIdle);
184         setAlarmIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
185         mContext.sendBroadcast(setAlarmIntent);
186     }
187 
scheduleAlarmClock(long triggerRTC)188     private void scheduleAlarmClock(long triggerRTC) {
189         AlarmManager.AlarmClockInfo alarmInfo = new AlarmManager.AlarmClockInfo(triggerRTC, null);
190 
191         final Intent setAlarmClockIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM_CLOCK);
192         setAlarmClockIntent.setComponent(mAlarmScheduler);
193         setAlarmClockIntent.putExtra(TestAlarmScheduler.EXTRA_ALARM_CLOCK_INFO, alarmInfo);
194         mContext.sendBroadcast(setAlarmClockIntent);
195     }
196 
197 
testBucketDelay(int bucketIndex)198     private void testBucketDelay(int bucketIndex) throws Exception {
199         setAppStandbyBucket("active");
200         final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
201         scheduleAlarm(firstTrigger, false, 0);
202         Thread.sleep(MIN_FUTURITY);
203         assertTrue("Alarm did not fire when app in active", waitForAlarm());
204 
205         setAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]);
206         final long nextTriggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
207         final long minTriggerTime = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[bucketIndex];
208         scheduleAlarm(nextTriggerTime, false, 0);
209         Thread.sleep(MIN_FUTURITY);
210         if (nextTriggerTime + DEFAULT_WAIT < minTriggerTime) {
211             assertFalse("Alarm went off before " + APP_BUCKET_TAGS[bucketIndex] + " delay",
212                     waitForAlarm());
213         }
214         Thread.sleep(minTriggerTime - SystemClock.elapsedRealtime());
215         assertTrue("Deferred alarm did not go off after " + APP_BUCKET_TAGS[bucketIndex] + " delay",
216                 waitForAlarm());
217     }
218 
219     @Test
testActiveDelay()220     public void testActiveDelay() throws Exception {
221         updateAlarmManagerConstants(false);
222         setAppStandbyBucket("active");
223         long nextTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
224         for (int i = 0; i < 3; i++) {
225             scheduleAlarm(nextTrigger, false, 0);
226             Thread.sleep(MIN_FUTURITY);
227             assertTrue("Alarm not received as expected when app is in active", waitForAlarm());
228             nextTrigger += MIN_FUTURITY;
229         }
230     }
231 
232     @Test
testWorkingSetDelay()233     public void testWorkingSetDelay() throws Exception {
234         updateAlarmManagerConstants(false);
235         testBucketDelay(WORKING_INDEX);
236     }
237 
238     @Test
testFrequentDelay()239     public void testFrequentDelay() throws Exception {
240         updateAlarmManagerConstants(false);
241         testBucketDelay(FREQUENT_INDEX);
242     }
243 
244     @Test
testRareDelay()245     public void testRareDelay() throws Exception {
246         updateAlarmManagerConstants(false);
247         testBucketDelay(RARE_INDEX);
248     }
249 
250     @Test
testNeverDelay()251     public void testNeverDelay() throws Exception {
252         updateAlarmManagerConstants(false);
253         setAppStandbyBucket("active");
254         final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
255         scheduleAlarm(firstTrigger, false, 0);
256         Thread.sleep(MIN_FUTURITY);
257         assertTrue("Alarm did not fire when app in active", waitForAlarm());
258 
259         setAppStandbyBucket("never");
260         final long expectedTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
261         scheduleAlarm(expectedTrigger, true, 0);
262         Thread.sleep(10_000);
263         assertFalse("Alarm received when app was in never bucket", waitForAlarm());
264     }
265 
266     @Test
testBucketUpgradeToSmallerDelay()267     public void testBucketUpgradeToSmallerDelay() throws Exception {
268         updateAlarmManagerConstants(false);
269         setAppStandbyBucket("active");
270         final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
271         scheduleAlarm(firstTrigger, false, 0);
272         Thread.sleep(MIN_FUTURITY);
273         assertTrue("Alarm did not fire when app in active", waitForAlarm());
274 
275         setAppStandbyBucket(APP_BUCKET_TAGS[FREQUENT_INDEX]);
276         final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
277         final long workingSetExpectedTrigger = sAlarmHistory.getLast(1)
278                 + APP_STANDBY_DELAYS[WORKING_INDEX];
279         scheduleAlarm(triggerTime, false, 0);
280         Thread.sleep(workingSetExpectedTrigger - SystemClock.elapsedRealtime());
281         assertFalse("The alarm went off before frequent delay", waitForAlarm());
282         setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
283         assertTrue("The alarm did not go off when app bucket upgraded to working_set",
284                 waitForAlarm());
285     }
286 
287     /**
288      * This is different to {@link #testBucketUpgradeToSmallerDelay()} in the sense that the bucket
289      * upgrade shifts eligibility to a point earlier than when the alarm is scheduled for.
290      * The alarm must then go off as soon as possible - at either the scheduled time or the bucket
291      * change, whichever happened later.
292      */
293     @Test
testBucketUpgradeToNoDelay()294     public void testBucketUpgradeToNoDelay() throws Exception {
295         updateAlarmManagerConstants(false);
296 
297         setAppStandbyBucket("active");
298         final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
299         scheduleAlarm(firstTrigger, false, 0);
300         Thread.sleep(MIN_FUTURITY);
301         assertTrue("Alarm did not fire when app in active", waitForAlarm());
302 
303         setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
304         final long triggerTime1 = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[FREQUENT_INDEX];
305         scheduleAlarm(triggerTime1, false, 0);
306         Thread.sleep(triggerTime1 - SystemClock.elapsedRealtime());
307         assertFalse("The alarm went off after frequent delay when app in rare bucket",
308                 waitForAlarm());
309         setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
310         assertTrue("The alarm did not go off when app bucket upgraded to working_set",
311                 waitForAlarm());
312 
313         // Once more
314         setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
315         final long triggerTime2 = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[FREQUENT_INDEX];
316         scheduleAlarm(triggerTime2, false, 0);
317         setAppStandbyBucket("active");
318         Thread.sleep(triggerTime2 - SystemClock.elapsedRealtime());
319         assertTrue("The alarm did not go off as scheduled when the app was in active",
320                 waitForAlarm());
321     }
322 
testSimpleQuotaDeferral(int bucketIndex)323     public void testSimpleQuotaDeferral(int bucketIndex) throws Exception {
324         setAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]);
325         final int quota = APP_STANDBY_QUOTAS[bucketIndex];
326 
327         long startElapsed = SystemClock.elapsedRealtime();
328         final long freshWindowPoint = sAlarmHistory.getLast(1) + APP_STANDBY_WINDOW + 1;
329         if (freshWindowPoint > startElapsed) {
330             Thread.sleep(freshWindowPoint - startElapsed);
331             startElapsed = freshWindowPoint;
332             // Now we should have no alarms in the past APP_STANDBY_WINDOW
333         }
334         final long desiredTrigger = startElapsed + APP_STANDBY_WINDOW;
335         final long firstTrigger = startElapsed + 4_000;
336         assertTrue("Quota too large for test",
337                 firstTrigger + ((quota - 1) * MIN_FUTURITY) < desiredTrigger);
338         for (int i = 0; i < quota; i++) {
339             final long trigger = firstTrigger + (i * MIN_FUTURITY);
340             scheduleAlarm(trigger, false, 0);
341             Thread.sleep(trigger - SystemClock.elapsedRealtime());
342             assertTrue("Alarm within quota not firing as expected", waitForAlarm());
343         }
344 
345         // Now quota is reached, any subsequent alarm should get deferred.
346         scheduleAlarm(desiredTrigger, false, 0);
347         Thread.sleep(desiredTrigger - SystemClock.elapsedRealtime());
348         assertFalse("Alarm exceeding quota not deferred", waitForAlarm());
349         final long minTrigger = firstTrigger + 1 + APP_STANDBY_WINDOW;
350         Thread.sleep(minTrigger - SystemClock.elapsedRealtime());
351         assertTrue("Alarm exceeding quota not delivered after expected delay", waitForAlarm());
352     }
353 
354     @Test
testActiveQuota()355     public void testActiveQuota() throws Exception {
356         setAppStandbyBucket("active");
357         long nextTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
358         for (int i = 0; i < 3; i++) {
359             scheduleAlarm(nextTrigger, false, 0);
360             Thread.sleep(MIN_FUTURITY);
361             assertTrue("Alarm not received as expected when app is in active", waitForAlarm());
362             nextTrigger += MIN_FUTURITY;
363         }
364     }
365 
366     @Test
testWorkingQuota()367     public void testWorkingQuota() throws Exception {
368         testSimpleQuotaDeferral(WORKING_INDEX);
369     }
370 
371     @Test
testFrequentQuota()372     public void testFrequentQuota() throws Exception {
373         testSimpleQuotaDeferral(FREQUENT_INDEX);
374     }
375 
376     @Test
testRareQuota()377     public void testRareQuota() throws Exception {
378         testSimpleQuotaDeferral(RARE_INDEX);
379     }
380 
381     @Test
testNeverQuota()382     public void testNeverQuota() throws Exception {
383         setAppStandbyBucket("never");
384         final long expectedTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
385         scheduleAlarm(expectedTrigger, true, 0);
386         Thread.sleep(10_000);
387         assertFalse("Alarm received when app was in never bucket", waitForAlarm());
388     }
389 
390     @Test
testAlarmClockUnaffected()391     public void testAlarmClockUnaffected() throws Exception {
392         setAppStandbyBucket("never");
393         final long trigger = System.currentTimeMillis() + MIN_FUTURITY;
394         scheduleAlarmClock(trigger);
395         Thread.sleep(MIN_FUTURITY);
396         assertTrue("Alarm clock not received as expected", waitForAlarm());
397     }
398 
399     @Test
testAllowWhileIdleAlarms()400     public void testAllowWhileIdleAlarms() throws Exception {
401         updateAlarmManagerConstants(false);
402         setAppStandbyBucket("active");
403         final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
404         scheduleAlarm(firstTrigger, true, 0);
405         Thread.sleep(MIN_FUTURITY);
406         assertTrue("first allow_while_idle alarm did not go off as scheduled", waitForAlarm());
407         scheduleAlarm(sAlarmHistory.getLast(1) + 7_000, true, 0);
408         // First check for the case where allow_while_idle delay should supersede app standby
409         setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
410         Thread.sleep(APP_STANDBY_DELAYS[WORKING_INDEX]);
411         assertFalse("allow_while_idle alarm went off before short time", waitForAlarm());
412         long expectedTriggerTime = sAlarmHistory.getLast(1) + ALLOW_WHILE_IDLE_SHORT_TIME;
413         Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
414         assertTrue("allow_while_idle alarm did not go off after short time", waitForAlarm());
415 
416         // Now the other case, app standby delay supersedes the allow_while_idle delay
417         scheduleAlarm(sAlarmHistory.getLast(1) + 7_000, true, 0);
418         setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
419         Thread.sleep(ALLOW_WHILE_IDLE_SHORT_TIME);
420         assertFalse("allow_while_idle alarm went off before " + APP_STANDBY_DELAYS[RARE_INDEX]
421                 + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
422         expectedTriggerTime = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[RARE_INDEX];
423         Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
424         assertTrue("allow_while_idle alarm did not go off even after "
425                 + APP_STANDBY_DELAYS[RARE_INDEX]
426                 + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
427     }
428 
429     @Test
testPowerWhitelistedAlarmNotBlocked()430     public void testPowerWhitelistedAlarmNotBlocked() throws Exception {
431         setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
432         setPowerWhitelisted(true);
433         final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
434         scheduleAlarm(triggerTime, false, 0);
435         Thread.sleep(MIN_FUTURITY);
436         assertTrue("Alarm did not go off for whitelisted app in rare bucket", waitForAlarm());
437         setPowerWhitelisted(false);
438     }
439 
440     @After
tearDown()441     public void tearDown() throws Exception {
442         setPowerWhitelisted(false);
443         setBatteryCharging(true);
444         deleteAlarmManagerConstants();
445         final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS);
446         cancelAlarmsIntent.setComponent(mAlarmScheduler);
447         mContext.sendBroadcast(cancelAlarmsIntent);
448         mContext.unregisterReceiver(mAlarmStateReceiver);
449         // Broadcast unregister may race with the next register in setUp
450         Thread.sleep(500);
451     }
452 
453     @AfterClass
tearDownTests()454     public static void tearDownTests() throws Exception {
455         if (!sOrigAppStandbyEnabled) {
456             AppStandbyUtils.setAppStandbyEnabledAtRuntime(sOrigAppStandbyEnabled);
457         }
458     }
459 
updateAlarmManagerConstants(boolean enableQuota)460     private void updateAlarmManagerConstants(boolean enableQuota) throws IOException {
461         final StringBuffer cmd = new StringBuffer("settings put global alarm_manager_constants ");
462         cmd.append(COMMON_SETTINGS);
463         if (!enableQuota) {
464             cmd.append(",app_standby_quotas_enabled=false");
465         }
466         executeAndLog(cmd.toString());
467     }
468 
setPowerWhitelisted(boolean whitelist)469     private void setPowerWhitelisted(boolean whitelist) throws IOException {
470         final StringBuffer cmd = new StringBuffer("cmd deviceidle whitelist ");
471         cmd.append(whitelist ? "+" : "-");
472         cmd.append(TEST_APP_PACKAGE);
473         executeAndLog(cmd.toString());
474     }
475 
deleteAlarmManagerConstants()476     private void deleteAlarmManagerConstants() throws IOException {
477         executeAndLog("settings delete global alarm_manager_constants");
478     }
479 
setAppStandbyBucket(String bucket)480     private void setAppStandbyBucket(String bucket) throws IOException {
481         executeAndLog("am set-standby-bucket " + TEST_APP_PACKAGE + " " + bucket);
482     }
483 
setBatteryCharging(final boolean charging)484     private void setBatteryCharging(final boolean charging) throws Exception {
485         final BatteryManager bm = mContext.getSystemService(BatteryManager.class);
486         if (charging) {
487             executeAndLog("dumpsys battery reset");
488         } else {
489             executeAndLog("dumpsys battery unplug");
490             executeAndLog("dumpsys battery set status " +
491                     BatteryManager.BATTERY_STATUS_DISCHARGING);
492             assertTrue("Battery could not be unplugged", waitUntil(() -> !bm.isCharging(), 5_000));
493         }
494     }
495 
executeAndLog(String cmd)496     private String executeAndLog(String cmd) throws IOException {
497         final String output = mUiDevice.executeShellCommand(cmd).trim();
498         Log.d(TAG, "command: [" + cmd + "], output: [" + output + "]");
499         return output;
500     }
501 
waitForAlarm()502     private boolean waitForAlarm() throws InterruptedException {
503         final boolean success = waitUntil(() -> (mAlarmCount.get() == 1), DEFAULT_WAIT);
504         mAlarmCount.set(0);
505         return success;
506     }
507 
waitUntil(Condition condition, long timeout)508     private boolean waitUntil(Condition condition, long timeout) throws InterruptedException {
509         final long deadLine = SystemClock.uptimeMillis() + timeout;
510         while (!condition.isMet() && SystemClock.uptimeMillis() < deadLine) {
511             Thread.sleep(POLL_INTERVAL);
512         }
513         return condition.isMet();
514     }
515 
516     private static final class TestAlarmHistory {
517         private LongArray mHistory = new LongArray();
518 
addTime(long timestamp)519         private synchronized void addTime(long timestamp) {
520             mHistory.add(timestamp);
521         }
522 
523         /**
524          * Get the xth alarm time from the end.
525          */
getLast(int x)526         private synchronized long getLast(int x) {
527             if (x == 0 || x > mHistory.size()) {
528                 return 0;
529             }
530             return mHistory.get(mHistory.size() - x);
531         }
532     }
533 
534     @FunctionalInterface
535     interface Condition {
isMet()536         boolean isMet();
537     }
538 }
539