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.cts.scheduling;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.fail;
22 
23 import android.Manifest;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.os.HandlerExecutor;
29 import android.os.HandlerThread;
30 import android.provider.DeviceConfig;
31 import android.scheduling.RebootReadinessManager;
32 import android.scheduling.RebootReadinessManager.RebootReadinessStatus;
33 import android.scheduling.RebootReadinessManager.RequestRebootReadinessStatusListener;
34 
35 import androidx.test.InstrumentationRegistry;
36 
37 import org.junit.After;
38 import org.junit.AfterClass;
39 import org.junit.BeforeClass;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 import org.junit.runners.JUnit4;
43 
44 import java.util.concurrent.CountDownLatch;
45 import java.util.concurrent.TimeUnit;
46 
47 /**
48  * Test system RebootReadinessManager APIs.
49  */
50 @RunWith(JUnit4.class)
51 public class RebootReadinessManagerTest {
52 
53     private static class RebootCallback implements RequestRebootReadinessStatusListener {
54         private final boolean mIsReadyToReboot;
55         private final long mEstimatedFinishTime;
56         private final String mSubsystemName;
57 
RebootCallback(boolean isReadyToReboot, long estimatedFinishTime, String subsystemName)58         RebootCallback(boolean isReadyToReboot, long estimatedFinishTime, String subsystemName) {
59             mIsReadyToReboot = isReadyToReboot;
60             mEstimatedFinishTime = estimatedFinishTime;
61             mSubsystemName = subsystemName;
62         }
63 
64         @Override
onRequestRebootReadinessStatus()65         public RebootReadinessStatus onRequestRebootReadinessStatus() {
66             return new RebootReadinessStatus(mIsReadyToReboot, mEstimatedFinishTime,
67                     mSubsystemName);
68         }
69     }
70 
71     private static final String TEST_CALLBACK_PREFIX = "TESTCOMPONENT";
72 
73     private static final RequestRebootReadinessStatusListener BLOCKING_CALLBACK =
74             new RebootCallback(false, 0, TEST_CALLBACK_PREFIX + ": blocking component");
75     private static final RequestRebootReadinessStatusListener READY_CALLBACK = new RebootCallback(
76             true, 0, TEST_CALLBACK_PREFIX + ": non-blocking component");
77 
78     private static final String PROPERTY_ACTIVE_POLLING_INTERVAL_MS = "active_polling_interval_ms";
79     private static final String PROPERTY_DISABLE_INTERACTIVITY_CHECK =
80             "disable_interactivity_check";
81     private static final String PROPERTY_INTERACTIVITY_THRESHOLD_MS = "interactivity_threshold_ms";
82     private static final String PROPERTY_DISABLE_APP_ACTIVITY_CHECK = "disable_app_activity_check";
83     private static final String PROPERTY_DISABLE_SUBSYSTEMS_CHECK = "disable_subsystems_check";
84 
85     RebootReadinessManager mRebootReadinessManager =
86             (RebootReadinessManager) InstrumentationRegistry.getContext().getSystemService(
87                     Context.REBOOT_READINESS_SERVICE);
88 
89     private static final HandlerThread sThread = new HandlerThread("RebootReadinessManagerTest");
90     private static HandlerExecutor sHandlerExecutor;
91 
92     @BeforeClass
setupClass()93     public static void setupClass() throws Exception {
94         sThread.start();
95         sHandlerExecutor = new HandlerExecutor(sThread.getThreadHandler());
96         adoptShellPermissions();
97         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
98                 PROPERTY_ACTIVE_POLLING_INTERVAL_MS, "1000", false);
99         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
100                 PROPERTY_DISABLE_INTERACTIVITY_CHECK, "true", false);
101         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
102                 PROPERTY_DISABLE_APP_ACTIVITY_CHECK, "true", false);
103         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
104                 PROPERTY_DISABLE_SUBSYSTEMS_CHECK, "true", false);
105         // Small delay to allow DeviceConfig changes to propagate.
106         Thread.sleep(1000);
107     }
108 
109     @After
tearDown()110     public void tearDown() {
111         mRebootReadinessManager.removeRequestRebootReadinessStatusListener(READY_CALLBACK);
112         mRebootReadinessManager.removeRequestRebootReadinessStatusListener(BLOCKING_CALLBACK);
113         mRebootReadinessManager.cancelPendingReboot();
114     }
115 
116     @AfterClass
teardownClass()117     public static void teardownClass() {
118         sThread.quitSafely();
119         dropShellPermissions();
120     }
121 
122     @Test
testRegisterAndUnregisterCallback()123     public void testRegisterAndUnregisterCallback() throws Exception {
124         assertThat(isReadyToReboot()).isTrue();
125         mRebootReadinessManager.cancelPendingReboot();
126 
127         mRebootReadinessManager.addRequestRebootReadinessStatusListener(
128                 sHandlerExecutor, BLOCKING_CALLBACK);
129         assertThat(isReadyToReboot()).isFalse();
130         mRebootReadinessManager.removeRequestRebootReadinessStatusListener(BLOCKING_CALLBACK);
131         mRebootReadinessManager.cancelPendingReboot();
132         assertThat(isReadyToReboot()).isTrue();
133     }
134 
135     @Test
testCallbackReadyToReboot()136     public void testCallbackReadyToReboot() throws Exception {
137         mRebootReadinessManager.addRequestRebootReadinessStatusListener(
138                 sHandlerExecutor, READY_CALLBACK);
139         CountDownLatch latch = new CountDownLatch(1);
140         final BroadcastReceiver receiver = new BroadcastReceiver() {
141             @Override
142             public void onReceive(Context context, Intent intent) {
143                 boolean extra = intent.getBooleanExtra(
144                         RebootReadinessManager.EXTRA_IS_READY_TO_REBOOT, false);
145                 assertThat(extra).isEqualTo(true);
146                 latch.countDown();
147             }
148         };
149         InstrumentationRegistry.getContext().registerReceiver(receiver,
150                 new IntentFilter(RebootReadinessManager.ACTION_REBOOT_READY));
151         mRebootReadinessManager.addRequestRebootReadinessStatusListener(
152                 sHandlerExecutor, READY_CALLBACK);
153         assertThat(isReadyToReboot()).isTrue();
154         InstrumentationRegistry.getContext().unregisterReceiver(receiver);
155     }
156 
157     @Test
testCallbackNotReadyToReboot()158     public void testCallbackNotReadyToReboot() throws Exception {
159         assertThat(isReadyToReboot()).isTrue();
160         mRebootReadinessManager.addRequestRebootReadinessStatusListener(
161                 sHandlerExecutor, READY_CALLBACK);
162         mRebootReadinessManager.addRequestRebootReadinessStatusListener(
163                 sHandlerExecutor, BLOCKING_CALLBACK);
164         mRebootReadinessManager.cancelPendingReboot();
165         assertThat(isReadyToReboot()).isFalse();
166     }
167 
168     @Test
testRebootPermissionCheck()169     public void testRebootPermissionCheck() {
170         dropShellPermissions();
171         try {
172             mRebootReadinessManager.markRebootPending();
173             fail("Expected to throw SecurityException");
174         } catch (SecurityException expected) {
175         } finally {
176             adoptShellPermissions();
177         }
178     }
179 
180     @Test
testSignalRebootReadinessPermissionCheck()181     public void testSignalRebootReadinessPermissionCheck() {
182         dropShellPermissions();
183         try {
184             mRebootReadinessManager.addRequestRebootReadinessStatusListener(
185                     sHandlerExecutor, READY_CALLBACK);
186             fail("Expected to throw SecurityException");
187         } catch (SecurityException expected) {
188         } finally {
189             adoptShellPermissions();
190         }
191     }
192 
193 
194     @Test
testCancelPendingReboot()195     public void testCancelPendingReboot() throws Exception {
196         mRebootReadinessManager.addRequestRebootReadinessStatusListener(
197                 sHandlerExecutor, BLOCKING_CALLBACK);
198         mRebootReadinessManager.markRebootPending();
199         mRebootReadinessManager.cancelPendingReboot();
200         CountDownLatch latch = new CountDownLatch(1);
201         final BroadcastReceiver receiver = new BroadcastReceiver() {
202             @Override
203             public void onReceive(Context context, Intent intent) {
204                 fail("Reboot readiness checks should be cancelled so no broadcast should be sent.");
205             }
206         };
207         InstrumentationRegistry.getContext().registerReceiver(receiver,
208                 new IntentFilter(RebootReadinessManager.ACTION_REBOOT_READY));
209         mRebootReadinessManager.removeRequestRebootReadinessStatusListener(BLOCKING_CALLBACK);
210 
211         // Ensure that no broadcast is received when reboot readiness checks are canceled.
212         latch.await(10, TimeUnit.SECONDS);
213         assertThat(latch.getCount()).isEqualTo(1);
214         InstrumentationRegistry.getContext().unregisterReceiver(receiver);
215     }
216 
217     @Test
testCancelPendingRebootWhenNotRegistered()218     public void testCancelPendingRebootWhenNotRegistered() {
219         // Ensure that the process does not crash or throw an exception
220         mRebootReadinessManager.cancelPendingReboot();
221     }
222 
223     @Test
testDisableInteractivityCheck()224     public void testDisableInteractivityCheck() throws Exception {
225         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
226                 PROPERTY_INTERACTIVITY_THRESHOLD_MS, Long.toString(Long.MAX_VALUE), false);
227         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
228                 PROPERTY_DISABLE_INTERACTIVITY_CHECK, "false", false);
229         // Allow a small amount of time for DeviceConfig changes to be noted.
230         Thread.sleep(1000);
231         assertThat(isReadyToReboot()).isFalse();
232         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_REBOOT_READINESS,
233                 PROPERTY_DISABLE_INTERACTIVITY_CHECK, "true", false);
234         // Allow a small amount of time for DeviceConfig changes to be noted.
235         Thread.sleep(1000);
236         assertThat(isReadyToReboot()).isTrue();
237     }
238 
239     @Test
testRebootReadinessStatus()240     public void testRebootReadinessStatus() {
241         RebootReadinessStatus status = new RebootReadinessStatus(false, 1000, "test");
242         assertThat(status.isReadyToReboot()).isFalse();
243         assertThat(status.getEstimatedFinishTime()).isEqualTo(1000);
244         assertThat(status.getLogSubsystemName()).isEqualTo("test");
245     }
246 
247     @Test
testRebootReadinessStatusWithEmptyNameThrowsException()248     public void testRebootReadinessStatusWithEmptyNameThrowsException() {
249         try {
250             RebootReadinessStatus status = new RebootReadinessStatus(false, 1000, "");
251             fail("Expected to throw exception when an empty name is supplied.");
252         } catch (IllegalArgumentException expected) {
253         }
254     }
255 
isReadyToReboot()256     private boolean isReadyToReboot() throws Exception {
257         mRebootReadinessManager.markRebootPending();
258         // Add a small timeout to allow the reboot readiness state to be noted.
259         // TODO(b/161353402): Negate the need for this timeout.
260         Thread.sleep(1000);
261         return mRebootReadinessManager.isReadyToReboot();
262     }
263 
adoptShellPermissions()264     private static void adoptShellPermissions() {
265         InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
266                 Manifest.permission.REBOOT, Manifest.permission.WRITE_DEVICE_CONFIG,
267                 Manifest.permission.SIGNAL_REBOOT_READINESS,
268                 Manifest.permission.INTERACT_ACROSS_USERS_FULL);
269     }
270 
dropShellPermissions()271     private static void dropShellPermissions() {
272         InstrumentationRegistry
273                 .getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
274     }
275 }
276