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.alarmmanager.util.AlarmManagerDeviceConfigHelper; 28 import android.app.Activity; 29 import android.content.BroadcastReceiver; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.os.BatteryManager; 35 import android.os.SystemClock; 36 import android.platform.test.annotations.AppModeFull; 37 import android.support.test.uiautomator.UiDevice; 38 import android.util.Log; 39 import android.util.LongArray; 40 41 import androidx.test.InstrumentationRegistry; 42 import androidx.test.filters.LargeTest; 43 import androidx.test.runner.AndroidJUnit4; 44 45 import com.android.compatibility.common.util.AppStandbyUtils; 46 47 import org.junit.After; 48 import org.junit.AfterClass; 49 import org.junit.Before; 50 import org.junit.BeforeClass; 51 import org.junit.Test; 52 import org.junit.runner.RunWith; 53 54 import java.io.IOException; 55 import java.util.concurrent.CountDownLatch; 56 import java.util.concurrent.TimeUnit; 57 import java.util.concurrent.atomic.AtomicInteger; 58 import java.util.function.BooleanSupplier; 59 60 /** 61 * Tests that app standby imposes the appropriate restrictions on alarms 62 */ 63 @AppModeFull 64 @LargeTest 65 @RunWith(AndroidJUnit4.class) 66 public class AppStandbyTests { 67 private static final String TAG = AppStandbyTests.class.getSimpleName(); 68 static final String TEST_APP_PACKAGE = "android.alarmmanager.alarmtestapp.cts"; 69 private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestAlarmScheduler"; 70 71 private static final long DEFAULT_WAIT = 2_000; 72 private static final long POLL_INTERVAL = 200; 73 74 // Tweaked alarm manager constants to facilitate testing 75 private static final long MIN_FUTURITY = 1_000; 76 77 // Not touching ACTIVE and RARE parameters for this test 78 private static final int WORKING_INDEX = 0; 79 private static final int FREQUENT_INDEX = 1; 80 private static final int RARE_INDEX = 2; 81 private static final String[] APP_BUCKET_TAGS = { 82 "working_set", 83 "frequent", 84 "rare", 85 }; 86 87 private static final long APP_STANDBY_WINDOW = 10_000; 88 private static final long MIN_WINDOW = 100; 89 private static final String[] APP_BUCKET_QUOTA_KEYS = { 90 "standby_quota_working", 91 "standby_quota_frequent", 92 "standby_quota_rare", 93 }; 94 private static final int[] APP_STANDBY_QUOTAS = { 95 5, // Working set 96 3, // Frequent 97 1, // Rare 98 }; 99 100 // Save the state before running tests to restore it after we finish testing. 101 private static boolean sOrigAppStandbyEnabled; 102 // Test app's alarm history to help predict when a subsequent alarm is going to get deferred. 103 private static TestAlarmHistory sAlarmHistory; 104 private static Context sContext = InstrumentationRegistry.getTargetContext(); 105 private static UiDevice sUiDevice = UiDevice.getInstance( 106 InstrumentationRegistry.getInstrumentation()); 107 108 private ComponentName mAlarmScheduler; 109 private AtomicInteger mAlarmCount; 110 private AlarmManagerDeviceConfigHelper mConfigHelper = new AlarmManagerDeviceConfigHelper(); 111 112 private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() { 113 @Override 114 public void onReceive(Context context, Intent intent) { 115 mAlarmCount.getAndAdd(intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1)); 116 final long nowElapsed = SystemClock.elapsedRealtime(); 117 sAlarmHistory.addTime(nowElapsed); 118 Log.d(TAG, "No. of expirations: " + mAlarmCount + " elapsed: " + nowElapsed); 119 } 120 }; 121 122 @BeforeClass setUpTests()123 public static void setUpTests() throws Exception { 124 sAlarmHistory = new TestAlarmHistory(); 125 sOrigAppStandbyEnabled = AppStandbyUtils.isAppStandbyEnabledAtRuntime(); 126 if (!sOrigAppStandbyEnabled) { 127 AppStandbyUtils.setAppStandbyEnabledAtRuntime(true); 128 129 // Give system sometime to initialize itself. 130 Thread.sleep(100); 131 } 132 } 133 134 @Before setUp()135 public void setUp() throws Exception { 136 mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER); 137 mAlarmCount = new AtomicInteger(0); 138 updateAlarmManagerConstants(); 139 setBatteryCharging(false); 140 final IntentFilter intentFilter = new IntentFilter(); 141 intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED); 142 sContext.registerReceiver(mAlarmStateReceiver, intentFilter); 143 assumeTrue("App Standby not enabled on device", AppStandbyUtils.isAppStandbyEnabled()); 144 } 145 scheduleAlarm(long triggerMillis, long interval)146 private void scheduleAlarm(long triggerMillis, long interval) throws InterruptedException { 147 final Intent setAlarmIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM); 148 setAlarmIntent.setComponent(mAlarmScheduler); 149 setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TYPE, ELAPSED_REALTIME_WAKEUP); 150 setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TRIGGER_TIME, triggerMillis); 151 setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_WINDOW_LENGTH, MIN_WINDOW); 152 setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_REPEAT_INTERVAL, interval); 153 setAlarmIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 154 final CountDownLatch resultLatch = new CountDownLatch(1); 155 sContext.sendOrderedBroadcast(setAlarmIntent, null, new BroadcastReceiver() { 156 @Override 157 public void onReceive(Context context, Intent intent) { 158 resultLatch.countDown(); 159 } 160 }, null, Activity.RESULT_CANCELED, null, null); 161 assertTrue("Request did not complete", resultLatch.await(10, TimeUnit.SECONDS)); 162 } 163 testSimpleQuotaDeferral(int bucketIndex)164 public void testSimpleQuotaDeferral(int bucketIndex) throws Exception { 165 setTestAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]); 166 final int quota = APP_STANDBY_QUOTAS[bucketIndex]; 167 168 long startElapsed = SystemClock.elapsedRealtime(); 169 final long freshWindowPoint = sAlarmHistory.getLast(1) + APP_STANDBY_WINDOW; 170 if (freshWindowPoint > startElapsed) { 171 Thread.sleep(freshWindowPoint - startElapsed); 172 startElapsed = freshWindowPoint; 173 // Now we should have no alarms in the past APP_STANDBY_WINDOW 174 } 175 final long desiredTrigger = startElapsed + APP_STANDBY_WINDOW; 176 final long firstTrigger = startElapsed + 4_000; 177 assertTrue("Quota too large for test", 178 firstTrigger + ((quota - 1) * MIN_FUTURITY) < desiredTrigger); 179 for (int i = 0; i < quota; i++) { 180 final long trigger = firstTrigger + (i * MIN_FUTURITY); 181 scheduleAlarm(trigger, 0); 182 Thread.sleep(trigger - SystemClock.elapsedRealtime()); 183 assertTrue("Alarm within quota not firing as expected", waitForAlarm()); 184 } 185 186 // Now quota is reached, any subsequent alarm should get deferred. 187 scheduleAlarm(desiredTrigger, 0); 188 Thread.sleep(desiredTrigger - SystemClock.elapsedRealtime()); 189 assertFalse("Alarm exceeding quota not deferred", waitForAlarm()); 190 final long minTrigger = firstTrigger + APP_STANDBY_WINDOW; 191 Thread.sleep(minTrigger - SystemClock.elapsedRealtime()); 192 assertTrue("Alarm exceeding quota not delivered after expected delay", waitForAlarm()); 193 } 194 195 @Test testActiveQuota()196 public void testActiveQuota() throws Exception { 197 setTestAppStandbyBucket("active"); 198 long nextTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY; 199 for (int i = 0; i < 3; i++) { 200 scheduleAlarm(nextTrigger, 0); 201 Thread.sleep(MIN_FUTURITY); 202 assertTrue("Alarm not received as expected when app is in active", waitForAlarm()); 203 nextTrigger += MIN_FUTURITY; 204 } 205 } 206 207 @Test testWorkingQuota()208 public void testWorkingQuota() throws Exception { 209 testSimpleQuotaDeferral(WORKING_INDEX); 210 } 211 212 @Test testFrequentQuota()213 public void testFrequentQuota() throws Exception { 214 testSimpleQuotaDeferral(FREQUENT_INDEX); 215 } 216 217 @Test testRareQuota()218 public void testRareQuota() throws Exception { 219 testSimpleQuotaDeferral(RARE_INDEX); 220 } 221 222 @Test testNeverQuota()223 public void testNeverQuota() throws Exception { 224 setTestAppStandbyBucket("never"); 225 final long expectedTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY; 226 scheduleAlarm(expectedTrigger, 0); 227 Thread.sleep(10_000); 228 assertFalse("Alarm received when app was in never bucket", waitForAlarm()); 229 } 230 231 @Test testPowerWhitelistedAlarmNotBlocked()232 public void testPowerWhitelistedAlarmNotBlocked() throws Exception { 233 setTestAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]); 234 setPowerWhitelisted(true); 235 final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY; 236 scheduleAlarm(triggerTime, 0); 237 Thread.sleep(MIN_FUTURITY); 238 assertTrue("Alarm did not go off for whitelisted app in rare bucket", waitForAlarm()); 239 setPowerWhitelisted(false); 240 } 241 242 @After tearDown()243 public void tearDown() throws Exception { 244 setPowerWhitelisted(false); 245 setBatteryCharging(true); 246 mConfigHelper.restoreAll(); 247 final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS); 248 cancelAlarmsIntent.setComponent(mAlarmScheduler); 249 sContext.sendBroadcast(cancelAlarmsIntent); 250 sContext.unregisterReceiver(mAlarmStateReceiver); 251 // Broadcast unregister may race with the next register in setUp 252 Thread.sleep(500); 253 } 254 255 @AfterClass tearDownTests()256 public static void tearDownTests() throws Exception { 257 if (!sOrigAppStandbyEnabled) { 258 AppStandbyUtils.setAppStandbyEnabledAtRuntime(sOrigAppStandbyEnabled); 259 } 260 } 261 updateAlarmManagerConstants()262 private void updateAlarmManagerConstants() { 263 mConfigHelper.with("min_futurity", MIN_FUTURITY) 264 .with("app_standby_window", APP_STANDBY_WINDOW) 265 .with("min_window", MIN_WINDOW) 266 .with("exact_alarm_deny_list", TEST_APP_PACKAGE); 267 for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) { 268 mConfigHelper.with(APP_BUCKET_QUOTA_KEYS[i], APP_STANDBY_QUOTAS[i]); 269 } 270 mConfigHelper.commitAndAwaitPropagation(); 271 } 272 setPowerWhitelisted(boolean whitelist)273 private void setPowerWhitelisted(boolean whitelist) throws IOException { 274 final StringBuffer cmd = new StringBuffer("cmd deviceidle whitelist "); 275 cmd.append(whitelist ? "+" : "-"); 276 cmd.append(TEST_APP_PACKAGE); 277 executeAndLog(cmd.toString()); 278 } 279 setTestAppStandbyBucket(String bucket)280 static void setTestAppStandbyBucket(String bucket) throws IOException { 281 executeAndLog("am set-standby-bucket " + TEST_APP_PACKAGE + " " + bucket); 282 } 283 setBatteryCharging(final boolean charging)284 private void setBatteryCharging(final boolean charging) throws Exception { 285 final BatteryManager bm = sContext.getSystemService(BatteryManager.class); 286 if (charging) { 287 executeAndLog("dumpsys battery reset"); 288 } else { 289 executeAndLog("dumpsys battery unplug"); 290 executeAndLog("dumpsys battery set status " + 291 BatteryManager.BATTERY_STATUS_DISCHARGING); 292 assertTrue("Battery could not be unplugged", waitUntil(() -> !bm.isCharging(), 5_000)); 293 } 294 } 295 executeAndLog(String cmd)296 private static String executeAndLog(String cmd) throws IOException { 297 final String output = sUiDevice.executeShellCommand(cmd).trim(); 298 Log.d(TAG, "command: [" + cmd + "], output: [" + output + "]"); 299 return output; 300 } 301 waitForAlarm()302 private boolean waitForAlarm() throws InterruptedException { 303 final boolean success = waitUntil(() -> (mAlarmCount.get() == 1), DEFAULT_WAIT); 304 mAlarmCount.set(0); 305 return success; 306 } 307 waitUntil(BooleanSupplier condition, long timeout)308 private boolean waitUntil(BooleanSupplier condition, long timeout) throws InterruptedException { 309 final long deadLine = SystemClock.uptimeMillis() + timeout; 310 while (!condition.getAsBoolean() && SystemClock.uptimeMillis() < deadLine) { 311 Thread.sleep(POLL_INTERVAL); 312 } 313 return condition.getAsBoolean(); 314 } 315 316 private static final class TestAlarmHistory { 317 private LongArray mHistory = new LongArray(); 318 addTime(long timestamp)319 private synchronized void addTime(long timestamp) { 320 mHistory.add(timestamp); 321 } 322 323 /** 324 * Get the xth alarm time from the end. 325 */ getLast(int x)326 private synchronized long getLast(int x) { 327 if (x == 0 || x > mHistory.size()) { 328 return 0; 329 } 330 return mHistory.get(mHistory.size() - x); 331 } 332 } 333 } 334