1 /*
2  * Copyright (C) 2023 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.devicelockcontroller.schedule;
18 
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
21 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
22 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
23 
24 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.PROVISION_PAUSED;
25 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.UNPROVISIONED;
26 import static com.android.devicelockcontroller.schedule.DeviceLockControllerSchedulerImpl.DEVICE_CHECK_IN_WORK_NAME;
27 
28 import static com.google.common.truth.Truth.assertThat;
29 
30 import android.app.AlarmManager;
31 import android.app.PendingIntent;
32 import android.net.NetworkRequest;
33 import android.os.SystemClock;
34 
35 import androidx.test.core.app.ApplicationProvider;
36 import androidx.work.Configuration;
37 import androidx.work.ExistingWorkPolicy;
38 import androidx.work.OneTimeWorkRequest;
39 import androidx.work.WorkInfo;
40 import androidx.work.WorkManager;
41 import androidx.work.testing.SynchronousExecutor;
42 import androidx.work.testing.WorkManagerTestInitHelper;
43 
44 import com.android.devicelockcontroller.TestDeviceLockControllerApplication;
45 import com.android.devicelockcontroller.common.DeviceLockConstants;
46 import com.android.devicelockcontroller.provision.worker.DeviceCheckInWorker;
47 import com.android.devicelockcontroller.storage.UserParameters;
48 import com.android.devicelockcontroller.util.ThreadUtils;
49 
50 import com.google.common.util.concurrent.Futures;
51 
52 import org.junit.Before;
53 import org.junit.Test;
54 import org.junit.runner.RunWith;
55 import org.robolectric.RobolectricTestRunner;
56 import org.robolectric.Shadows;
57 import org.robolectric.shadows.ShadowAlarmManager;
58 
59 import java.time.Clock;
60 import java.time.Duration;
61 import java.time.Instant;
62 import java.time.ZoneOffset;
63 import java.util.List;
64 import java.util.concurrent.TimeUnit;
65 
66 @RunWith(RobolectricTestRunner.class)
67 public final class DeviceLockControllerSchedulerImplTest {
68     private static final long PROVISION_PAUSED_MILLIS = TimeUnit.MINUTES.toMillis(
69             DeviceLockControllerSchedulerImpl.PROVISION_PAUSED_MINUTES_DEFAULT);
70     private static final Duration TEST_RETRY_CHECK_IN_DELAY = Duration.ofDays(30);
71     private static final long TEST_NEXT_CHECK_IN_TIME_MILLIS = Duration.ofHours(10).toMillis();
72     private static final long TEST_RESUME_PROVISION_TIME_MILLIS = Duration.ofHours(20).toMillis();
73     private static final long TEST_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS = Duration.ofHours(
74             2).toMillis();
75     private static final long TEST_RESET_DEVICE_TIME_MILLIS = Duration.ofHours(
76             50).toMillis();
77 
78     private static final long PROVISION_STATE_REPORT_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(
79             DeviceLockControllerSchedulerImpl.PROVISION_STATE_REPORT_INTERVAL_DEFAULT_MINUTES);
80     private static final long TEST_CURRENT_TIME_MILLIS = Duration.ofHours(5).toMillis();
81     private static final Clock TEST_CLOCK = Clock.fixed(
82             Instant.ofEpochMilli(TEST_CURRENT_TIME_MILLIS),
83             ZoneOffset.UTC);
84     public static final long TEST_POSITIVE_DELTA_MILLIS = Duration.ofHours(5).toMillis();
85     public static final long TEST_NEGATIVE_DELTA_MILLIS = Duration.ofHours(-5).toMillis();
86     private static final long RESET_DEVICE_MILLIS = TimeUnit.MINUTES.toMillis(
87             DeviceLockConstants.NON_MANDATORY_PROVISION_DEVICE_RESET_COUNTDOWN_MINUTE);
88     DeviceLockControllerSchedulerImpl mScheduler;
89     TestDeviceLockControllerApplication mTestApp;
90 
91     @Before
setUp()92     public void setUp() throws Exception {
93         mTestApp = ApplicationProvider.getApplicationContext();
94         mScheduler = new DeviceLockControllerSchedulerImpl(mTestApp, TEST_CLOCK,
95                 mTestApp.getProvisionStateController());
96         Configuration config = new Configuration.Builder()
97                 .setMinimumLoggingLevel(android.util.Log.DEBUG)
98                 .setExecutor(new SynchronousExecutor())
99                 .build();
100         WorkManagerTestInitHelper.initializeTestWorkManager(mTestApp, config);
101     }
102 
103     @Test
correctExpectedToRunTime_retryCheckInExpected_positiveDelta_shouldUpdate()104     public void correctExpectedToRunTime_retryCheckInExpected_positiveDelta_shouldUpdate() {
105         // GIVEN retry check in is expected
106         UserParameters.setNextCheckInTimeMillis(mTestApp, TEST_NEXT_CHECK_IN_TIME_MILLIS);
107 
108         // GIVEN time change delta is positive.
109         UserParameters.setBootTimeMillis(mTestApp,
110                 TEST_CURRENT_TIME_MILLIS - TEST_POSITIVE_DELTA_MILLIS
111                         - SystemClock.elapsedRealtime());
112 
113 
114         runBySequentialExecutor(() -> {
115             // WHEN correct expected to run time for UNPROVISIONED state
116             mScheduler.correctStoredTime(UNPROVISIONED);
117 
118             // THEN next check in time should be updated
119             long expectedToRunAfterChange =
120                     TEST_NEXT_CHECK_IN_TIME_MILLIS + TEST_POSITIVE_DELTA_MILLIS;
121             assertThat(UserParameters.getNextCheckInTimeMillis(mTestApp)).isEqualTo(
122                     expectedToRunAfterChange);
123         });
124     }
125 
126 
127     @Test
correctExpectedToRunTime_retryCheckInExpected_negativeDelta_shouldUpdate()128     public void correctExpectedToRunTime_retryCheckInExpected_negativeDelta_shouldUpdate() {
129         // GIVEN retry check in is expected
130         UserParameters.setNextCheckInTimeMillis(mTestApp, TEST_NEXT_CHECK_IN_TIME_MILLIS);
131 
132         // GIVEN time change delta is negative.
133         UserParameters.setBootTimeMillis(mTestApp,
134                 TEST_CURRENT_TIME_MILLIS - TEST_NEGATIVE_DELTA_MILLIS
135                         - SystemClock.elapsedRealtime());
136 
137         runBySequentialExecutor(() -> {
138             // WHEN correct expected to run time for UNPROVISIONED state
139             mScheduler.correctStoredTime(UNPROVISIONED);
140 
141             // THEN next check in time should be updated
142             long expectedToRunAfterChange =
143                     TEST_NEXT_CHECK_IN_TIME_MILLIS + TEST_NEGATIVE_DELTA_MILLIS;
144             assertThat(UserParameters.getNextCheckInTimeMillis(mTestApp)).isEqualTo(
145                     expectedToRunAfterChange);
146         });
147     }
148 
149     @Test
correctExpectedToRunTime_resumeProvisionExpected_positiveDelta_shouldUpdate()150     public void correctExpectedToRunTime_resumeProvisionExpected_positiveDelta_shouldUpdate() {
151         // GIVEN retry check in is expected
152         UserParameters.setResumeProvisionTimeMillis(mTestApp, TEST_RESUME_PROVISION_TIME_MILLIS);
153 
154         // GIVEN time change delta is positive.
155         UserParameters.setBootTimeMillis(mTestApp,
156                 TEST_CURRENT_TIME_MILLIS - TEST_POSITIVE_DELTA_MILLIS
157                         - SystemClock.elapsedRealtime());
158 
159         runBySequentialExecutor(() -> {
160             // WHEN correct expected to run time for PROVISION_PAUSED state
161             mScheduler.correctStoredTime(PROVISION_PAUSED);
162 
163             // THEN next check in time should be updated
164             long expectedToRunAfterChange =
165                     TEST_RESUME_PROVISION_TIME_MILLIS + TEST_POSITIVE_DELTA_MILLIS;
166             assertThat(UserParameters.getResumeProvisionTimeMillis(mTestApp)).isEqualTo(
167                     expectedToRunAfterChange);
168         });
169     }
170 
171     @Test
correctExpectedToRunTime_resumeProvisionExpected_negativeDelta_shouldUpdate()172     public void correctExpectedToRunTime_resumeProvisionExpected_negativeDelta_shouldUpdate() {
173         // GIVEN retry check in is expected
174         UserParameters.setResumeProvisionTimeMillis(mTestApp, TEST_RESUME_PROVISION_TIME_MILLIS);
175 
176         // GIVEN time change delta is negative.
177         UserParameters.setBootTimeMillis(mTestApp,
178                 TEST_CURRENT_TIME_MILLIS - TEST_NEGATIVE_DELTA_MILLIS
179                         - SystemClock.elapsedRealtime());
180 
181         runBySequentialExecutor(() -> {
182             // WHEN correct expected to run time for PROVISION_PAUSED state
183             mScheduler.correctStoredTime(PROVISION_PAUSED);
184 
185             // THEN next check in time should be updated
186             long expectedToRunAfterChange =
187                     TEST_RESUME_PROVISION_TIME_MILLIS + TEST_NEGATIVE_DELTA_MILLIS;
188             assertThat(UserParameters.getResumeProvisionTimeMillis(mTestApp)).isEqualTo(
189                     expectedToRunAfterChange);
190         });
191     }
192 
193     @Test
scheduleResumeProvisionAlarm()194     public void scheduleResumeProvisionAlarm() {
195         // GIVEN no alarm is scheduled and no expected resume time
196         ShadowAlarmManager alarmManager = Shadows.shadowOf(
197                 mTestApp.getSystemService(AlarmManager.class));
198         assertThat(alarmManager.peekNextScheduledAlarm()).isNull();
199         runBySequentialExecutor(() ->
200                 assertThat(UserParameters.getResumeProvisionTimeMillis(mTestApp)).isEqualTo(0));
201 
202         // WHEN resume provision alarm is scheduled
203         mScheduler.scheduleResumeProvisionAlarm();
204 
205         // THEN correct alarm should be scheduled
206         PendingIntent actualPendingIntent = alarmManager.peekNextScheduledAlarm().operation;
207         assertThat(actualPendingIntent.isBroadcast()).isTrue();
208 
209         // THEN alarm should be scheduled at correct time
210         long actualTriggerTime = alarmManager.peekNextScheduledAlarm().triggerAtTime;
211         assertThat(actualTriggerTime).isEqualTo(
212                 SystemClock.elapsedRealtime() + PROVISION_PAUSED_MILLIS);
213 
214         // THEN expected trigger time should be stored in storage
215         runBySequentialExecutor(() -> {
216             assertThat(UserParameters.getResumeProvisionTimeMillis(mTestApp)).isEqualTo(
217                     TEST_CURRENT_TIME_MILLIS + PROVISION_PAUSED_MILLIS);
218         });
219     }
220 
221     @Test
rescheduleResumeProvisionAlarm()222     public void rescheduleResumeProvisionAlarm() {
223         // GIVEN no alarm is scheduled
224         ShadowAlarmManager alarmManager = Shadows.shadowOf(
225                 mTestApp.getSystemService(AlarmManager.class));
226         assertThat(alarmManager.peekNextScheduledAlarm()).isNull();
227 
228         // GIVEN expected resume time in storage
229         UserParameters.setResumeProvisionTimeMillis(mTestApp, TEST_RESUME_PROVISION_TIME_MILLIS);
230 
231         // WHEN resume provision alarm is rescheduled
232         runBySequentialExecutor(mScheduler::rescheduleResumeProvisionAlarmIfNeeded);
233 
234         // THEN correct alarm should be scheduled at correct time
235         PendingIntent actualPendingIntent = alarmManager.peekNextScheduledAlarm().operation;
236         assertThat(actualPendingIntent.isBroadcast()).isTrue();
237 
238 
239         long expectedTriggerTime = SystemClock.elapsedRealtime()
240                 + (TEST_RESUME_PROVISION_TIME_MILLIS - TEST_CURRENT_TIME_MILLIS);
241         assertThat(alarmManager.peekNextScheduledAlarm().triggerAtTime).isEqualTo(
242                 expectedTriggerTime);
243     }
244 
245     @Test
scheduleInitialCheckInWork()246     public void scheduleInitialCheckInWork() throws Exception {
247         // GIVEN check-in work is not scheduled
248         WorkManager workManager = WorkManager.getInstance(mTestApp);
249         assertThat(workManager.getWorkInfosForUniqueWork(
250                 DEVICE_CHECK_IN_WORK_NAME).get()).isEmpty();
251 
252         // WHEN schedule initial check-in work
253         mScheduler.scheduleInitialCheckInWork();
254 
255         // THEN check-in work should be scheduled
256         List<WorkInfo> actualWorks = Futures.getUnchecked(workManager.getWorkInfosForUniqueWork(
257                 DEVICE_CHECK_IN_WORK_NAME));
258         assertThat(actualWorks.size()).isEqualTo(1);
259         WorkInfo actualWorkInfo = actualWorks.get(0);
260         NetworkRequest networkRequest = actualWorkInfo.getConstraints().getRequiredNetworkRequest();
261         assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
262         assertThat(networkRequest.hasCapability(NET_CAPABILITY_TRUSTED)).isTrue();
263         assertThat(networkRequest.hasCapability(NET_CAPABILITY_INTERNET)).isTrue();
264         assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
265         assertThat(actualWorkInfo.getInitialDelayMillis()).isEqualTo(0);
266     }
267 
268     @Test
scheduleRetryCheckInWork()269     public void scheduleRetryCheckInWork() throws Exception {
270         // GIVEN check-in work is not scheduled
271         WorkManager workManager = WorkManager.getInstance(mTestApp);
272         assertThat(workManager.getWorkInfosForUniqueWork(
273                 DEVICE_CHECK_IN_WORK_NAME).get()).isEmpty();
274 
275         // WHEN schedule retry check-in work
276         mScheduler.scheduleRetryCheckInWork(TEST_RETRY_CHECK_IN_DELAY);
277 
278         // THEN retry check-in work should be scheduled
279         List<WorkInfo> actualWorks = Futures.getUnchecked(workManager.getWorkInfosForUniqueWork(
280                 DEVICE_CHECK_IN_WORK_NAME));
281         assertThat(actualWorks.size()).isEqualTo(1);
282         WorkInfo actualWorkInfo = actualWorks.get(0);
283         NetworkRequest networkRequest = actualWorkInfo.getConstraints().getRequiredNetworkRequest();
284         assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
285         assertThat(networkRequest.hasCapability(NET_CAPABILITY_TRUSTED)).isTrue();
286         assertThat(networkRequest.hasCapability(NET_CAPABILITY_INTERNET)).isTrue();
287         assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
288         assertThat(actualWorkInfo.getInitialDelayMillis()).isEqualTo(
289                 TEST_RETRY_CHECK_IN_DELAY.toMillis());
290 
291         // THEN expected trigger time is stored in storage
292         long expectedTriggerTime =
293                 TEST_CURRENT_TIME_MILLIS + TEST_RETRY_CHECK_IN_DELAY.toMillis();
294         runBySequentialExecutor(
295                 () -> assertThat(UserParameters.getNextCheckInTimeMillis(mTestApp)).isEqualTo(
296                         expectedTriggerTime));
297     }
298 
299     @Test
rescheduleRetryCheckInWork()300     public void rescheduleRetryCheckInWork() {
301         // GIVEN check-in work is scheduled with original delay
302         OneTimeWorkRequest request =
303                 new OneTimeWorkRequest.Builder(DeviceCheckInWorker.class)
304                         .setInitialDelay(TEST_RETRY_CHECK_IN_DELAY).build();
305         WorkManager workManager = WorkManager.getInstance(mTestApp);
306         workManager.enqueueUniqueWork(DEVICE_CHECK_IN_WORK_NAME,
307                 ExistingWorkPolicy.REPLACE,
308                 request);
309 
310         // GIVEN expected trigger time
311         UserParameters.setNextCheckInTimeMillis(mTestApp, TEST_NEXT_CHECK_IN_TIME_MILLIS);
312 
313         // WHEN reschedule retry check-in work
314         runBySequentialExecutor(mScheduler::rescheduleRetryCheckInWork);
315 
316         // THEN retry check-in work should be scheduled with correct delay
317         List<WorkInfo> actualWorks = Futures.getUnchecked(workManager.getWorkInfosForUniqueWork(
318                 DEVICE_CHECK_IN_WORK_NAME));
319         assertThat(actualWorks.size()).isEqualTo(1);
320         WorkInfo actualWorkInfo = actualWorks.get(0);
321         NetworkRequest networkRequest = actualWorkInfo.getConstraints().getRequiredNetworkRequest();
322         assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
323         assertThat(networkRequest.hasCapability(NET_CAPABILITY_TRUSTED)).isTrue();
324         assertThat(networkRequest.hasCapability(NET_CAPABILITY_INTERNET)).isTrue();
325         assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
326 
327         long expectedDelay = TEST_NEXT_CHECK_IN_TIME_MILLIS - TEST_CURRENT_TIME_MILLIS;
328         assertThat(actualWorkInfo.getInitialDelayMillis()).isEqualTo(expectedDelay);
329     }
330 
331     @Test
maybeScheduleInitialCheckIn_needInitialCheckIn_enqueuesWorker()332     public void maybeScheduleInitialCheckIn_needInitialCheckIn_enqueuesWorker() throws Exception {
333         // Make sure the initial state is "initial check in needed"
334         runBySequentialExecutor(
335                 () -> assertThat(UserParameters.needInitialCheckIn(mTestApp)).isTrue());
336 
337         // GIVEN check-in work is not scheduled
338         WorkManager workManager = WorkManager.getInstance(mTestApp);
339         assertThat(workManager.getWorkInfosForUniqueWork(
340                 DEVICE_CHECK_IN_WORK_NAME).get()).isEmpty();
341 
342         // WHEN maybe schedule initial check-in work
343         mScheduler.maybeScheduleInitialCheckIn().get();
344 
345         // THEN check-in work should be scheduled
346         assertThat(workManager.getWorkInfosForUniqueWork(
347                 DEVICE_CHECK_IN_WORK_NAME).get()).isNotEmpty();
348     }
349 
350     @Test
maybeScheduleInitialCheckIn_NoNeedInitialCheckIn_noWorker()351     public void maybeScheduleInitialCheckIn_NoNeedInitialCheckIn_noWorker() throws Exception {
352         // GIVEN initial check in marked as scheduled
353         UserParameters.initialCheckInScheduled(mTestApp);
354 
355         // GIVEN check-in work is not scheduled
356         WorkManager workManager = WorkManager.getInstance(mTestApp);
357         assertThat(workManager.getWorkInfosForUniqueWork(
358                 DEVICE_CHECK_IN_WORK_NAME).get()).isEmpty();
359 
360         // WHEN maybe schedule initial check-in work
361         mScheduler.maybeScheduleInitialCheckIn().get();
362 
363         // THEN check-in work should not be scheduled
364         assertThat(workManager.getWorkInfosForUniqueWork(
365                 DEVICE_CHECK_IN_WORK_NAME).get()).isEmpty();
366     }
367 
368     @Test
maybeScheduleInitialCheckIn_noNeedCheckIn_reschedule()369     public void maybeScheduleInitialCheckIn_noNeedCheckIn_reschedule() throws Exception {
370         // GIVEN check-in work is scheduled with original delay
371         OneTimeWorkRequest request =
372                 new OneTimeWorkRequest.Builder(DeviceCheckInWorker.class)
373                         .setInitialDelay(TEST_RETRY_CHECK_IN_DELAY).build();
374         WorkManager workManager = WorkManager.getInstance(mTestApp);
375         workManager.enqueueUniqueWork(DEVICE_CHECK_IN_WORK_NAME,
376                 ExistingWorkPolicy.REPLACE,
377                 request);
378 
379         // GIVEN initial check in marked as scheduled
380         UserParameters.initialCheckInScheduled(mTestApp);
381 
382         // GIVEN expected trigger time
383         UserParameters.setNextCheckInTimeMillis(mTestApp, TEST_NEXT_CHECK_IN_TIME_MILLIS);
384 
385         // WHEN maybe schedule initial check-in work
386         mScheduler.maybeScheduleInitialCheckIn().get();
387 
388         // THEN retry check-in work should be scheduled with correct delay
389         List<WorkInfo> actualWorks = Futures.getUnchecked(workManager.getWorkInfosForUniqueWork(
390                 DEVICE_CHECK_IN_WORK_NAME));
391         assertThat(actualWorks.size()).isEqualTo(1);
392         WorkInfo actualWorkInfo = actualWorks.get(0);
393         NetworkRequest networkRequest = actualWorkInfo.getConstraints().getRequiredNetworkRequest();
394         assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
395         assertThat(networkRequest.hasCapability(NET_CAPABILITY_TRUSTED)).isTrue();
396         assertThat(networkRequest.hasCapability(NET_CAPABILITY_INTERNET)).isTrue();
397         assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
398 
399         long expectedDelay = TEST_NEXT_CHECK_IN_TIME_MILLIS - TEST_CURRENT_TIME_MILLIS;
400         assertThat(actualWorkInfo.getInitialDelayMillis()).isEqualTo(expectedDelay);
401     }
402 
403     @Test
scheduleNextProvisionFailedStepAlarm_initialStep_setsAlarm()404     public void scheduleNextProvisionFailedStepAlarm_initialStep_setsAlarm() {
405         // GIVEN no alarm is scheduled
406         ShadowAlarmManager alarmManager = Shadows.shadowOf(
407                 mTestApp.getSystemService(AlarmManager.class));
408         assertThat(alarmManager.peekNextScheduledAlarm()).isNull();
409 
410         // GIVEN no existing timestamp
411         runBySequentialExecutor(() -> assertThat(
412                 UserParameters.getNextProvisionFailedStepTimeMills(mTestApp)).isEqualTo(0));
413 
414         // WHEN schedule next provision failed step alarm
415         runBySequentialExecutor(() -> mScheduler.scheduleNextProvisionFailedStepAlarm());
416 
417         // THEN correct alarm should be scheduled
418         PendingIntent actualPendingIntent = alarmManager.peekNextScheduledAlarm().operation;
419         assertThat(actualPendingIntent.isBroadcast()).isTrue();
420 
421         // THEN alarm should be scheduled at correct time
422         long actualTriggerTime = alarmManager.peekNextScheduledAlarm().triggerAtTime;
423         assertThat(actualTriggerTime).isEqualTo(
424                 SystemClock.elapsedRealtime() + PROVISION_STATE_REPORT_INTERVAL_MILLIS);
425 
426         // THEN expected trigger time should be stored in storage
427         runBySequentialExecutor(() -> assertThat(
428                 UserParameters.getNextProvisionFailedStepTimeMills(mTestApp)).isEqualTo(
429                 TEST_CURRENT_TIME_MILLIS + PROVISION_STATE_REPORT_INTERVAL_MILLIS));
430     }
431 
432     @Test
scheduleNextProvisionFailedStepAlarm_followUpStep_setsAlarm()433     public void scheduleNextProvisionFailedStepAlarm_followUpStep_setsAlarm() {
434         // GIVEN no alarm is scheduled
435         ShadowAlarmManager alarmManager = Shadows.shadowOf(
436                 mTestApp.getSystemService(AlarmManager.class));
437         assertThat(alarmManager.peekNextScheduledAlarm()).isNull();
438 
439         // GIVEN timestamp exists for last performed step.
440         UserParameters.setNextProvisionFailedStepTimeMills(mTestApp,
441                 TEST_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS);
442 
443         // WHEN schedule next provision failed step alarm
444         runBySequentialExecutor(() -> mScheduler.scheduleNextProvisionFailedStepAlarm());
445 
446         // THEN correct alarm should be scheduled
447         PendingIntent actualPendingIntent = alarmManager.peekNextScheduledAlarm().operation;
448         assertThat(actualPendingIntent.isBroadcast()).isTrue();
449 
450         // THEN alarm should be scheduled at correct time
451         long actualTriggerTime = alarmManager.peekNextScheduledAlarm().triggerAtTime;
452         long expectedTriggerTime = TEST_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS
453                 + PROVISION_STATE_REPORT_INTERVAL_MILLIS - TEST_CURRENT_TIME_MILLIS
454                 + SystemClock.elapsedRealtime();
455         assertThat(actualTriggerTime).isEqualTo(expectedTriggerTime);
456 
457 
458         // THEN expected trigger time should be stored in storage
459         runBySequentialExecutor(() -> assertThat(
460                 UserParameters.getNextProvisionFailedStepTimeMills(mTestApp)).isEqualTo(
461                 TEST_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS
462                         + PROVISION_STATE_REPORT_INTERVAL_MILLIS));
463     }
464 
465     @Test
scheduleResetDeviceAlarm()466     public void scheduleResetDeviceAlarm() {
467         // GIVEN no alarm is scheduled
468         ShadowAlarmManager alarmManager = Shadows.shadowOf(
469                 mTestApp.getSystemService(AlarmManager.class));
470         assertThat(alarmManager.peekNextScheduledAlarm()).isNull();
471         runBySequentialExecutor(
472                 () -> assertThat(UserParameters.getResetDeviceTimeMillis(mTestApp)).isEqualTo(0));
473 
474         // WHEN schedule reset device alarm
475         mScheduler.scheduleResetDeviceAlarm();
476 
477         // THEN correct alarm should be scheduled
478         PendingIntent actualPendingIntent = alarmManager.peekNextScheduledAlarm().operation;
479         assertThat(actualPendingIntent.isBroadcast()).isTrue();
480 
481         // THEN alarm should be scheduled at correct time
482         long actualTriggerTime = alarmManager.peekNextScheduledAlarm().triggerAtTime;
483         assertThat(actualTriggerTime).isEqualTo(
484                 SystemClock.elapsedRealtime() + RESET_DEVICE_MILLIS);
485 
486         // THEN expected trigger time should be stored in storage
487         runBySequentialExecutor(
488                 () -> assertThat(UserParameters.getResetDeviceTimeMillis(mTestApp)).isEqualTo(
489                         TEST_CURRENT_TIME_MILLIS + RESET_DEVICE_MILLIS));
490     }
491 
492     @Test
rescheduleNextProvisionFailedStepAlarm()493     public void rescheduleNextProvisionFailedStepAlarm() {
494         // GIVEN no alarm is scheduled
495         ShadowAlarmManager alarmManager = Shadows.shadowOf(
496                 mTestApp.getSystemService(AlarmManager.class));
497         assertThat(alarmManager.peekNextScheduledAlarm()).isNull();
498 
499         // GIVEN expected time in storage
500         UserParameters.setNextProvisionFailedStepTimeMills(mTestApp,
501                 TEST_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS);
502 
503         // WHEN next provision failed step alarm is rescheduled
504         runBySequentialExecutor(mScheduler::rescheduleNextProvisionFailedStepAlarmIfNeeded);
505 
506         // THEN correct alarm should be scheduled at correct time
507         PendingIntent actualPendingIntent = alarmManager.peekNextScheduledAlarm().operation;
508         assertThat(actualPendingIntent.isBroadcast()).isTrue();
509 
510         long expectedTriggerTime = SystemClock.elapsedRealtime()
511                 + (TEST_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS - TEST_CURRENT_TIME_MILLIS);
512         assertThat(alarmManager.peekNextScheduledAlarm().triggerAtTime).isEqualTo(
513                 expectedTriggerTime);
514     }
515 
516     @Test
rescheduleResetDeviceAlarm()517     public void rescheduleResetDeviceAlarm() {
518         // GIVEN no alarm is scheduled
519         ShadowAlarmManager alarmManager = Shadows.shadowOf(
520                 mTestApp.getSystemService(AlarmManager.class));
521         assertThat(alarmManager.peekNextScheduledAlarm()).isNull();
522 
523         // GIVEN expected reset device time in storage
524         UserParameters.setResetDeviceTimeMillis(mTestApp, TEST_RESET_DEVICE_TIME_MILLIS);
525 
526         // WHEN reset device alarm is rescheduled
527         runBySequentialExecutor(mScheduler::rescheduleResetDeviceAlarmIfNeeded);
528 
529         // THEN correct alarm should be scheduled at correct time
530         PendingIntent actualPendingIntent = alarmManager.peekNextScheduledAlarm().operation;
531         assertThat(actualPendingIntent.isBroadcast()).isTrue();
532 
533         long expectedTriggerTime = SystemClock.elapsedRealtime()
534                 + (TEST_RESET_DEVICE_TIME_MILLIS - TEST_CURRENT_TIME_MILLIS);
535         assertThat(alarmManager.peekNextScheduledAlarm().triggerAtTime).isEqualTo(
536                 expectedTriggerTime);
537     }
538 
runBySequentialExecutor(Runnable runnable)539     private static void runBySequentialExecutor(Runnable runnable) {
540         Futures.getUnchecked(
541                 Futures.submit(runnable, ThreadUtils.getSequentialSchedulerExecutor()));
542     }
543 }
544