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