1 package com.android.server.notification;
2 
3 import static org.junit.Assert.assertEquals;
4 import static org.junit.Assert.assertFalse;
5 import static org.junit.Assert.assertTrue;
6 import static org.mockito.Mockito.mock;
7 import static org.mockito.Mockito.spy;
8 
9 import android.app.Application;
10 import android.app.PendingIntent;
11 import android.content.Intent;
12 import android.net.Uri;
13 import android.service.notification.Condition;
14 import android.service.notification.ScheduleCalendar;
15 import android.service.notification.ZenModeConfig;
16 import android.testing.AndroidTestingRunner;
17 import android.testing.TestableLooper.RunWithLooper;
18 
19 import androidx.test.filters.SmallTest;
20 
21 import com.android.server.UiServiceTestCase;
22 import com.android.server.pm.PackageManagerService;
23 
24 import org.junit.Before;
25 import org.junit.Test;
26 import org.junit.runner.RunWith;
27 import org.mockito.MockitoAnnotations;
28 
29 import java.util.Calendar;
30 import java.util.GregorianCalendar;
31 
32 @RunWith(AndroidTestingRunner.class)
33 @SmallTest
34 @RunWithLooper
35 public class ScheduleConditionProviderTest extends UiServiceTestCase {
36 
37     ScheduleConditionProvider mService;
38 
39     @Before
setUp()40     public void setUp() throws Exception {
41         MockitoAnnotations.initMocks(this);
42 
43         Intent startIntent =
44                 new Intent("com.android.server.notification.ScheduleConditionProvider");
45         startIntent.setPackage("android");
46         ScheduleConditionProvider service = new ScheduleConditionProvider();
47         service.attach(
48                 getContext(),
49                 null,               // ActivityThread not actually used in Service
50                 ScheduleConditionProvider.class.getName(),
51                 null,               // token not needed when not talking with the activity manager
52                 mock(Application.class),
53                 null                // mocked services don't talk with the activity manager
54                 );
55         service.onCreate();
56         service.onBind(startIntent);
57         mService = spy(service);
58    }
59 
60     @Test
testIsValidConditionId_incomplete()61     public void testIsValidConditionId_incomplete() throws Exception {
62         Uri badConditionId = Uri.EMPTY;
63         assertFalse(mService.isValidConditionId(badConditionId));
64         assertEquals(Condition.STATE_ERROR,
65                 mService.evaluateSubscriptionLocked(badConditionId, null, 0, 1000).state);
66     }
67 
68     @Test
testIsValidConditionId()69     public void testIsValidConditionId() throws Exception {
70         ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
71         info.days = new int[] {1, 2, 4};
72         info.startHour = 8;
73         info.startMinute = 56;
74         info.nextAlarm = 1000;
75         info.exitAtAlarm = true;
76         info.endHour = 12;
77         info.endMinute = 9;
78         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
79         assertTrue(mService.isValidConditionId(conditionId));
80     }
81 
82     @Test
testEvaluateSubscription_noAlarmExit_InSchedule()83     public void testEvaluateSubscription_noAlarmExit_InSchedule() {
84         Calendar now = getNow();
85 
86         // Schedule - 1 hour long; starts now
87         ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
88         info.days = new int[] {Calendar.FRIDAY};
89         info.startHour = now.get(Calendar.HOUR_OF_DAY);
90         info.startMinute = now.get(Calendar.MINUTE);
91         info.nextAlarm = 0;
92         info.exitAtAlarm = false;
93         info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1;
94         info.endMinute = info.startMinute;
95         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
96         ScheduleCalendar cal = new ScheduleCalendar();
97         cal.setSchedule(info);
98         assertTrue(cal.isInSchedule(now.getTimeInMillis()));
99 
100         Condition condition = mService.evaluateSubscriptionLocked(
101                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
102 
103         assertEquals(Condition.STATE_TRUE, condition.state);
104     }
105 
106     @Test
testEvaluateSubscription_noAlarmExit_InScheduleSnoozed()107     public void testEvaluateSubscription_noAlarmExit_InScheduleSnoozed() {
108         Calendar now = getNow();
109 
110         // Schedule - 1 hour long; starts now
111         ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
112         info.days = new int[] {Calendar.FRIDAY};
113         info.startHour = now.get(Calendar.HOUR_OF_DAY);
114         info.startMinute = now.get(Calendar.MINUTE);
115         info.nextAlarm = 0;
116         info.exitAtAlarm = false;
117         info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1;
118         info.endMinute = info.startMinute;
119         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
120         ScheduleCalendar cal = new ScheduleCalendar();
121         cal.setSchedule(info);
122         assertTrue(cal.isInSchedule(now.getTimeInMillis()));
123 
124         mService.addSnoozed(conditionId);
125 
126         Condition condition = mService.evaluateSubscriptionLocked(
127                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
128 
129         assertEquals(Condition.STATE_FALSE, condition.state);
130     }
131 
132     @Test
testEvaluateSubscription_noAlarmExit_beforeSchedule()133     public void testEvaluateSubscription_noAlarmExit_beforeSchedule() {
134         Calendar now = new GregorianCalendar();
135         now.set(Calendar.HOUR_OF_DAY, 14);
136         now.set(Calendar.MINUTE, 15);
137         now.set(Calendar.SECOND, 59);
138         now.set(Calendar.MILLISECOND, 0);
139         now.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
140 
141         // Schedule - 1 hour long; starts in 1 second
142         ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
143         info.days = new int[] {Calendar.FRIDAY};
144         info.startHour = now.get(Calendar.HOUR_OF_DAY);
145         info.startMinute = now.get(Calendar.MINUTE) + 1;
146         info.nextAlarm = 0;
147         info.exitAtAlarm = false;
148         info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1;
149         info.endMinute = info.startMinute;
150         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
151         ScheduleCalendar cal = new ScheduleCalendar();
152         cal.setSchedule(info);
153 
154         Condition condition = mService.evaluateSubscriptionLocked(
155                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
156 
157         assertEquals(Condition.STATE_FALSE, condition.state);
158     }
159 
160     @Test
testEvaluateSubscription_noAlarmExit_endSchedule()161     public void testEvaluateSubscription_noAlarmExit_endSchedule() {
162         Calendar now = getNow();
163 
164         // Schedule - 1 hour long; ends now
165         ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
166         info.days = new int[] {Calendar.FRIDAY};
167         info.startHour = now.get(Calendar.HOUR_OF_DAY) - 1;
168         info.startMinute = now.get(Calendar.MINUTE);
169         info.nextAlarm = 0;
170         info.exitAtAlarm = false;
171         info.endHour = now.get(Calendar.HOUR_OF_DAY);
172         info.endMinute = now.get(Calendar.MINUTE);
173         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
174         ScheduleCalendar cal = new ScheduleCalendar();
175         cal.setSchedule(info);
176 
177         Condition condition = mService.evaluateSubscriptionLocked(
178                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
179 
180         assertEquals(Condition.STATE_FALSE, condition.state);
181     }
182 
183     @Test
testEvaluateSubscription_alarmSetBeforeInSchedule()184     public void testEvaluateSubscription_alarmSetBeforeInSchedule() {
185         Calendar now = getNow();
186 
187         // Schedule - 1 hour long; starts now, ends with alarm
188         ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
189         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
190         ScheduleCalendar cal = new ScheduleCalendar();
191         cal.setSchedule(info);
192 
193         // an hour before start, update with an alarm that will fire during the schedule
194         mService.evaluateSubscriptionLocked(
195                 conditionId, cal, now.getTimeInMillis() - 1000, now.getTimeInMillis() + 1000);
196 
197         // at start, should be in dnd
198         Condition condition = mService.evaluateSubscriptionLocked(
199                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
200         assertEquals(Condition.STATE_TRUE, condition.state);
201 
202         // at alarm fire time, should exit dnd
203         assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
204         assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
205                 cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
206         condition = mService.evaluateSubscriptionLocked(
207                 conditionId, cal, now.getTimeInMillis() + 1000, 0);
208         assertEquals(Condition.STATE_FALSE, condition.state);
209     }
210 
211     @Test
testEvaluateSubscription_alarmSetInSchedule()212     public void testEvaluateSubscription_alarmSetInSchedule() {
213         Calendar now = getNow();
214 
215         // Schedule - 1 hour long; starts now, ends with alarm
216         ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
217         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
218         ScheduleCalendar cal = new ScheduleCalendar();
219         cal.setSchedule(info);
220 
221         // at start, should be in dnd
222         Condition condition = mService.evaluateSubscriptionLocked(
223                 conditionId, cal, now.getTimeInMillis(), 0);
224         assertEquals(Condition.STATE_TRUE, condition.state);
225 
226         // in schedule, update with alarm time, should be in dnd
227         condition = mService.evaluateSubscriptionLocked(
228                 conditionId, cal, now.getTimeInMillis() + 500, now.getTimeInMillis() + 1000);
229         assertEquals(Condition.STATE_TRUE, condition.state);
230 
231         // at alarm fire time, should exit dnd
232         assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
233         assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
234                 cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
235         condition = mService.evaluateSubscriptionLocked(
236                 conditionId, cal, now.getTimeInMillis() + 1000, 0);
237         assertEquals(Condition.STATE_FALSE, condition.state);
238     }
239 
240     @Test
testEvaluateSubscription_earlierAlarmSet()241     public void testEvaluateSubscription_earlierAlarmSet() {
242         Calendar now = getNow();
243 
244         // Schedule - 1 hour long; starts now, ends with alarm
245         ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
246         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
247         ScheduleCalendar cal = new ScheduleCalendar();
248         cal.setSchedule(info);
249 
250         // at start, should be in dnd, alarm in 2000 ms
251         Condition condition = mService.evaluateSubscriptionLocked(
252                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 2000);
253         assertEquals(Condition.STATE_TRUE, condition.state);
254 
255         // in schedule, update with earlier alarm time, should be in dnd
256         condition = mService.evaluateSubscriptionLocked(
257                 conditionId, cal, now.getTimeInMillis() + 500, now.getTimeInMillis() + 1000);
258         assertEquals(Condition.STATE_TRUE, condition.state);
259 
260         // at earliest alarm fire time, should exit dnd
261         assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
262         assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
263                 cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
264         condition = mService.evaluateSubscriptionLocked(
265                 conditionId, cal, now.getTimeInMillis() + 1000, 0);
266         assertEquals(Condition.STATE_FALSE, condition.state);
267     }
268 
269     @Test
testEvaluateSubscription_laterAlarmSet()270     public void testEvaluateSubscription_laterAlarmSet() {
271         Calendar now = getNow();
272 
273         // Schedule - 1 hour long; starts now, ends with alarm
274         ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
275         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
276         ScheduleCalendar cal = new ScheduleCalendar();
277         cal.setSchedule(info);
278 
279         // at start, should be in dnd, alarm in 500 ms
280         Condition condition = mService.evaluateSubscriptionLocked(
281                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 500);
282         assertEquals(Condition.STATE_TRUE, condition.state);
283 
284         // in schedule, update with nextAlarm = later alarm time (1000), should be in dnd
285         condition = mService.evaluateSubscriptionLocked(
286                 conditionId, cal, now.getTimeInMillis() + 250, now.getTimeInMillis() + 1000);
287         assertEquals(Condition.STATE_TRUE, condition.state);
288 
289         // at next alarm fire time (1000), should exit dnd
290         assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
291         assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
292                 cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
293         condition = mService.evaluateSubscriptionLocked(
294                 conditionId, cal, now.getTimeInMillis() + 1000, 0);
295         assertEquals(Condition.STATE_FALSE, condition.state);
296     }
297 
298     @Test
testEvaluateSubscription_alarmCanceled()299     public void testEvaluateSubscription_alarmCanceled() {
300         Calendar now = getNow();
301 
302         // Schedule - 1 hour long; starts now, ends with alarm
303         ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
304         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
305         ScheduleCalendar cal = new ScheduleCalendar();
306         cal.setSchedule(info);
307 
308         // at start, should be in dnd, alarm in 500 ms
309         Condition condition = mService.evaluateSubscriptionLocked(
310                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 500);
311         assertEquals(Condition.STATE_TRUE, condition.state);
312 
313         // in schedule, cancel alarm
314         condition = mService.evaluateSubscriptionLocked(
315                 conditionId, cal, now.getTimeInMillis() + 250, 0);
316         assertEquals(Condition.STATE_TRUE, condition.state);
317 
318         // at previous alarm time, should not exit DND
319         assertTrue(cal.isInSchedule(now.getTimeInMillis() + 500));
320         assertFalse(cal.shouldExitForAlarm(now.getTimeInMillis() + 500));
321         condition = mService.evaluateSubscriptionLocked(
322                 conditionId, cal, now.getTimeInMillis() + 500, 0);
323         assertEquals(Condition.STATE_TRUE, condition.state);
324 
325         // end of schedule, exit DND
326         now.add(Calendar.HOUR_OF_DAY, 1);
327         condition = mService.evaluateSubscriptionLocked(conditionId, cal, now.getTimeInMillis(), 0);
328         assertEquals(Condition.STATE_FALSE, condition.state);
329     }
330 
331     @Test
testGetPendingIntent()332     public void testGetPendingIntent() {
333         PendingIntent pi = mService.getPendingIntent(1000);
334         assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, pi.getIntent().getPackage());
335     }
336 
getNow()337     private Calendar getNow() {
338         Calendar now = new GregorianCalendar();
339         now.set(Calendar.HOUR_OF_DAY, 14);
340         now.set(Calendar.MINUTE, 16);
341         now.set(Calendar.SECOND, 0);
342         now.set(Calendar.MILLISECOND, 0);
343         now.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
344         return now;
345     }
346 
getScheduleEndsInHour(Calendar now)347     private ZenModeConfig.ScheduleInfo getScheduleEndsInHour(Calendar now) {
348         ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
349         info.days = new int[] {Calendar.FRIDAY};
350         info.startHour = now.get(Calendar.HOUR_OF_DAY);
351         info.startMinute = now.get(Calendar.MINUTE);
352         info.exitAtAlarm = true;
353         info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1;
354         info.endMinute = now.get(Calendar.MINUTE);
355         return info;
356     }
357 }
358