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