1 /* 2 * Copyright (C) 2021 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 com.android.server.utils; 18 19 import static android.text.format.DateUtils.HOUR_IN_MILLIS; 20 import static android.text.format.DateUtils.MINUTE_IN_MILLIS; 21 22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; 23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 24 25 import static org.junit.Assert.assertEquals; 26 import static org.junit.Assert.fail; 27 import static org.mockito.ArgumentMatchers.any; 28 import static org.mockito.ArgumentMatchers.anyInt; 29 import static org.mockito.ArgumentMatchers.anyLong; 30 import static org.mockito.ArgumentMatchers.eq; 31 import static org.mockito.Mockito.never; 32 import static org.mockito.Mockito.timeout; 33 import static org.mockito.Mockito.verify; 34 import static org.mockito.Mockito.when; 35 36 import android.app.AlarmManager; 37 import android.content.Context; 38 import android.os.Handler; 39 import android.os.Looper; 40 import android.os.SystemClock; 41 import android.util.ArraySet; 42 43 import androidx.annotation.NonNull; 44 import androidx.test.runner.AndroidJUnit4; 45 46 import com.android.server.LocalServices; 47 48 import org.junit.After; 49 import org.junit.Before; 50 import org.junit.Test; 51 import org.junit.runner.RunWith; 52 import org.mockito.InOrder; 53 import org.mockito.Mock; 54 import org.mockito.MockitoSession; 55 import org.mockito.quality.Strictness; 56 57 /** 58 * Tests for {@link AlarmQueue}. 59 */ 60 @RunWith(AndroidJUnit4.class) 61 public class AlarmQueueTest { 62 private static final String ALARM_TAG = "*test*"; 63 64 private final InjectorForTest mInjector = new InjectorForTest(); 65 private ArraySet<String> mExpiredPackages; 66 private MockitoSession mMockingSession; 67 @Mock 68 private AlarmManager mAlarmManager; 69 @Mock 70 private Context mContext; 71 72 private static class InjectorForTest extends AlarmQueue.Injector { 73 private long mElapsedTime = SystemClock.elapsedRealtime(); 74 75 @Override getElapsedRealtime()76 long getElapsedRealtime() { 77 return mElapsedTime; 78 } 79 } 80 81 @Before setUp()82 public void setUp() { 83 mMockingSession = mockitoSession() 84 .initMocks(this) 85 .strictness(Strictness.LENIENT) 86 .mockStatic(LocalServices.class) 87 .startMocking(); 88 89 when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); 90 when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager); 91 92 // Freeze the clocks at 24 hours after this moment in time. 93 advanceElapsedClock(24 * HOUR_IN_MILLIS); 94 } 95 96 @After tearDown()97 public void tearDown() { 98 if (mMockingSession != null) { 99 mMockingSession.finishMocking(); 100 } 101 } 102 advanceElapsedClock(long incrementMs)103 private void advanceElapsedClock(long incrementMs) { 104 mInjector.mElapsedTime += incrementMs; 105 } 106 107 @NonNull createAlarmQueue(boolean exactAlarm, long minTimeBetweenAlarmsMs)108 private AlarmQueue<String> createAlarmQueue(boolean exactAlarm, long minTimeBetweenAlarmsMs) { 109 return new AlarmQueue<String>(mContext, mContext.getMainLooper(), ALARM_TAG, "Test", 110 exactAlarm, minTimeBetweenAlarmsMs, mInjector) { 111 @Override 112 protected boolean isForUser(String key, int userId) { 113 return true; 114 } 115 116 @Override 117 protected void processExpiredAlarms(@NonNull ArraySet<String> expired) { 118 mExpiredPackages = expired; 119 } 120 }; 121 } 122 123 @Test 124 public void testAddingIncreasingAlarms() { 125 final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0); 126 final long nowElapsed = mInjector.getElapsedRealtime(); 127 128 InOrder inOrder = inOrder(mAlarmManager); 129 130 alarmQueue.addAlarm("com.android.test.1", nowElapsed + HOUR_IN_MILLIS); 131 inOrder.verify(mAlarmManager, timeout(1000).times(1)) 132 .setExact(anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any()); 133 alarmQueue.addAlarm("com.android.test.2", nowElapsed + 2 * HOUR_IN_MILLIS); 134 inOrder.verify(mAlarmManager, never()) 135 .setExact(anyInt(), anyLong(), eq(ALARM_TAG), any(), any()); 136 alarmQueue.addAlarm("com.android.test.3", nowElapsed + 3 * HOUR_IN_MILLIS); 137 inOrder.verify(mAlarmManager, never()) 138 .setExact(anyInt(), anyLong(), eq(ALARM_TAG), any(), any()); 139 } 140 141 @Test 142 public void testAddingDecreasingAlarms() { 143 final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0); 144 final long nowElapsed = mInjector.getElapsedRealtime(); 145 146 InOrder inOrder = inOrder(mAlarmManager); 147 148 alarmQueue.addAlarm("com.android.test.3", nowElapsed + 3 * HOUR_IN_MILLIS); 149 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 150 anyInt(), eq(nowElapsed + 3 * HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any()); 151 alarmQueue.addAlarm("com.android.test.2", nowElapsed + 2 * HOUR_IN_MILLIS); 152 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 153 anyInt(), eq(nowElapsed + 2 * HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any()); 154 alarmQueue.addAlarm("com.android.test.1", nowElapsed + HOUR_IN_MILLIS); 155 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 156 anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any()); 157 } 158 159 @Test 160 public void testAddingLargeAlarmTimes() { 161 final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0); 162 final long nowElapsed = mInjector.getElapsedRealtime(); 163 164 InOrder inOrder = inOrder(mAlarmManager); 165 166 alarmQueue.addAlarm("com.android.test.1", Long.MAX_VALUE - 5); 167 inOrder.verify(mAlarmManager, timeout(1000).times(1)) 168 .setExact(anyInt(), eq(Long.MAX_VALUE - 5), eq(ALARM_TAG), any(), any()); 169 alarmQueue.addAlarm("com.android.test.2", Long.MAX_VALUE - 4); 170 inOrder.verify(mAlarmManager, never()) 171 .setExact(anyInt(), anyLong(), eq(ALARM_TAG), any(), any()); 172 alarmQueue.addAlarm("com.android.test.3", nowElapsed + 5); 173 inOrder.verify(mAlarmManager, timeout(1000).times(1)) 174 .setExact(anyInt(), eq(nowElapsed + 5), eq(ALARM_TAG), any(), any()); 175 alarmQueue.addAlarm("com.android.test.4", nowElapsed + 6); 176 inOrder.verify(mAlarmManager, never()) 177 .setExact(anyInt(), anyLong(), eq(ALARM_TAG), any(), any()); 178 } 179 180 /** 181 * Verify that updating the alarm time for a key will result in the AlarmManager alarm changing, 182 * if needed. 183 */ 184 @Test 185 public void testChangingKeyAlarm() { 186 final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0); 187 final long nowElapsed = mInjector.getElapsedRealtime(); 188 189 InOrder inOrder = inOrder(mAlarmManager); 190 191 alarmQueue.addAlarm("1", nowElapsed + 5 * MINUTE_IN_MILLIS); 192 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 193 anyInt(), eq(nowElapsed + 5 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any()); 194 195 // Only alarm, but the time has changed, so we should reschedule what's set with AM. 196 alarmQueue.addAlarm("1", nowElapsed + 20 * MINUTE_IN_MILLIS); 197 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 198 anyInt(), eq(nowElapsed + 20 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any()); 199 200 alarmQueue.addAlarm("1", nowElapsed + 10 * MINUTE_IN_MILLIS); 201 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 202 anyInt(), eq(nowElapsed + 10 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any()); 203 204 // Add another keyed alarm and check that we don't bother rescheduling when the changed 205 // alarm is after the first alarm to go off. 206 alarmQueue.addAlarm("2", nowElapsed + 11 * MINUTE_IN_MILLIS); 207 inOrder.verify(mAlarmManager, never()).setExact( 208 anyInt(), anyLong(), eq(ALARM_TAG), any(), any()); 209 210 alarmQueue.addAlarm("2", nowElapsed + 51 * MINUTE_IN_MILLIS); 211 inOrder.verify(mAlarmManager, never()).setExact( 212 anyInt(), anyLong(), eq(ALARM_TAG), any(), any()); 213 214 alarmQueue.addAlarm("1", nowElapsed + 52 * MINUTE_IN_MILLIS); 215 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 216 anyInt(), eq(nowElapsed + 51 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any()); 217 } 218 219 @Test 220 public void testInexactQueue() { 221 final AlarmQueue<String> alarmQueue = createAlarmQueue(false, 0); 222 final long nowElapsed = mInjector.getElapsedRealtime(); 223 224 alarmQueue.addAlarm("com.android.test.1", nowElapsed + HOUR_IN_MILLIS); 225 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 226 anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), anyLong(), eq(ALARM_TAG), any(), any( 227 Handler.class)); 228 } 229 230 @Test 231 public void testMinTimeBetweenAlarms() { 232 final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 2 * HOUR_IN_MILLIS); 233 final long nowElapsed = mInjector.getElapsedRealtime(); 234 235 InOrder inOrder = inOrder(mAlarmManager); 236 237 final String pkg1 = "com.android.test.1"; 238 final String pkg2 = "com.android.test.2"; 239 final String pkg3 = "com.android.test.3"; 240 alarmQueue.addAlarm(pkg1, nowElapsed + HOUR_IN_MILLIS); 241 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 242 anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any()); 243 alarmQueue.addAlarm(pkg2, nowElapsed + 2 * HOUR_IN_MILLIS); 244 alarmQueue.addAlarm(pkg3, nowElapsed + 3 * HOUR_IN_MILLIS); 245 alarmQueue.addAlarm("com.android.test.4", nowElapsed + 4 * HOUR_IN_MILLIS); 246 247 advanceElapsedClock(HOUR_IN_MILLIS); 248 249 alarmQueue.onAlarm(); 250 // Minimum of 2 hours between alarms, so the next alarm should be 2 hours after the first. 251 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 252 anyInt(), eq(nowElapsed + 3 * HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any()); 253 254 advanceElapsedClock(2 * HOUR_IN_MILLIS); 255 alarmQueue.onAlarm(); 256 // Minimum of 2 hours between alarms, so the next alarm should be 2 hours after the second. 257 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 258 anyInt(), eq(nowElapsed + 5 * HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any()); 259 } 260 261 @Test 262 public void testMinTimeBetweenAlarms_freshAlarm() { 263 final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 5 * MINUTE_IN_MILLIS); 264 final long fixedTimeElapsed = mInjector.getElapsedRealtime(); 265 266 InOrder inOrder = inOrder(mAlarmManager); 267 268 final String pkg1 = "com.android.test.1"; 269 final String pkg2 = "com.android.test.2"; 270 alarmQueue.addAlarm(pkg1, fixedTimeElapsed + MINUTE_IN_MILLIS); 271 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 272 anyInt(), eq(fixedTimeElapsed + MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any()); 273 274 advanceElapsedClock(MINUTE_IN_MILLIS); 275 276 alarmQueue.onAlarm(); 277 // Minimum of 5 minutes between alarms, so the next alarm should be 5 minutes after the 278 // first. 279 alarmQueue.addAlarm(pkg2, fixedTimeElapsed + 2 * MINUTE_IN_MILLIS); 280 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 281 anyInt(), eq(fixedTimeElapsed + 6 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any()); 282 } 283 284 @Test 285 public void testOnAlarm() { 286 final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0); 287 final long nowElapsed = mInjector.getElapsedRealtime(); 288 289 InOrder inOrder = inOrder(mAlarmManager); 290 291 final String pkg1 = "com.android.test.1"; 292 final String pkg2 = "com.android.test.2"; 293 final String pkg3 = "com.android.test.3"; 294 final String pkg4 = "com.android.test.4"; 295 alarmQueue.addAlarm(pkg1, nowElapsed + HOUR_IN_MILLIS); 296 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 297 anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any()); 298 alarmQueue.addAlarm(pkg2, nowElapsed + 2 * HOUR_IN_MILLIS); 299 alarmQueue.addAlarm(pkg3, nowElapsed + 3 * HOUR_IN_MILLIS); 300 alarmQueue.addAlarm(pkg4, nowElapsed + 4 * HOUR_IN_MILLIS); 301 302 advanceElapsedClock(HOUR_IN_MILLIS); 303 304 final ArraySet<String> expectedExpired = new ArraySet<>(); 305 306 expectedExpired.add(pkg1); 307 alarmQueue.onAlarm(); 308 assertEquals(expectedExpired, mExpiredPackages); 309 // The next alarm should also be scheduled. 310 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 311 anyInt(), eq(nowElapsed + 2 * HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any()); 312 313 advanceElapsedClock(2 * HOUR_IN_MILLIS); 314 315 expectedExpired.clear(); 316 expectedExpired.add(pkg2); 317 expectedExpired.add(pkg3); 318 alarmQueue.onAlarm(); 319 assertEquals(expectedExpired, mExpiredPackages); 320 // The next alarm should also be scheduled. 321 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( 322 anyInt(), eq(nowElapsed + 4 * HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any()); 323 324 advanceElapsedClock(HOUR_IN_MILLIS); 325 326 expectedExpired.clear(); 327 expectedExpired.add(pkg4); 328 alarmQueue.onAlarm(); 329 assertEquals(expectedExpired, mExpiredPackages); 330 // No more alarms, so nothing should be scheduled with AlarmManager. 331 inOrder.verify(mAlarmManager, timeout(1000).times(0)) 332 .setExact(anyInt(), anyLong(), eq(ALARM_TAG), any(), any()); 333 } 334 335 @Test 336 public void testSettingMinTimeBetweenAlarms() { 337 final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 50); 338 assertEquals(50, alarmQueue.getMinTimeBetweenAlarmsMs()); 339 340 alarmQueue.setMinTimeBetweenAlarmsMs(2345); 341 assertEquals(2345, alarmQueue.getMinTimeBetweenAlarmsMs()); 342 343 try { 344 alarmQueue.setMinTimeBetweenAlarmsMs(-1); 345 fail("Successfully set negative time between alarms"); 346 } catch (IllegalArgumentException expected) { 347 // Success 348 } 349 try { 350 createAlarmQueue(false, -1); 351 fail("Successfully set negative time between alarms"); 352 } catch (IllegalArgumentException expected) { 353 // Success 354 } 355 } 356 } 357