• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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("never");
254         final long expectedTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
255         scheduleAlarm(expectedTrigger, true, 0);
256         Thread.sleep(10_000);
257         assertFalse("Alarm received when app was in never bucket", waitForAlarm());
258     }
259 
260     @Test
testBucketUpgradeToSmallerDelay()261     public void testBucketUpgradeToSmallerDelay() throws Exception {
262         updateAlarmManagerConstants(false);
263         setAppStandbyBucket("active");
264         final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
265         scheduleAlarm(firstTrigger, false, 0);
266         Thread.sleep(MIN_FUTURITY);
267         assertTrue("Alarm did not fire when app in active", waitForAlarm());
268 
269         setAppStandbyBucket(APP_BUCKET_TAGS[FREQUENT_INDEX]);
270         final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
271         final long workingSetExpectedTrigger = sAlarmHistory.getLast(1)
272                 + APP_STANDBY_DELAYS[WORKING_INDEX];
273         scheduleAlarm(triggerTime, false, 0);
274         Thread.sleep(workingSetExpectedTrigger - SystemClock.elapsedRealtime());
275         assertFalse("The alarm went off before frequent delay", waitForAlarm());
276         setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
277         assertTrue("The alarm did not go off when app bucket upgraded to working_set",
278                 waitForAlarm());
279     }
280 
281     /**
282      * This is different to {@link #testBucketUpgradeToSmallerDelay()} in the sense that the bucket
283      * upgrade shifts eligibility to a point earlier than when the alarm is scheduled for.
284      * The alarm must then go off as soon as possible - at either the scheduled time or the bucket
285      * change, whichever happened later.
286      */
287     @Test
testBucketUpgradeToNoDelay()288     public void testBucketUpgradeToNoDelay() throws Exception {
289         updateAlarmManagerConstants(false);
290 
291         setAppStandbyBucket("active");
292         final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
293         scheduleAlarm(firstTrigger, false, 0);
294         Thread.sleep(MIN_FUTURITY);
295         assertTrue("Alarm did not fire when app in active", waitForAlarm());
296 
297         setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
298         final long triggerTime1 = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[FREQUENT_INDEX];
299         scheduleAlarm(triggerTime1, false, 0);
300         Thread.sleep(triggerTime1 - SystemClock.elapsedRealtime());
301         assertFalse("The alarm went off after frequent delay when app in rare bucket",
302                 waitForAlarm());
303         setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
304         assertTrue("The alarm did not go off when app bucket upgraded to working_set",
305                 waitForAlarm());
306 
307         // Once more
308         setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
309         final long triggerTime2 = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[FREQUENT_INDEX];
310         scheduleAlarm(triggerTime2, false, 0);
311         setAppStandbyBucket("active");
312         Thread.sleep(triggerTime2 - SystemClock.elapsedRealtime());
313         assertTrue("The alarm did not go off as scheduled when the app was in active",
314                 waitForAlarm());
315     }
316 
testSimpleQuotaDeferral(int bucketIndex)317     public void testSimpleQuotaDeferral(int bucketIndex) throws Exception {
318         setAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]);
319         final int quota = APP_STANDBY_QUOTAS[bucketIndex];
320 
321         long startElapsed = SystemClock.elapsedRealtime();
322         final long freshWindowPoint = sAlarmHistory.getLast(1) + APP_STANDBY_WINDOW + 1;
323         if (freshWindowPoint > startElapsed) {
324             Thread.sleep(freshWindowPoint - startElapsed);
325             startElapsed = freshWindowPoint;
326             // Now we should have no alarms in the past APP_STANDBY_WINDOW
327         }
328         final long desiredTrigger = startElapsed + APP_STANDBY_WINDOW;
329         final long firstTrigger = startElapsed + 4_000;
330         assertTrue("Quota too large for test",
331                 firstTrigger + ((quota - 1) * MIN_FUTURITY) < desiredTrigger);
332         for (int i = 0; i < quota; i++) {
333             final long trigger = firstTrigger + (i * MIN_FUTURITY);
334             scheduleAlarm(trigger, false, 0);
335             Thread.sleep(trigger - SystemClock.elapsedRealtime());
336             assertTrue("Alarm within quota not firing as expected", waitForAlarm());
337         }
338 
339         // Now quota is reached, any subsequent alarm should get deferred.
340         scheduleAlarm(desiredTrigger, false, 0);
341         Thread.sleep(desiredTrigger - SystemClock.elapsedRealtime());
342         assertFalse("Alarm exceeding quota not deferred", waitForAlarm());
343         final long minTrigger = firstTrigger + 1 + APP_STANDBY_WINDOW;
344         Thread.sleep(minTrigger - SystemClock.elapsedRealtime());
345         assertTrue("Alarm exceeding quota not delivered after expected delay", waitForAlarm());
346     }
347 
348     @Test
testActiveQuota()349     public void testActiveQuota() throws Exception {
350         setAppStandbyBucket("active");
351         long nextTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
352         for (int i = 0; i < 3; i++) {
353             scheduleAlarm(nextTrigger, false, 0);
354             Thread.sleep(MIN_FUTURITY);
355             assertTrue("Alarm not received as expected when app is in active", waitForAlarm());
356             nextTrigger += MIN_FUTURITY;
357         }
358     }
359 
360     @Test
testWorkingQuota()361     public void testWorkingQuota() throws Exception {
362         testSimpleQuotaDeferral(WORKING_INDEX);
363     }
364 
365     @Test
testFrequentQuota()366     public void testFrequentQuota() throws Exception {
367         testSimpleQuotaDeferral(FREQUENT_INDEX);
368     }
369 
370     @Test
testRareQuota()371     public void testRareQuota() throws Exception {
372         testSimpleQuotaDeferral(RARE_INDEX);
373     }
374 
375     @Test
testNeverQuota()376     public void testNeverQuota() throws Exception {
377         setAppStandbyBucket("never");
378         final long expectedTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
379         scheduleAlarm(expectedTrigger, true, 0);
380         Thread.sleep(10_000);
381         assertFalse("Alarm received when app was in never bucket", waitForAlarm());
382     }
383 
384     @Test
testAlarmClockUnaffected()385     public void testAlarmClockUnaffected() throws Exception {
386         setAppStandbyBucket("never");
387         final long trigger = System.currentTimeMillis() + MIN_FUTURITY;
388         scheduleAlarmClock(trigger);
389         Thread.sleep(MIN_FUTURITY);
390         assertTrue("Alarm clock not received as expected", waitForAlarm());
391     }
392 
393     @Test
testAllowWhileIdleAlarms()394     public void testAllowWhileIdleAlarms() throws Exception {
395         updateAlarmManagerConstants(false);
396         setAppStandbyBucket("active");
397         final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
398         scheduleAlarm(firstTrigger, true, 0);
399         Thread.sleep(MIN_FUTURITY);
400         assertTrue("first allow_while_idle alarm did not go off as scheduled", waitForAlarm());
401         scheduleAlarm(sAlarmHistory.getLast(1) + 7_000, true, 0);
402         // First check for the case where allow_while_idle delay should supersede app standby
403         setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
404         Thread.sleep(APP_STANDBY_DELAYS[WORKING_INDEX]);
405         assertFalse("allow_while_idle alarm went off before short time", waitForAlarm());
406         long expectedTriggerTime = sAlarmHistory.getLast(1) + ALLOW_WHILE_IDLE_SHORT_TIME;
407         Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
408         assertTrue("allow_while_idle alarm did not go off after short time", waitForAlarm());
409 
410         // Now the other case, app standby delay supersedes the allow_while_idle delay
411         scheduleAlarm(sAlarmHistory.getLast(1) + 7_000, true, 0);
412         setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
413         Thread.sleep(ALLOW_WHILE_IDLE_SHORT_TIME);
414         assertFalse("allow_while_idle alarm went off before " + APP_STANDBY_DELAYS[RARE_INDEX]
415                 + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
416         expectedTriggerTime = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[RARE_INDEX];
417         Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
418         assertTrue("allow_while_idle alarm did not go off even after "
419                 + APP_STANDBY_DELAYS[RARE_INDEX]
420                 + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
421     }
422 
423     @Test
testPowerWhitelistedAlarmNotBlocked()424     public void testPowerWhitelistedAlarmNotBlocked() throws Exception {
425         setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
426         setPowerWhitelisted(true);
427         final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
428         scheduleAlarm(triggerTime, false, 0);
429         Thread.sleep(MIN_FUTURITY);
430         assertTrue("Alarm did not go off for whitelisted app in rare bucket", waitForAlarm());
431         setPowerWhitelisted(false);
432     }
433 
434     @After
tearDown()435     public void tearDown() throws Exception {
436         setPowerWhitelisted(false);
437         setBatteryCharging(true);
438         deleteAlarmManagerConstants();
439         final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS);
440         cancelAlarmsIntent.setComponent(mAlarmScheduler);
441         mContext.sendBroadcast(cancelAlarmsIntent);
442         mContext.unregisterReceiver(mAlarmStateReceiver);
443         // Broadcast unregister may race with the next register in setUp
444         Thread.sleep(500);
445     }
446 
447     @AfterClass
tearDownTests()448     public static void tearDownTests() throws Exception {
449         if (!sOrigAppStandbyEnabled) {
450             AppStandbyUtils.setAppStandbyEnabledAtRuntime(sOrigAppStandbyEnabled);
451         }
452     }
453 
updateAlarmManagerConstants(boolean enableQuota)454     private void updateAlarmManagerConstants(boolean enableQuota) throws IOException {
455         final StringBuffer cmd = new StringBuffer("settings put global alarm_manager_constants ");
456         cmd.append(COMMON_SETTINGS);
457         if (!enableQuota) {
458             cmd.append(",app_standby_quotas_enabled=false");
459         }
460         executeAndLog(cmd.toString());
461     }
462 
setPowerWhitelisted(boolean whitelist)463     private void setPowerWhitelisted(boolean whitelist) throws IOException {
464         final StringBuffer cmd = new StringBuffer("cmd deviceidle whitelist ");
465         cmd.append(whitelist ? "+" : "-");
466         cmd.append(TEST_APP_PACKAGE);
467         executeAndLog(cmd.toString());
468     }
469 
deleteAlarmManagerConstants()470     private void deleteAlarmManagerConstants() throws IOException {
471         executeAndLog("settings delete global alarm_manager_constants");
472     }
473 
setAppStandbyBucket(String bucket)474     private void setAppStandbyBucket(String bucket) throws IOException {
475         executeAndLog("am set-standby-bucket " + TEST_APP_PACKAGE + " " + bucket);
476     }
477 
setBatteryCharging(final boolean charging)478     private void setBatteryCharging(final boolean charging) throws Exception {
479         final BatteryManager bm = mContext.getSystemService(BatteryManager.class);
480         if (charging) {
481             executeAndLog("dumpsys battery reset");
482         } else {
483             executeAndLog("dumpsys battery unplug");
484             executeAndLog("dumpsys battery set status " +
485                     BatteryManager.BATTERY_STATUS_DISCHARGING);
486             assertTrue("Battery could not be unplugged", waitUntil(() -> !bm.isCharging(), 5_000));
487         }
488     }
489 
executeAndLog(String cmd)490     private String executeAndLog(String cmd) throws IOException {
491         final String output = mUiDevice.executeShellCommand(cmd).trim();
492         Log.d(TAG, "command: [" + cmd + "], output: [" + output + "]");
493         return output;
494     }
495 
waitForAlarm()496     private boolean waitForAlarm() throws InterruptedException {
497         final boolean success = waitUntil(() -> (mAlarmCount.get() == 1), DEFAULT_WAIT);
498         mAlarmCount.set(0);
499         return success;
500     }
501 
waitUntil(Condition condition, long timeout)502     private boolean waitUntil(Condition condition, long timeout) throws InterruptedException {
503         final long deadLine = SystemClock.uptimeMillis() + timeout;
504         while (!condition.isMet() && SystemClock.uptimeMillis() < deadLine) {
505             Thread.sleep(POLL_INTERVAL);
506         }
507         return condition.isMet();
508     }
509 
510     private static final class TestAlarmHistory {
511         private LongArray mHistory = new LongArray();
512 
addTime(long timestamp)513         private synchronized void addTime(long timestamp) {
514             mHistory.add(timestamp);
515         }
516 
517         /**
518          * Get the xth alarm time from the end.
519          */
getLast(int x)520         private synchronized long getLast(int x) {
521             if (x == 0 || x > mHistory.size()) {
522                 return 0;
523             }
524             return mHistory.get(mHistory.size() - x);
525         }
526     }
527 
528     @FunctionalInterface
529     interface Condition {
isMet()530         boolean isMet();
531     }
532 }
533