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