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