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