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.content.BroadcastReceiver; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.os.BatteryManager; 33 import android.os.SystemClock; 34 import android.support.test.InstrumentationRegistry; 35 import android.support.test.filters.LargeTest; 36 import android.support.test.runner.AndroidJUnit4; 37 import android.support.test.uiautomator.UiDevice; 38 import android.util.Log; 39 40 import com.android.compatibility.common.util.AppStandbyUtils; 41 42 import org.junit.After; 43 import org.junit.AfterClass; 44 import org.junit.Before; 45 import org.junit.BeforeClass; 46 import org.junit.Test; 47 import org.junit.runner.RunWith; 48 49 import java.io.IOException; 50 import java.util.concurrent.atomic.AtomicInteger; 51 52 /** 53 * Tests that app standby imposes the appropriate restrictions on alarms 54 */ 55 @LargeTest 56 @RunWith(AndroidJUnit4.class) 57 public class AppStandbyTests { 58 private static final String TAG = AppStandbyTests.class.getSimpleName(); 59 private static final String TEST_APP_PACKAGE = "android.alarmmanager.alarmtestapp.cts"; 60 private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestAlarmScheduler"; 61 62 private static final long DEFAULT_WAIT = 4_000; 63 private static final long POLL_INTERVAL = 200; 64 65 // Tweaked alarm manager constants to facilitate testing 66 private static final long ALLOW_WHILE_IDLE_SHORT_TIME = 15_000; 67 private static final long MIN_FUTURITY = 2_000; 68 private static final long[] APP_STANDBY_DELAYS = {0, 10_000, 20_000, 30_000, 600_000}; 69 private static final String[] APP_BUCKET_TAGS = { 70 "active", 71 "working_set", 72 "frequent", 73 "rare", 74 "never" 75 }; 76 private static final String[] APP_BUCKET_KEYS = { 77 "standby_active_delay", 78 "standby_working_delay", 79 "standby_frequent_delay", 80 "standby_rare_delay", 81 "standby_never_delay", 82 }; 83 84 // Save the state before running tests to restore it after we finish testing. 85 private static boolean sOrigAppStandbyEnabled; 86 87 private Context mContext; 88 private ComponentName mAlarmScheduler; 89 private UiDevice mUiDevice; 90 private AtomicInteger mAlarmCount; 91 private volatile long mLastAlarmTime; 92 93 private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() { 94 @Override 95 public void onReceive(Context context, Intent intent) { 96 mAlarmCount.getAndAdd(intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1)); 97 mLastAlarmTime = SystemClock.elapsedRealtime(); 98 Log.d(TAG, "No. of expirations: " + mAlarmCount 99 + " elapsed: " + SystemClock.elapsedRealtime()); 100 } 101 }; 102 103 @BeforeClass 104 public static void setUpTests() throws Exception { 105 sOrigAppStandbyEnabled = AppStandbyUtils.isAppStandbyEnabledAtRuntime(); 106 if (!sOrigAppStandbyEnabled) { 107 AppStandbyUtils.setAppStandbyEnabledAtRuntime(true); 108 109 // Give system sometime to initialize itself. 110 Thread.sleep(100); 111 } 112 } 113 114 @Before 115 public void setUp() throws Exception { 116 mContext = InstrumentationRegistry.getTargetContext(); 117 mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 118 mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER); 119 mAlarmCount = new AtomicInteger(0); 120 updateAlarmManagerConstants(); 121 setBatteryCharging(false); 122 final IntentFilter intentFilter = new IntentFilter(); 123 intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED); 124 mContext.registerReceiver(mAlarmStateReceiver, intentFilter); 125 assumeTrue("App Standby not enabled on device", AppStandbyUtils.isAppStandbyEnabled()); 126 setAppStandbyBucket("active"); 127 scheduleAlarm(SystemClock.elapsedRealtime(), false, 0); 128 Thread.sleep(MIN_FUTURITY); 129 assertTrue("Alarm not sent when app in active", waitForAlarms(1)); 130 } 131 132 private void scheduleAlarm(long triggerMillis, boolean allowWhileIdle, long interval) { 133 final Intent setAlarmIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM); 134 setAlarmIntent.setComponent(mAlarmScheduler); 135 setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TYPE, ELAPSED_REALTIME_WAKEUP); 136 setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TRIGGER_TIME, triggerMillis); 137 setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_REPEAT_INTERVAL, interval); 138 setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_ALLOW_WHILE_IDLE, allowWhileIdle); 139 setAlarmIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 140 mContext.sendBroadcast(setAlarmIntent); 141 } 142 143 private void testBucketDelay(int bucketIndex) throws Exception { 144 setAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]); 145 final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY; 146 final long minTriggerTime = mLastAlarmTime + APP_STANDBY_DELAYS[bucketIndex]; 147 scheduleAlarm(triggerTime, false, 0); 148 Thread.sleep(MIN_FUTURITY); 149 if (triggerTime + DEFAULT_WAIT < minTriggerTime) { 150 assertFalse("Alarm went off before " + APP_BUCKET_TAGS[bucketIndex] + " delay", 151 waitForAlarms(1)); 152 Thread.sleep(minTriggerTime - SystemClock.elapsedRealtime()); 153 } 154 assertTrue("Deferred alarm did not go off after " + APP_BUCKET_TAGS[bucketIndex] + " delay", 155 waitForAlarms(1)); 156 } 157 158 @Test 159 public void testWorkingSetDelay() throws Exception { 160 testBucketDelay(1); 161 } 162 163 @Test 164 public void testFrequentDelay() throws Exception { 165 testBucketDelay(2); 166 } 167 168 @Test 169 public void testRareDelay() throws Exception { 170 testBucketDelay(3); 171 } 172 173 @Test 174 public void testBucketUpgradeToSmallerDelay() throws Exception { 175 setAppStandbyBucket(APP_BUCKET_TAGS[2]); 176 final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY; 177 final long workingSetExpectedTrigger = mLastAlarmTime + APP_STANDBY_DELAYS[1]; 178 scheduleAlarm(triggerTime, false, 0); 179 Thread.sleep(workingSetExpectedTrigger - SystemClock.elapsedRealtime()); 180 assertFalse("The alarm went off before frequent delay", waitForAlarms(1)); 181 setAppStandbyBucket(APP_BUCKET_TAGS[1]); 182 assertTrue("The alarm did not go off when app bucket upgraded to working_set", 183 waitForAlarms(1)); 184 } 185 186 187 /** 188 * This is different to {@link #testBucketUpgradeToSmallerDelay()} in the sense that the bucket 189 * upgrade shifts eligibility to a point earlier than when the alarm is scheduled for. 190 * The alarm must then go off as soon as possible - at either the scheduled time or the bucket 191 * change, whichever happened later. 192 */ 193 @Test 194 public void testBucketUpgradeToNoDelay() throws Exception { 195 setAppStandbyBucket(APP_BUCKET_TAGS[3]); 196 final long triggerTime1 = mLastAlarmTime + APP_STANDBY_DELAYS[2]; 197 scheduleAlarm(triggerTime1, false, 0); 198 Thread.sleep(triggerTime1 - SystemClock.elapsedRealtime()); 199 assertFalse("The alarm went off after frequent delay when app in rare bucket", 200 waitForAlarms(1)); 201 setAppStandbyBucket(APP_BUCKET_TAGS[1]); 202 assertTrue("The alarm did not go off when app bucket upgraded to working_set", 203 waitForAlarms(1)); 204 205 // Once more 206 setAppStandbyBucket(APP_BUCKET_TAGS[3]); 207 final long triggerTime2 = mLastAlarmTime + APP_STANDBY_DELAYS[2]; 208 scheduleAlarm(triggerTime2, false, 0); 209 setAppStandbyBucket(APP_BUCKET_TAGS[0]); 210 Thread.sleep(triggerTime2 - SystemClock.elapsedRealtime()); 211 assertTrue("The alarm did not go off as scheduled when the app was in active", 212 waitForAlarms(1)); 213 } 214 215 @Test 216 public void testAllowWhileIdleAlarms() throws Exception { 217 final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY; 218 scheduleAlarm(firstTrigger, true, 0); 219 Thread.sleep(MIN_FUTURITY); 220 assertTrue("first allow_while_idle alarm did not go off as scheduled", waitForAlarms(1)); 221 scheduleAlarm(mLastAlarmTime + 9_000, true, 0); 222 // First check for the case where allow_while_idle delay should supersede app standby 223 setAppStandbyBucket(APP_BUCKET_TAGS[1]); 224 Thread.sleep(APP_STANDBY_DELAYS[1]); 225 assertFalse("allow_while_idle alarm went off before short time", waitForAlarms(1)); 226 long expectedTriggerTime = mLastAlarmTime + ALLOW_WHILE_IDLE_SHORT_TIME; 227 Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime()); 228 assertTrue("allow_while_idle alarm did not go off after short time", waitForAlarms(1)); 229 230 // Now the other case, app standby delay supersedes the allow_while_idle delay 231 scheduleAlarm(mLastAlarmTime + 12_000, true, 0); 232 setAppStandbyBucket(APP_BUCKET_TAGS[2]); 233 Thread.sleep(ALLOW_WHILE_IDLE_SHORT_TIME); 234 assertFalse("allow_while_idle alarm went off before " + APP_STANDBY_DELAYS[2] 235 + "ms, when in bucket " + APP_BUCKET_TAGS[2], waitForAlarms(1)); 236 expectedTriggerTime = mLastAlarmTime + APP_STANDBY_DELAYS[2]; 237 Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime()); 238 assertTrue("allow_while_idle alarm did not go off even after " + APP_STANDBY_DELAYS[2] 239 + "ms, when in bucket " + APP_BUCKET_TAGS[2], waitForAlarms(1)); 240 } 241 242 @Test 243 public void testPowerWhitelistedAlarmNotBlocked() throws Exception { 244 setAppStandbyBucket(APP_BUCKET_TAGS[3]); 245 setPowerWhitelisted(true); 246 final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY; 247 scheduleAlarm(triggerTime, false, 0); 248 Thread.sleep(MIN_FUTURITY); 249 assertTrue("Alarm did not go off for whitelisted app in rare bucket", waitForAlarms(1)); 250 setPowerWhitelisted(false); 251 } 252 253 @After 254 public void tearDown() throws Exception { 255 setPowerWhitelisted(false); 256 setBatteryCharging(true); 257 deleteAlarmManagerConstants(); 258 final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS); 259 cancelAlarmsIntent.setComponent(mAlarmScheduler); 260 mContext.sendBroadcast(cancelAlarmsIntent); 261 mContext.unregisterReceiver(mAlarmStateReceiver); 262 // Broadcast unregister may race with the next register in setUp 263 Thread.sleep(500); 264 } 265 266 @AfterClass 267 public static void tearDownTests() throws Exception { 268 if (!sOrigAppStandbyEnabled) { 269 AppStandbyUtils.setAppStandbyEnabledAtRuntime(sOrigAppStandbyEnabled); 270 } 271 } 272 273 private void updateAlarmManagerConstants() throws IOException { 274 final StringBuffer cmd = new StringBuffer("settings put global alarm_manager_constants "); 275 cmd.append("min_futurity="); cmd.append(MIN_FUTURITY); 276 cmd.append(",allow_while_idle_short_time="); cmd.append(ALLOW_WHILE_IDLE_SHORT_TIME); 277 for (int i = 0; i < APP_STANDBY_DELAYS.length; i++) { 278 cmd.append(","); 279 cmd.append(APP_BUCKET_KEYS[i]); cmd.append("="); cmd.append(APP_STANDBY_DELAYS[i]); 280 } 281 executeAndLog(cmd.toString()); 282 } 283 284 private void setPowerWhitelisted(boolean whitelist) throws IOException { 285 final StringBuffer cmd = new StringBuffer("cmd deviceidle whitelist "); 286 cmd.append(whitelist ? "+" : "-"); 287 cmd.append(TEST_APP_PACKAGE); 288 executeAndLog(cmd.toString()); 289 } 290 291 private void deleteAlarmManagerConstants() throws IOException { 292 executeAndLog("settings delete global alarm_manager_constants"); 293 } 294 295 private void setAppStandbyBucket(String bucket) throws IOException { 296 executeAndLog("am set-standby-bucket " + TEST_APP_PACKAGE + " " + bucket); 297 } 298 299 private void setBatteryCharging(final boolean charging) throws Exception { 300 final BatteryManager bm = mContext.getSystemService(BatteryManager.class); 301 final String cmd = "dumpsys battery " + (charging ? "reset" : "unplug"); 302 executeAndLog(cmd); 303 if (!charging) { 304 assertTrue("Battery could not be unplugged", waitUntil(() -> !bm.isCharging(), 5_000)); 305 } 306 } 307 308 private String executeAndLog(String cmd) throws IOException { 309 final String output = mUiDevice.executeShellCommand(cmd).trim(); 310 Log.d(TAG, "command: [" + cmd + "], output: [" + output + "]"); 311 return output; 312 } 313 314 private boolean waitForAlarms(final int numAlarms) throws InterruptedException { 315 final boolean success = waitUntil(() -> (mAlarmCount.get() == numAlarms), DEFAULT_WAIT); 316 mAlarmCount.set(0); 317 return success; 318 } 319 320 private boolean waitUntil(Condition condition, long timeout) throws InterruptedException { 321 final long deadLine = SystemClock.uptimeMillis() + timeout; 322 while (!condition.isMet() && SystemClock.uptimeMillis() < deadLine) { 323 Thread.sleep(POLL_INTERVAL); 324 } 325 return condition.isMet(); 326 } 327 328 @FunctionalInterface 329 interface Condition { 330 boolean isMet(); 331 } 332 } 333