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.scheduling;
18 
19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
27 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
28 
29 import static com.google.common.truth.Truth.assertThat;
30 
31 import static org.mockito.ArgumentMatchers.eq;
32 import static org.mockito.Mockito.atLeastOnce;
33 import static org.mockito.Mockito.spy;
34 import static org.mockito.Mockito.times;
35 
36 import android.Manifest;
37 import android.app.ActivityManager;
38 import android.app.AlarmManager;
39 import android.content.BroadcastReceiver;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.pm.PackageManager;
44 import android.net.TetheringManager;
45 import android.os.Binder;
46 import android.os.Bundle;
47 import android.os.Handler;
48 import android.os.IBinder;
49 import android.os.PowerManager;
50 import android.os.RemoteCallback;
51 import android.os.UserHandle;
52 import android.provider.DeviceConfig;
53 import android.scheduling.IRequestRebootReadinessStatusListener;
54 import android.scheduling.RebootReadinessManager;
55 
56 import androidx.test.InstrumentationRegistry;
57 
58 import com.android.dx.mockito.inline.extended.ExtendedMockito;
59 
60 import org.junit.After;
61 import org.junit.Before;
62 import org.junit.Test;
63 import org.mockito.Answers;
64 import org.mockito.ArgumentCaptor;
65 import org.mockito.Captor;
66 import org.mockito.Mock;
67 import org.mockito.MockitoSession;
68 import org.mockito.quality.Strictness;
69 
70 import java.io.BufferedReader;
71 import java.io.File;
72 import java.io.FileDescriptor;
73 import java.io.FileReader;
74 import java.io.PrintWriter;
75 import java.util.ArrayList;
76 import java.util.List;
77 import java.util.concurrent.CountDownLatch;
78 import java.util.concurrent.TimeUnit;
79 
80 
81 /**
82  * Tests for {@link com.android.server.scheduling.RebootReadinessManagerService}.
83  */
84 public class RebootReadinessUnitTest {
85 
86     private Context mMockContext;
87 
88     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
89     private ActivityManager mActivityManager;
90 
91     private AlarmManager mAlarmManager;
92 
93     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
94     private PowerManager mPowerManager;
95 
96     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
97     private TetheringManager mTetheringManager;
98 
99     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
100     private PackageManager mPackageManager;
101 
102     @Captor
103     private ArgumentCaptor<Intent> mIntentArgumentCaptor;
104 
105     @Captor
106     private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
107 
108     @Captor
109     private ArgumentCaptor<IntentFilter> mIntentFilters;
110 
111     @Captor
112     private ArgumentCaptor<Integer> mIntCaptor;
113 
114     @Captor
115     private ArgumentCaptor<Long> mLongCaptor;
116 
117 
118     private MockitoSession mSession;
119 
120     private RebootReadinessManagerService mService;
121     private RebootReadinessLogger mLogger;
122 
123     private static final String TEST_PACKAGE = "test.package";
124     private static final String TEST_PACKAGE2 = "test.package2";
125 
126     private static final String PROPERTY_ACTIVE_POLLING_INTERVAL_MS = "active_polling_interval_ms";
127     private static final String PROPERTY_DISABLE_INTERACTIVITY_CHECK =
128             "disable_interactivity_check";
129     private static final String PROPERTY_INTERACTIVITY_THRESHOLD_MS = "interactivity_threshold_ms";
130     private static final String PROPERTY_DISABLE_APP_ACTIVITY_CHECK = "disable_app_activity_check";
131     private static final String PROPERTY_DISABLE_SUBSYSTEMS_CHECK = "disable_subsystems_check";
132     private static final String PROPERTY_ALARM_CLOCK_THRESHOLD_MS = "alarm_clock_threshold_ms";
133     private static final String PROPERTY_LOGGING_BLOCKING_ENTITY_THRESHOLD_MS =
134             "logging_blocking_entity_threshold_ms";
135 
136     // The prefix "TESTCOMPONENT" is used to allow testing when subsystem checks are disabled.
137     private static final String COMPONENT_NAME = "TESTCOMPONENT_component";
138 
139     // Small delay to allow DeviceConfig updates to propagate.
140     private static final int DEVICE_CONFIG_DELAY = 1000;
141 
142     // Small delay to allow reboot readiness state to change.
143     private static final int STATE_CHANGE_DELAY = 5000;
144 
145     @Before
setup()146     public void setup() {
147         mSession =
148                 ExtendedMockito.mockitoSession().initMocks(
149                         this)
150                         .strictness(Strictness.LENIENT)
151                         .spyStatic(SchedulingStatsLog.class)
152                         .startMocking();
153         mMockContext = spy(InstrumentationRegistry.getContext());
154         mAlarmManager = spy(mMockContext.getSystemService(AlarmManager.class));
155         doNothing().when(mMockContext).enforceCallingPermission(anyString(), anyString());
156         doNothing().when(mMockContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class),
157                 anyString());
158         doReturn(PackageManager.PERMISSION_GRANTED).when(mMockContext)
159                 .checkCallingOrSelfPermission(anyString());
160 
161         doReturn(mActivityManager).when(mMockContext).getSystemService(eq(ActivityManager.class));
162         doReturn(mAlarmManager).when(mMockContext).getSystemService(eq(AlarmManager.class));
163         doCallRealMethod().when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), any(
164                 AlarmManager.OnAlarmListener.class), any(Handler.class));
165         doReturn(mPowerManager).when(mMockContext).getSystemService(eq(PowerManager.class));
166         when(mPowerManager.isInteractive()).thenReturn(true);
167         doReturn(mTetheringManager).when(mMockContext).getSystemService(eq(TetheringManager.class));
168         when(mAlarmManager.getNextAlarmClock()).thenReturn(null);
169         when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
170         InstrumentationRegistry.getInstrumentation().getUiAutomation()
171                 .adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG,
172                         Manifest.permission.READ_DEVICE_CONFIG,
173                         Manifest.permission.SCHEDULE_EXACT_ALARM,
174                         Manifest.permission.ACCESS_NETWORK_STATE);
175         initializeDeviceConfig();
176         File testDir = new File(mMockContext.getFilesDir(), "reboot-readiness");
177         mLogger = spy(new RebootReadinessLogger(testDir, mMockContext));
178         mService = new RebootReadinessManagerService(mMockContext, mLogger);
179     }
180 
initializeDeviceConfig()181     private void initializeDeviceConfig()  {
182         // Checks may only be scheduled every 5000ms due to AlarmManager scheduling restrictions.
183         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
184                 PROPERTY_ACTIVE_POLLING_INTERVAL_MS, "5000", false);
185 
186         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
187                 PROPERTY_DISABLE_INTERACTIVITY_CHECK, "true", false);
188         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
189                 PROPERTY_DISABLE_APP_ACTIVITY_CHECK, "true", false);
190         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
191                 PROPERTY_DISABLE_SUBSYSTEMS_CHECK, "true", false);
192         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
193                 PROPERTY_ALARM_CLOCK_THRESHOLD_MS, "500000", false);
194         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
195                 PROPERTY_INTERACTIVITY_THRESHOLD_MS, "1000", false);
196         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
197                 PROPERTY_LOGGING_BLOCKING_ENTITY_THRESHOLD_MS, "1000", false);
198     }
199 
200     @After
teardown()201     public void teardown() {
202         mService.cancelPendingReboot(TEST_PACKAGE);
203         mService.cancelPendingReboot(TEST_PACKAGE2);
204         mSession.finishMocking();
205     }
206 
207     /**
208      * Test that calling packages are tracked correctly when reboot readiness checks are started
209      * or stopped.
210      */
211     @Test
testCallingPackages()212     public void testCallingPackages() {
213         mService.markRebootPending(TEST_PACKAGE);
214         assertThat(mService.getCallingPackages().valueAt(0)).contains(TEST_PACKAGE);
215         mService.cancelPendingReboot(TEST_PACKAGE);
216         assertThat(mService.getCallingPackages().size()).isEqualTo(0);
217     }
218 
219     /**
220      * Test that a registered listener is called as part of the reboot readiness checks.
221      */
222     @Test
testRegisteredListenerIsCalled()223     public void testRegisteredListenerIsCalled() throws Exception {
224         CountDownLatch latch = new CountDownLatch(1);
225         mService.addRequestRebootReadinessStatusListener(getStatusListener(latch));
226         mService.markRebootPending(TEST_PACKAGE);
227         assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue();
228     }
229 
230     /** Test that the correct intents are sent when the reboot readiness state changes. */
231     @Test
testCorrectIntentsSent()232     public void testCorrectIntentsSent() throws Exception {
233         mService.markRebootPending(TEST_PACKAGE);
234         Thread.sleep(STATE_CHANGE_DELAY);
235         assertThat(mService.isReadyToReboot()).isTrue();
236 
237         // Make device interactive, and therefore not ready to reboot.
238         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
239                 PROPERTY_DISABLE_INTERACTIVITY_CHECK, "false", false);
240         Thread.sleep(DEVICE_CONFIG_DELAY);
241         setScreenState(true);
242         Thread.sleep(STATE_CHANGE_DELAY);
243         assertThat(mService.isReadyToReboot()).isFalse();
244 
245         // 2 broadcasts should have been sent: one when the device became ready to reboot, and
246         // another when it became no longer ready to reboot.
247         verify(mMockContext, times(2)).sendBroadcastAsUser(mIntentArgumentCaptor.capture(),
248                 any(UserHandle.class), anyString());
249         List<Intent> intents = mIntentArgumentCaptor.getAllValues();
250 
251         Intent readyIntent = intents.get(0);
252         Intent notReadyIntent = intents.get(1);
253         boolean sentExtra;
254 
255         assertThat(readyIntent.getAction()).isEqualTo(RebootReadinessManager.ACTION_REBOOT_READY);
256         assertThat(readyIntent.getPackage()).isEqualTo(TEST_PACKAGE);
257         sentExtra = readyIntent.getBooleanExtra(
258                 RebootReadinessManager.EXTRA_IS_READY_TO_REBOOT, false);
259         assertThat(sentExtra).isTrue();
260 
261         assertThat(notReadyIntent.getAction()).isEqualTo(
262                 RebootReadinessManager.ACTION_REBOOT_READY);
263         assertThat(notReadyIntent.getPackage()).isEqualTo(TEST_PACKAGE);
264         sentExtra = notReadyIntent.getBooleanExtra(
265                 RebootReadinessManager.EXTRA_IS_READY_TO_REBOOT, false);
266         assertThat(sentExtra).isFalse();
267     }
268 
269     /** Test that a second client will receive an immediate broadcast when they register. */
270     @Test
testSecondClientReceivesBroadcastImmediately()271     public void testSecondClientReceivesBroadcastImmediately() throws Exception {
272         mService.markRebootPending(TEST_PACKAGE);
273         Thread.sleep(STATE_CHANGE_DELAY);
274         assertThat(mService.isReadyToReboot()).isTrue();
275 
276         mService.markRebootPending(TEST_PACKAGE2);
277         verify(mMockContext, times(2)).sendBroadcastAsUser(mIntentArgumentCaptor.capture(),
278                 any(UserHandle.class), anyString());
279         List<Intent> intents = mIntentArgumentCaptor.getAllValues();
280 
281         Intent secondIntent = intents.get(1);
282 
283         assertThat(secondIntent.getPackage()).isEqualTo(TEST_PACKAGE2);
284         assertThat(secondIntent.getAction()).isEqualTo(RebootReadinessManager.ACTION_REBOOT_READY);
285         boolean sentExtra = secondIntent.getBooleanExtra(
286                 RebootReadinessManager.EXTRA_IS_READY_TO_REBOOT, false);
287         assertThat(sentExtra).isTrue();
288     }
289 
290     @Test
testWritingAfterRebootReadyBroadcast()291     public void testWritingAfterRebootReadyBroadcast() throws Exception {
292         mService.markRebootPending(TEST_PACKAGE);
293         Thread.sleep(STATE_CHANGE_DELAY);
294         assertThat(mService.isReadyToReboot()).isTrue();
295         verify(mLogger, times(1)).writeAfterRebootReadyBroadcast(
296                 anyLong(), anyLong(), eq(0), eq(0), eq(0));
297 
298         // Make device interactive and allow the device to become not reboot-ready for a while. Then
299         // check that the fact that interactivity has blocked the reboot is noted.
300         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
301                 PROPERTY_DISABLE_INTERACTIVITY_CHECK, "false", false);
302         Thread.sleep(DEVICE_CONFIG_DELAY);
303         setScreenState(true);
304         Thread.sleep(STATE_CHANGE_DELAY);
305 
306         setScreenState(false);
307         Thread.sleep(STATE_CHANGE_DELAY);
308         verify(mLogger, times(2)).writeAfterRebootReadyBroadcast(
309                 anyLong(), anyLong(), mIntCaptor.capture(), eq(0), eq(0));
310         assertThat(mIntCaptor.getAllValues().get(1)).isAtLeast(1);
311 
312 
313     }
314 
315     /**
316      * Test that the device interactivity checks return the correct result based on the screen
317      * state.
318      */
319     @Test
testInteractivityChecks()320     public void testInteractivityChecks() throws Exception {
321         assertThat(mService.checkDeviceInteractivity()).isFalse();
322 
323         setScreenState(false);
324         // Screen needs to be off for 1000ms to be ready to reboot
325         Thread.sleep(1000);
326         assertThat(mService.checkDeviceInteractivity()).isTrue();
327 
328         setScreenState(true);
329         assertThat(mService.checkDeviceInteractivity()).isFalse();
330     }
331 
332     /** Test that foreground services block the reboot. */
333     @Test
testAppActivityChecks()334     public void testAppActivityChecks() {
335         List<ActivityManager.RunningServiceInfo> runningServicesList = new ArrayList<>();
336         when(mActivityManager.getRunningServices(anyInt())).thenReturn(runningServicesList);
337         assertThat(mService.checkBackgroundAppActivity()).isTrue();
338 
339         // Ensure that non-foreground services do not block the reboot
340         ActivityManager.RunningServiceInfo nonForeground = new ActivityManager.RunningServiceInfo();
341         nonForeground.foreground = false;
342         runningServicesList.add(nonForeground);
343         assertThat(mService.checkBackgroundAppActivity()).isTrue();
344 
345         // Ensure that foreground services do block the reboot
346         ActivityManager.RunningServiceInfo foreground = new ActivityManager.RunningServiceInfo();
347         foreground.foreground = true;
348         runningServicesList.add(foreground);
349         assertThat(mService.checkBackgroundAppActivity()).isFalse();
350     }
351 
352     /**
353      * Test that pending alarm clocks block the reboot if they are within a given threshold from
354      * the current time.
355      */
356     @Test
testAlarmClockBlocksReboot()357     public void testAlarmClockBlocksReboot() throws Exception {
358         long threshold = TimeUnit.MINUTES.toMillis(15);
359         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
360                 PROPERTY_DISABLE_SUBSYSTEMS_CHECK, "false", false);
361         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
362                 PROPERTY_ALARM_CLOCK_THRESHOLD_MS, Long.toString(threshold), false);
363         Thread.sleep(DEVICE_CONFIG_DELAY);
364         assertThat(mService.checkSystemComponentsState()).isTrue();
365 
366         // Alarm is within the threshold, therefore the reboot should be blocked.
367         AlarmManager.AlarmClockInfo alarm = new AlarmManager.AlarmClockInfo(
368                 System.currentTimeMillis() + threshold / 2, null);
369         when(mAlarmManager.getNextAlarmClock()).thenReturn(alarm);
370         assertThat(mService.checkSystemComponentsState()).isFalse();
371 
372         // Alarm is outside the threshold, therefore the reboot should not be blocked.
373         alarm = new AlarmManager.AlarmClockInfo(System.currentTimeMillis() + threshold * 2, null);
374         when(mAlarmManager.getNextAlarmClock()).thenReturn(alarm);
375         assertThat(mService.checkSystemComponentsState()).isTrue();
376 
377     }
378 
379     /**
380      * Test that metrics are logged for long-blocking subsystems.
381      */
382     @Test
testLongSubsystemBlockingReported()383     public void testLongSubsystemBlockingReported() throws Exception {
384         CountDownLatch latch = new CountDownLatch(2);
385         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
386                 PROPERTY_DISABLE_SUBSYSTEMS_CHECK, "false", false);
387         Thread.sleep(DEVICE_CONFIG_DELAY);
388         mService.addRequestRebootReadinessStatusListener(getStatusListener(latch));
389         mService.markRebootPending(TEST_PACKAGE);
390 
391         assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
392         verify(() -> SchedulingStatsLog.write(eq(SchedulingStatsLog.LONG_REBOOT_BLOCKING_REPORTED),
393                 eq(SchedulingStatsLog
394                         .LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__SYSTEM_COMPONENT),
395                 eq(COMPONENT_NAME), anyInt()));
396     }
397 
398     /**
399      * Test that metrics are logged for long-blocking apps.
400      */
401     @Test
testLongAppBlockingReported()402     public void testLongAppBlockingReported() throws Exception {
403         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
404                 PROPERTY_DISABLE_APP_ACTIVITY_CHECK, "false", false);
405         Thread.sleep(DEVICE_CONFIG_DELAY);
406         List<ActivityManager.RunningServiceInfo> runningServicesList = new ArrayList<>();
407         ActivityManager.RunningServiceInfo service = new ActivityManager.RunningServiceInfo();
408         service.foreground = true;
409         service.uid = 1000;
410         runningServicesList.add(service);
411         when(mActivityManager.getRunningServices(anyInt())).thenReturn(runningServicesList);
412         mService.markRebootPending(TEST_PACKAGE);
413 
414         // Allow the app to block the reboot for a small amount of time.
415         Thread.sleep(6000);
416         verify(() -> SchedulingStatsLog.write(eq(SchedulingStatsLog.LONG_REBOOT_BLOCKING_REPORTED),
417                 eq(SchedulingStatsLog.LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__APP_UID),
418                 anyString(), eq(1000)), atLeastOnce());
419     }
420 
421     /**
422      * Test that logging information is correctly written and read before and after the reboot.
423      */
424     @Test
testReadingAndWritingUnattendedReboot()425     public void testReadingAndWritingUnattendedReboot() {
426         mLogger.writeAfterRebootReadyBroadcast(1000, 2000, 0, 0, 0);
427         mLogger.readMetricsPostReboot();
428         mLogger.writePostRebootMetrics();
429         verify(() -> SchedulingStatsLog.write(eq(SchedulingStatsLog.UNATTENDED_REBOOT_OCCURRED),
430                 eq(1000L), anyLong(), eq(0), eq(0), eq(0), anyLong()));
431     }
432 
433     /**
434      * Test that logging information is correctly written in the case that the device becomes not
435      * ready to reboot.
436      */
437     @Test
testMetricsLoggedWhenNotReadyToReboot()438     public void testMetricsLoggedWhenNotReadyToReboot() throws Exception {
439         mService.markRebootPending(TEST_PACKAGE);
440         Thread.sleep(STATE_CHANGE_DELAY);
441         assertThat(mService.isReadyToReboot()).isTrue();
442 
443         // Make device interactive and allow the device to become not reboot-ready for a while.
444         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
445                 PROPERTY_DISABLE_INTERACTIVITY_CHECK, "false", false);
446         Thread.sleep(DEVICE_CONFIG_DELAY);
447         setScreenState(true);
448         Thread.sleep(STATE_CHANGE_DELAY);
449         assertThat(mService.isReadyToReboot()).isFalse();
450 
451         // The device has become not ready to reboot, therefore no metrics should be logged.
452         mLogger.readMetricsPostReboot();
453         mLogger.writePostRebootMetrics();
454 
455         // Verify that the time_to_next_interaction_millis field is correctly populated.
456         verify(() -> SchedulingStatsLog.write(eq(SchedulingStatsLog.UNATTENDED_REBOOT_OCCURRED),
457                 anyLong(), anyLong(), anyInt(), anyInt(),
458                 anyInt(), mLongCaptor.capture()), times(1));
459         assertThat(mLongCaptor.getValue()).isGreaterThan(STATE_CHANGE_DELAY + DEVICE_CONFIG_DELAY);
460         assertThat(mLongCaptor.getValue()).isLessThan(TimeUnit.MINUTES.toMillis(1));
461     }
462 
463     /**
464      * Test that a pending alarm clock will cause the reboot readiness state to be checked before
465      * the alarm triggers.
466      */
467     @Test
testAlarmClockCheckWhileReadyToReboot()468     public void testAlarmClockCheckWhileReadyToReboot() throws Exception {
469         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
470                 PROPERTY_DISABLE_SUBSYSTEMS_CHECK, "false", false);
471         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
472                 PROPERTY_ALARM_CLOCK_THRESHOLD_MS, Long.toString(5000), false);
473         Thread.sleep(DEVICE_CONFIG_DELAY);
474 
475         // Alarm clock is scheduled for 6 seconds from now. The device will initially be ready
476         // to reboot, but another check will be scheduled before the alarm triggers, causing the
477         // device to no longer be ready to reboot.
478         AlarmManager.AlarmClockInfo alarm = new AlarmManager.AlarmClockInfo(
479                 System.currentTimeMillis() + 6000, null);
480         when(mAlarmManager.getNextAlarmClock()).thenReturn(alarm);
481         mService.markRebootPending(TEST_PACKAGE);
482         assertThat(mService.isReadyToReboot()).isTrue();
483         Thread.sleep(STATE_CHANGE_DELAY);
484         assertThat(mService.isReadyToReboot()).isFalse();
485     }
486 
487     /**
488      * Test that reboot readiness checks happen for the duration supplied by the timeout flag.
489      */
490     @Test
testStateChangeListenerShellCommand()491     public void testStateChangeListenerShellCommand() throws Exception {
492         Long startTime = System.currentTimeMillis();
493         executeShellCommand("start-readiness-checks --timeout-secs 10");
494         Long endTime = System.currentTimeMillis();
495         assertThat(endTime - startTime).isGreaterThan(10000);
496     }
497 
498     /**
499      * Test that the check-interactivity-state shell command prints the correct output.
500      */
501     @Test
testInteractivityShellCommand()502     public void testInteractivityShellCommand() throws Exception {
503         setScreenState(false);
504         String output = executeShellCommand(
505                 "check-interactivity-state --interactivity-threshold-ms 0");
506         assertThat(output).contains("true");
507 
508         setScreenState(true);
509         output = executeShellCommand("check-interactivity-state --interactivity-threshold-ms 0");
510         assertThat(output).contains("false");
511     }
512 
513     /**
514      * Test that the check-subsystems-state shell command prints the correct output.
515      */
516     @Test
testSubsystemsShellCommand()517     public void testSubsystemsShellCommand() throws Exception {
518         String output = executeShellCommand("check-subsystems-state");
519         assertThat(output).contains("true");
520 
521         mService.addRequestRebootReadinessStatusListener(getStatusListener(new CountDownLatch(1)));
522         output = executeShellCommand("check-subsystems-state --list-blocking");
523         assertThat(output).contains("false");
524         assertThat(output).contains("Blocking subsystem: " + COMPONENT_NAME);
525     }
526 
527     /**
528      * Test that the check-app-activity-state shell command prints the correct output.
529      */
530     @Test
testAppActivityShellCommand()531     public void testAppActivityShellCommand() throws Exception {
532         List<ActivityManager.RunningServiceInfo> runningServicesList = new ArrayList<>();
533         when(mActivityManager.getRunningServices(anyInt())).thenReturn(runningServicesList);
534         String output = executeShellCommand("check-app-activity-state --list-blocking");
535         assertThat(output).contains("true");
536 
537         ActivityManager.RunningServiceInfo service = new ActivityManager.RunningServiceInfo();
538         service.foreground = true;
539         service.uid = 1234;
540         runningServicesList.add(service);
541 
542         doReturn(new String[]{"test.package"}).when(mPackageManager).getPackagesForUid(anyInt());
543         output = executeShellCommand("check-app-activity-state --list-blocking");
544         assertThat(output).contains("false");
545         assertThat(output).containsMatch("Blocking uid: 1234.*test.package");
546     }
547 
548     /**
549      * Test that DeviceConfig values may be tuned by the shell command interface.
550      */
551     @Test
testShellCommandDeviceConfig()552     public void testShellCommandDeviceConfig() throws Exception {
553         executeShellCommand("check-interactivity-state --polling-interval-ms 1234"
554                 + " --interactivity-threshold-ms 2468 --disable-interactivity-checks"
555                 + " --disable-subsystem-checks --disable-app-activity-checks");
556         assertThat(DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_REBOOT_READINESS,
557                 PROPERTY_DISABLE_INTERACTIVITY_CHECK, false)).isTrue();
558         assertThat(DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_REBOOT_READINESS,
559                 PROPERTY_DISABLE_SUBSYSTEMS_CHECK, false)).isTrue();
560         assertThat(DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_REBOOT_READINESS,
561                 PROPERTY_DISABLE_APP_ACTIVITY_CHECK, false)).isTrue();
562         assertThat(DeviceConfig.getLong(DeviceConfig.NAMESPACE_REBOOT_READINESS,
563                 PROPERTY_ACTIVE_POLLING_INTERVAL_MS, 0)).isEqualTo(1234);
564         assertThat(DeviceConfig.getLong(DeviceConfig.NAMESPACE_REBOOT_READINESS,
565                 PROPERTY_INTERACTIVITY_THRESHOLD_MS, 0)).isEqualTo(2468);
566     }
567 
568     /**
569      * Calls the equivalent of "adb shell cmd reboot_readiness" and returns the output.
570      */
executeShellCommand(String command)571     private String executeShellCommand(String command) throws Exception {
572         RebootReadinessShellCommand commandHandler =
573                 spy(new RebootReadinessShellCommand(mService, mMockContext));
574         File tmpFile = File.createTempFile("output", ".txt");
575         PrintWriter pw = new PrintWriter(tmpFile);
576         doReturn(pw).when(commandHandler).getOutPrintWriter();
577         commandHandler.exec(new Binder(), new FileDescriptor(), new FileDescriptor(),
578                 new FileDescriptor(), command.split(" "));
579         pw.flush();
580         BufferedReader reader = new BufferedReader(new FileReader(tmpFile));
581         String line;
582         StringBuilder sb = new StringBuilder();
583         while ((line = reader.readLine()) != null) {
584             sb.append(line);
585         }
586         return sb.toString();
587     }
588 
589     /**
590      * Sets the device interactivity on or off, and delays to allow the change to be processed.
591      */
setScreenState(boolean screenOn)592     private void setScreenState(boolean screenOn) throws Exception {
593         IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
594         filter.addAction(Intent.ACTION_SCREEN_OFF);
595         verify(mMockContext, atLeastOnce()).registerReceiver(
596                 mBroadcastReceiverArgumentCaptor.capture(), mIntentFilters.capture());
597 
598         // Get the broadcast receiver that receives interactivity broadcasts.
599         int index = -1;
600         List<IntentFilter> capturedFilters = mIntentFilters.getAllValues();
601         for (int i = 0; i < capturedFilters.size(); i++) {
602             if (capturedFilters.get(i).hasAction(Intent.ACTION_SCREEN_ON)) {
603                 index = i;
604             }
605         }
606         assertThat(index).isNotEqualTo(-1);
607         BroadcastReceiver receiver = mBroadcastReceiverArgumentCaptor.getAllValues().get(index);
608 
609         // Send updated interactivity state to receiver.
610         if (screenOn) {
611             receiver.onReceive(mMockContext, new Intent(Intent.ACTION_SCREEN_ON));
612         } else {
613             receiver.onReceive(mMockContext, new Intent(Intent.ACTION_SCREEN_OFF));
614         }
615 
616         // ALlow the state change to be processed.
617         Thread.sleep(1000);
618     }
619 
620     /**
621      * Returns a status listener which will decrement a passed CountdownLatch when it is called.
622      */
getStatusListener(CountDownLatch latch)623     private IRequestRebootReadinessStatusListener getStatusListener(CountDownLatch latch) {
624         return new IRequestRebootReadinessStatusListener() {
625             @Override
626             public void onRequestRebootReadinessStatus(RemoteCallback remoteCallback) {
627                 latch.countDown();
628                 Bundle bundle = new Bundle();
629                 bundle.putBoolean(RebootReadinessManager.IS_REBOOT_READY_KEY, false);
630                 bundle.putLong(RebootReadinessManager.ESTIMATED_FINISH_TIME_KEY, 1000L);
631                 bundle.putString(RebootReadinessManager.SUBSYSTEM_NAME_KEY, COMPONENT_NAME);
632                 remoteCallback.sendResult(bundle);
633             }
634 
635             @Override
636             public IBinder asBinder() {
637                 return new Binder();
638             }
639         };
640     }
641 }
642