• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.server.notification;
2 
3 import static android.app.AlarmManager.RTC_WAKEUP;
4 
5 import static com.google.common.truth.Truth.assertThat;
6 
7 import static org.junit.Assert.assertEquals;
8 import static org.junit.Assert.assertFalse;
9 import static org.junit.Assert.assertTrue;
10 import static org.mockito.ArgumentMatchers.any;
11 import static org.mockito.ArgumentMatchers.anyInt;
12 import static org.mockito.ArgumentMatchers.eq;
13 import static org.mockito.Mockito.mock;
14 import static org.mockito.Mockito.never;
15 import static org.mockito.Mockito.reset;
16 import static org.mockito.Mockito.times;
17 import static org.mockito.Mockito.verify;
18 import static org.mockito.Mockito.when;
19 
20 import static java.time.temporal.ChronoUnit.HOURS;
21 import static java.time.temporal.ChronoUnit.MINUTES;
22 
23 import android.app.ActivityManager;
24 import android.app.AlarmManager;
25 import android.app.Application;
26 import android.app.PendingIntent;
27 import android.content.BroadcastReceiver;
28 import android.content.ComponentName;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.os.SimpleClock;
34 import android.platform.test.annotations.EnableFlags;
35 import android.platform.test.flag.junit.SetFlagsRule;
36 import android.service.notification.Condition;
37 import android.service.notification.ScheduleCalendar;
38 import android.service.notification.ZenModeConfig;
39 import android.testing.AndroidTestingRunner;
40 import android.testing.TestableLooper.RunWithLooper;
41 
42 import androidx.test.filters.SmallTest;
43 
44 import com.android.server.UiServiceTestCase;
45 import com.android.server.pm.PackageManagerService;
46 
47 import com.google.common.collect.ImmutableList;
48 
49 import org.junit.Before;
50 import org.junit.Rule;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 import org.mockito.ArgumentCaptor;
54 import org.mockito.Mock;
55 import org.mockito.MockitoAnnotations;
56 
57 import java.time.Instant;
58 import java.time.ZoneId;
59 import java.time.ZoneOffset;
60 import java.time.ZonedDateTime;
61 import java.util.Calendar;
62 import java.util.GregorianCalendar;
63 
64 @RunWith(AndroidTestingRunner.class)
65 @SmallTest
66 @RunWithLooper
67 public class ScheduleConditionProviderTest extends UiServiceTestCase {
68     @Rule
69     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
70 
71     private ScheduleConditionProvider mService;
72     private TestClock mClock = new TestClock();
73     @Mock private AlarmManager mAlarmManager;
74 
75     @Before
setUp()76     public void setUp() throws Exception {
77         MockitoAnnotations.initMocks(this);
78         mContext.addMockSystemService(AlarmManager.class, mAlarmManager);
79 
80         Intent startIntent =
81                 new Intent("com.android.server.notification.ScheduleConditionProvider");
82         startIntent.setPackage("android");
83         ScheduleConditionProvider service = new ScheduleConditionProvider(mClock);
84         service.attach(
85                 getContext(),
86                 null,               // ActivityThread not actually used in Service
87                 ScheduleConditionProvider.class.getName(),
88                 null,               // token not needed when not talking with the activity manager
89                 mock(Application.class),
90                 null                // mocked services don't talk with the activity manager
91                 );
92         service.onCreate();
93         service.onBind(startIntent);
94         mService = service;
95    }
96 
97     @Test
getComponent_returnsComponent()98     public void getComponent_returnsComponent() {
99         assertThat(mService.getComponent()).isEqualTo(new ComponentName("android",
100                 "com.android.server.notification.ScheduleConditionProvider"));
101     }
102 
103     @Test
testIsValidConditionId_incomplete()104     public void testIsValidConditionId_incomplete() throws Exception {
105         Uri badConditionId = Uri.EMPTY;
106         assertFalse(mService.isValidConditionId(badConditionId));
107         assertEquals(Condition.STATE_ERROR,
108                 mService.evaluateSubscriptionLocked(badConditionId, null, 0, 1000).state);
109     }
110 
111     @Test
testIsValidConditionId()112     public void testIsValidConditionId() throws Exception {
113         ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
114         info.days = new int[] {1, 2, 4};
115         info.startHour = 8;
116         info.startMinute = 56;
117         info.nextAlarm = 1000;
118         info.exitAtAlarm = true;
119         info.endHour = 12;
120         info.endMinute = 9;
121         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
122         assertTrue(mService.isValidConditionId(conditionId));
123     }
124 
125     @Test
testEvaluateSubscription_noAlarmExit_InSchedule()126     public void testEvaluateSubscription_noAlarmExit_InSchedule() {
127         Calendar now = getNow();
128 
129         // Schedule - 1 hour long; starts now
130         ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
131         info.days = new int[] {Calendar.FRIDAY};
132         info.startHour = now.get(Calendar.HOUR_OF_DAY);
133         info.startMinute = now.get(Calendar.MINUTE);
134         info.nextAlarm = 0;
135         info.exitAtAlarm = false;
136         info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1;
137         info.endMinute = info.startMinute;
138         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
139         ScheduleCalendar cal = new ScheduleCalendar();
140         cal.setSchedule(info);
141         assertTrue(cal.isInSchedule(now.getTimeInMillis()));
142 
143         Condition condition = mService.evaluateSubscriptionLocked(
144                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
145 
146         assertEquals(Condition.STATE_TRUE, condition.state);
147     }
148 
149     @Test
testEvaluateSubscription_noAlarmExit_InScheduleSnoozed()150     public void testEvaluateSubscription_noAlarmExit_InScheduleSnoozed() {
151         Calendar now = getNow();
152 
153         // Schedule - 1 hour long; starts now
154         ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
155         info.days = new int[] {Calendar.FRIDAY};
156         info.startHour = now.get(Calendar.HOUR_OF_DAY);
157         info.startMinute = now.get(Calendar.MINUTE);
158         info.nextAlarm = 0;
159         info.exitAtAlarm = false;
160         info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1;
161         info.endMinute = info.startMinute;
162         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
163         ScheduleCalendar cal = new ScheduleCalendar();
164         cal.setSchedule(info);
165         assertTrue(cal.isInSchedule(now.getTimeInMillis()));
166 
167         mService.addSnoozed(conditionId);
168 
169         Condition condition = mService.evaluateSubscriptionLocked(
170                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
171 
172         assertEquals(Condition.STATE_FALSE, condition.state);
173     }
174 
175     @Test
testEvaluateSubscription_noAlarmExit_beforeSchedule()176     public void testEvaluateSubscription_noAlarmExit_beforeSchedule() {
177         Calendar now = new GregorianCalendar();
178         now.set(Calendar.HOUR_OF_DAY, 14);
179         now.set(Calendar.MINUTE, 15);
180         now.set(Calendar.SECOND, 59);
181         now.set(Calendar.MILLISECOND, 0);
182         now.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
183 
184         // Schedule - 1 hour long; starts in 1 second
185         ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
186         info.days = new int[] {Calendar.FRIDAY};
187         info.startHour = now.get(Calendar.HOUR_OF_DAY);
188         info.startMinute = now.get(Calendar.MINUTE) + 1;
189         info.nextAlarm = 0;
190         info.exitAtAlarm = false;
191         info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1;
192         info.endMinute = info.startMinute;
193         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
194         ScheduleCalendar cal = new ScheduleCalendar();
195         cal.setSchedule(info);
196 
197         Condition condition = mService.evaluateSubscriptionLocked(
198                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
199 
200         assertEquals(Condition.STATE_FALSE, condition.state);
201     }
202 
203     @Test
testEvaluateSubscription_noAlarmExit_endSchedule()204     public void testEvaluateSubscription_noAlarmExit_endSchedule() {
205         Calendar now = getNow();
206 
207         // Schedule - 1 hour long; ends now
208         ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
209         info.days = new int[] {Calendar.FRIDAY};
210         info.startHour = now.get(Calendar.HOUR_OF_DAY) - 1;
211         info.startMinute = now.get(Calendar.MINUTE);
212         info.nextAlarm = 0;
213         info.exitAtAlarm = false;
214         info.endHour = now.get(Calendar.HOUR_OF_DAY);
215         info.endMinute = now.get(Calendar.MINUTE);
216         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
217         ScheduleCalendar cal = new ScheduleCalendar();
218         cal.setSchedule(info);
219 
220         Condition condition = mService.evaluateSubscriptionLocked(
221                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
222 
223         assertEquals(Condition.STATE_FALSE, condition.state);
224     }
225 
226     @Test
testEvaluateSubscription_alarmSetBeforeInSchedule()227     public void testEvaluateSubscription_alarmSetBeforeInSchedule() {
228         Calendar now = getNow();
229 
230         // Schedule - 1 hour long; starts now, ends with alarm
231         ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
232         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
233         ScheduleCalendar cal = new ScheduleCalendar();
234         cal.setSchedule(info);
235 
236         // an hour before start, update with an alarm that will fire during the schedule
237         mService.evaluateSubscriptionLocked(
238                 conditionId, cal, now.getTimeInMillis() - 1000, now.getTimeInMillis() + 1000);
239 
240         // at start, should be in dnd
241         Condition condition = mService.evaluateSubscriptionLocked(
242                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
243         assertEquals(Condition.STATE_TRUE, condition.state);
244 
245         // at alarm fire time, should exit dnd
246         assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
247         assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
248                 cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
249         condition = mService.evaluateSubscriptionLocked(
250                 conditionId, cal, now.getTimeInMillis() + 1000, 0);
251         assertEquals(Condition.STATE_FALSE, condition.state);
252     }
253 
254     @Test
testEvaluateSubscription_alarmSetInSchedule()255     public void testEvaluateSubscription_alarmSetInSchedule() {
256         Calendar now = getNow();
257 
258         // Schedule - 1 hour long; starts now, ends with alarm
259         ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
260         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
261         ScheduleCalendar cal = new ScheduleCalendar();
262         cal.setSchedule(info);
263 
264         // at start, should be in dnd
265         Condition condition = mService.evaluateSubscriptionLocked(
266                 conditionId, cal, now.getTimeInMillis(), 0);
267         assertEquals(Condition.STATE_TRUE, condition.state);
268 
269         // in schedule, update with alarm time, should be in dnd
270         condition = mService.evaluateSubscriptionLocked(
271                 conditionId, cal, now.getTimeInMillis() + 500, now.getTimeInMillis() + 1000);
272         assertEquals(Condition.STATE_TRUE, condition.state);
273 
274         // at alarm fire time, should exit dnd
275         assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
276         assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
277                 cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
278         condition = mService.evaluateSubscriptionLocked(
279                 conditionId, cal, now.getTimeInMillis() + 1000, 0);
280         assertEquals(Condition.STATE_FALSE, condition.state);
281     }
282 
283     @Test
testEvaluateSubscription_earlierAlarmSet()284     public void testEvaluateSubscription_earlierAlarmSet() {
285         Calendar now = getNow();
286 
287         // Schedule - 1 hour long; starts now, ends with alarm
288         ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
289         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
290         ScheduleCalendar cal = new ScheduleCalendar();
291         cal.setSchedule(info);
292 
293         // at start, should be in dnd, alarm in 2000 ms
294         Condition condition = mService.evaluateSubscriptionLocked(
295                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 2000);
296         assertEquals(Condition.STATE_TRUE, condition.state);
297 
298         // in schedule, update with earlier alarm time, should be in dnd
299         condition = mService.evaluateSubscriptionLocked(
300                 conditionId, cal, now.getTimeInMillis() + 500, now.getTimeInMillis() + 1000);
301         assertEquals(Condition.STATE_TRUE, condition.state);
302 
303         // at earliest alarm fire time, should exit dnd
304         assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
305         assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
306                 cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
307         condition = mService.evaluateSubscriptionLocked(
308                 conditionId, cal, now.getTimeInMillis() + 1000, 0);
309         assertEquals(Condition.STATE_FALSE, condition.state);
310     }
311 
312     @Test
testEvaluateSubscription_laterAlarmSet()313     public void testEvaluateSubscription_laterAlarmSet() {
314         Calendar now = getNow();
315 
316         // Schedule - 1 hour long; starts now, ends with alarm
317         ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
318         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
319         ScheduleCalendar cal = new ScheduleCalendar();
320         cal.setSchedule(info);
321 
322         // at start, should be in dnd, alarm in 500 ms
323         Condition condition = mService.evaluateSubscriptionLocked(
324                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 500);
325         assertEquals(Condition.STATE_TRUE, condition.state);
326 
327         // in schedule, update with nextAlarm = later alarm time (1000), should be in dnd
328         condition = mService.evaluateSubscriptionLocked(
329                 conditionId, cal, now.getTimeInMillis() + 250, now.getTimeInMillis() + 1000);
330         assertEquals(Condition.STATE_TRUE, condition.state);
331 
332         // at next alarm fire time (1000), should exit dnd
333         assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
334         assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
335                 cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
336         condition = mService.evaluateSubscriptionLocked(
337                 conditionId, cal, now.getTimeInMillis() + 1000, 0);
338         assertEquals(Condition.STATE_FALSE, condition.state);
339     }
340 
341     @Test
testEvaluateSubscription_alarmCanceled()342     public void testEvaluateSubscription_alarmCanceled() {
343         Calendar now = getNow();
344 
345         // Schedule - 1 hour long; starts now, ends with alarm
346         ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
347         Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
348         ScheduleCalendar cal = new ScheduleCalendar();
349         cal.setSchedule(info);
350 
351         // at start, should be in dnd, alarm in 500 ms
352         Condition condition = mService.evaluateSubscriptionLocked(
353                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 500);
354         assertEquals(Condition.STATE_TRUE, condition.state);
355 
356         // in schedule, cancel alarm
357         condition = mService.evaluateSubscriptionLocked(
358                 conditionId, cal, now.getTimeInMillis() + 250, 0);
359         assertEquals(Condition.STATE_TRUE, condition.state);
360 
361         // at previous alarm time, should not exit DND
362         assertTrue(cal.isInSchedule(now.getTimeInMillis() + 500));
363         assertFalse(cal.shouldExitForAlarm(now.getTimeInMillis() + 500));
364         condition = mService.evaluateSubscriptionLocked(
365                 conditionId, cal, now.getTimeInMillis() + 500, 0);
366         assertEquals(Condition.STATE_TRUE, condition.state);
367 
368         // end of schedule, exit DND
369         now.add(Calendar.HOUR_OF_DAY, 1);
370         condition = mService.evaluateSubscriptionLocked(conditionId, cal, now.getTimeInMillis(), 0);
371         assertEquals(Condition.STATE_FALSE, condition.state);
372     }
373 
374     @Test
testGetPendingIntent()375     public void testGetPendingIntent() {
376         PendingIntent pi = mService.getPendingIntent(1000);
377         assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, pi.getIntent().getPackage());
378     }
379 
380     @Test
381     @EnableFlags(android.app.Flags.FLAG_MODES_HSUM)
onSubscribe_registersReceiverForAllUsers()382     public void onSubscribe_registersReceiverForAllUsers() {
383         Calendar now = getNow();
384         Uri condition = ZenModeConfig.toScheduleConditionId(getScheduleEndsInHour(now));
385 
386         mService.onSubscribe(condition);
387 
388         ArgumentCaptor<IntentFilter> filterCaptor = ArgumentCaptor.forClass(IntentFilter.class);
389         verify(mContext).registerReceiverForAllUsers(any(), filterCaptor.capture(), any(), any());
390         IntentFilter filter = filterCaptor.getValue();
391         assertThat(filter.actionsIterator()).isNotNull();
392         assertThat(ImmutableList.copyOf(filter.actionsIterator()))
393                 .contains(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
394     }
395 
396     @Test
397     @EnableFlags(android.app.Flags.FLAG_MODES_HSUM)
onAlarmClockChanged_storesNextAlarm()398     public void onAlarmClockChanged_storesNextAlarm() {
399         Instant scheduleStart = Instant.parse("2024-10-22T16:00:00Z");
400         Instant scheduleEnd = scheduleStart.plus(1, HOURS);
401 
402         Instant now = scheduleStart.plus(15, MINUTES);
403         mClock.setNowMillis(now.toEpochMilli());
404 
405         Uri condition = ZenModeConfig.toScheduleConditionId(
406                 getOneHourSchedule(scheduleStart.atZone(ZoneId.systemDefault())));
407         mService.onSubscribe(condition);
408 
409         // Now prepare to send an "alarm set for 16:30" broadcast.
410         Instant alarm = scheduleStart.plus(30, MINUTES);
411         ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(
412                 BroadcastReceiver.class);
413         verify(mContext).registerReceiverForAllUsers(receiverCaptor.capture(), any(), any(), any());
414         BroadcastReceiver receiver = receiverCaptor.getValue();
415         receiver.setPendingResult(pendingResultForUserBroadcast(ActivityManager.getCurrentUser()));
416         when(mAlarmManager.getNextAlarmClock(anyInt())).thenReturn(
417                 new AlarmManager.AlarmClockInfo(alarm.toEpochMilli(), null));
418 
419         Intent intent = new Intent(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
420         receiver.onReceive(mContext, intent);
421 
422         // The time for the alarm was stored in the ScheduleCalendar, meaning the rule will end when
423         // the next evaluation after that point happens.
424         ScheduleCalendar scheduleCalendar =
425                 mService.getSubscriptions().values().stream().findFirst().get();
426         assertThat(scheduleCalendar.shouldExitForAlarm(alarm.toEpochMilli() - 1)).isFalse();
427         assertThat(scheduleCalendar.shouldExitForAlarm(alarm.toEpochMilli() + 1)).isTrue();
428 
429         // But the next wakeup is unchanged, at the time of the schedule end (17:00).
430         verify(mAlarmManager, times(2)).setExact(eq(RTC_WAKEUP), eq(scheduleEnd.toEpochMilli()),
431                 any());
432     }
433 
434     @Test
435     @EnableFlags(android.app.Flags.FLAG_MODES_HSUM)
onAlarmClockChanged_forAnotherUser_isIgnored()436     public void onAlarmClockChanged_forAnotherUser_isIgnored() {
437         Instant scheduleStart = Instant.parse("2024-10-22T16:00:00Z");
438         Instant now = scheduleStart.plus(15, MINUTES);
439         mClock.setNowMillis(now.toEpochMilli());
440 
441         Uri condition = ZenModeConfig.toScheduleConditionId(
442                 getOneHourSchedule(scheduleStart.atZone(ZoneId.systemDefault())));
443         mService.onSubscribe(condition);
444 
445         // Now prepare to send an "alarm set for a different user" broadcast.
446         ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(
447                 BroadcastReceiver.class);
448         verify(mContext).registerReceiverForAllUsers(receiverCaptor.capture(), any(), any(), any());
449         BroadcastReceiver receiver = receiverCaptor.getValue();
450 
451         reset(mAlarmManager);
452         int anotherUser = ActivityManager.getCurrentUser() + 1;
453         receiver.setPendingResult(pendingResultForUserBroadcast(anotherUser));
454         Intent intent = new Intent(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
455         receiver.onReceive(mContext, intent);
456 
457         // The alarm data was not read.
458         verify(mAlarmManager, never()).getNextAlarmClock(anyInt());
459     }
460 
getNow()461     private Calendar getNow() {
462         Calendar now = new GregorianCalendar();
463         now.set(Calendar.HOUR_OF_DAY, 14);
464         now.set(Calendar.MINUTE, 16);
465         now.set(Calendar.SECOND, 0);
466         now.set(Calendar.MILLISECOND, 0);
467         now.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
468         return now;
469     }
470 
getScheduleEndsInHour(Calendar now)471     private ZenModeConfig.ScheduleInfo getScheduleEndsInHour(Calendar now) {
472         ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
473         info.days = new int[] {Calendar.FRIDAY};
474         info.startHour = now.get(Calendar.HOUR_OF_DAY);
475         info.startMinute = now.get(Calendar.MINUTE);
476         info.exitAtAlarm = true;
477         info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1;
478         info.endMinute = now.get(Calendar.MINUTE);
479         return info;
480     }
481 
getOneHourSchedule(ZonedDateTime start)482     private static ZenModeConfig.ScheduleInfo getOneHourSchedule(ZonedDateTime start) {
483         ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
484         // Note: DayOfWeek.MONDAY doesn't match Calendar.MONDAY
485         info.days = new int[] { (start.getDayOfWeek().getValue() % 7) + 1 };
486         info.startHour = start.getHour();
487         info.startMinute = start.getMinute();
488         info.endHour = start.plusHours(1).getHour();
489         info.endMinute = start.plusHours(1).getMinute();
490         info.exitAtAlarm = true;
491         return info;
492     }
493 
pendingResultForUserBroadcast(int userId)494     private static BroadcastReceiver.PendingResult pendingResultForUserBroadcast(int userId) {
495         return new BroadcastReceiver.PendingResult(0, "", new Bundle(), 0, false, false, null,
496                 userId, 0);
497     }
498 
499     private static class TestClock extends SimpleClock {
500         private long mNowMillis = 441644400000L;
501 
TestClock()502         private TestClock() {
503             super(ZoneOffset.UTC);
504         }
505 
506         @Override
millis()507         public long millis() {
508             return mNowMillis;
509         }
510 
setNowMillis(long millis)511         private void setNowMillis(long millis) {
512             mNowMillis = millis;
513         }
514     }
515 }
516