1 /* 2 * Copyright 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.server.bluetooth; 18 19 import static android.bluetooth.BluetoothAdapter.STATE_BLE_ON; 20 import static android.bluetooth.BluetoothAdapter.STATE_OFF; 21 import static android.bluetooth.BluetoothAdapter.STATE_ON; 22 import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON; 23 24 import static com.android.server.bluetooth.BluetoothManagerService.MESSAGE_BLUETOOTH_SERVICE_CONNECTED; 25 import static com.android.server.bluetooth.BluetoothManagerService.MESSAGE_BLUETOOTH_STATE_CHANGE; 26 import static com.android.server.bluetooth.BluetoothManagerService.MESSAGE_DISABLE; 27 import static com.android.server.bluetooth.BluetoothManagerService.MESSAGE_ENABLE; 28 import static com.android.server.bluetooth.BluetoothManagerService.MESSAGE_TIMEOUT_BIND; 29 30 import static com.google.common.truth.Truth.assertThat; 31 32 import static org.mockito.ArgumentMatchers.anyBoolean; 33 import static org.mockito.ArgumentMatchers.eq; 34 import static org.mockito.Mockito.any; 35 import static org.mockito.Mockito.anyInt; 36 import static org.mockito.Mockito.doAnswer; 37 import static org.mockito.Mockito.doNothing; 38 import static org.mockito.Mockito.doReturn; 39 import static org.mockito.Mockito.mock; 40 import static org.mockito.Mockito.times; 41 import static org.mockito.Mockito.validateMockitoUsage; 42 import static org.mockito.Mockito.verify; 43 44 import android.bluetooth.IBluetooth; 45 import android.bluetooth.IBluetoothCallback; 46 import android.bluetooth.IBluetoothManagerCallback; 47 import android.bluetooth.IBluetoothStateChangeCallback; 48 import android.content.ComponentName; 49 import android.content.Context; 50 import android.content.ContextWrapper; 51 import android.content.Intent; 52 import android.content.ServiceConnection; 53 import android.os.IBinder; 54 import android.os.Message; 55 import android.os.UserHandle; 56 import android.os.UserManager; 57 import android.os.test.TestLooper; 58 import android.provider.Settings; 59 60 import androidx.test.platform.app.InstrumentationRegistry; 61 import androidx.test.runner.AndroidJUnit4; 62 63 import org.junit.After; 64 import org.junit.Before; 65 import org.junit.Test; 66 import org.junit.runner.RunWith; 67 import org.mockito.ArgumentCaptor; 68 import org.mockito.Mock; 69 import org.mockito.MockitoAnnotations; 70 import org.mockito.Spy; 71 72 import java.util.stream.IntStream; 73 74 @RunWith(AndroidJUnit4.class) 75 public class BluetoothManagerServiceTest { 76 private static final String TAG = BluetoothManagerServiceTest.class.getSimpleName(); 77 private static final int STATE_BLE_TURNING_ON = 14; // can't find the symbol because hidden api 78 79 BluetoothManagerService mManagerService; 80 81 @Spy 82 private final Context mContext = 83 new ContextWrapper(InstrumentationRegistry.getInstrumentation().getTargetContext()); 84 85 @Spy BluetoothServerProxy mBluetoothServerProxy; 86 @Mock UserManager mUserManager; 87 @Mock UserHandle mUserHandle; 88 89 @Mock IBinder mBinder; 90 @Mock IBluetoothManagerCallback mManagerCallback; 91 @Mock IBluetoothStateChangeCallback mStateChangeCallback; 92 93 @Mock IBluetooth mAdapterService; 94 @Mock AdapterBinder mAdapterBinder; 95 96 TestLooper mLooper; 97 98 static { 99 // Required for reading DeviceConfig. 100 InstrumentationRegistry.getInstrumentation() 101 .getUiAutomation() 102 .adoptShellPermissionIdentity( 103 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); 104 } 105 106 @Before setUp()107 public void setUp() throws Exception { 108 MockitoAnnotations.initMocks(this); 109 110 // Mock these functions so security errors won't throw 111 doReturn("name") 112 .when(mBluetoothServerProxy) 113 .settingsSecureGetString(any(), eq(Settings.Secure.BLUETOOTH_NAME)); 114 doReturn("00:11:22:33:44:55") 115 .when(mBluetoothServerProxy) 116 .settingsSecureGetString(any(), eq(Settings.Secure.BLUETOOTH_ADDRESS)); 117 // Set persisted state to BLUETOOTH_OFF to not generate unwanted behavior when starting test 118 doReturn(BluetoothManagerService.BLUETOOTH_OFF) 119 .when(mBluetoothServerProxy) 120 .getBluetoothPersistedState(any(), anyInt()); 121 122 doAnswer( 123 inv -> { 124 doReturn(inv.getArguments()[1]) 125 .when(mBluetoothServerProxy) 126 .getBluetoothPersistedState(any(), anyInt()); 127 return null; 128 }) 129 .when(mBluetoothServerProxy) 130 .setBluetoothPersistedState(any(), anyInt()); 131 132 // Test is not allowed to send broadcast as Bluetooth. doNothing Prevent SecurityException 133 doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any(), any()); 134 doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); 135 136 doReturn(mBinder).when(mManagerCallback).asBinder(); 137 138 doReturn(mAdapterBinder).when(mBluetoothServerProxy).createAdapterBinder(any()); 139 doReturn(mAdapterService).when(mAdapterBinder).getAdapterBinder(); 140 141 doReturn(mock(Intent.class)) 142 .when(mContext) 143 .registerReceiverForAllUsers(any(), any(), eq(null), eq(null)); 144 145 doReturn(true) 146 .when(mContext) 147 .bindServiceAsUser( 148 any(Intent.class), 149 any(ServiceConnection.class), 150 anyInt(), 151 any(UserHandle.class)); 152 153 BluetoothServerProxy.setInstanceForTesting(mBluetoothServerProxy); 154 155 mLooper = new TestLooper(); 156 157 mManagerService = new BluetoothManagerService(mContext, mLooper.getLooper()); 158 mManagerService.initialize(mUserHandle); 159 160 mManagerService.registerAdapter(mManagerCallback); 161 } 162 163 @After tearDown()164 public void tearDown() { 165 if (mManagerService != null) { 166 mManagerService.unregisterAdapter(mManagerCallback); 167 mManagerService = null; 168 } 169 mLooper.moveTimeForward(120_000); // 120 seconds 170 171 assertThat(mLooper.nextMessage()).isNull(); 172 validateMockitoUsage(); 173 } 174 175 /** 176 * Dispatch all the message on the Loopper and check that the what is expected 177 * 178 * @param what list of message that are expected to be run by the handler 179 */ syncHandler(int... what)180 private void syncHandler(int... what) { 181 IntStream.of(what) 182 .forEach( 183 w -> { 184 Message msg = mLooper.nextMessage(); 185 assertThat(msg).isNotNull(); 186 assertThat(msg.what).isEqualTo(w); 187 msg.getTarget().dispatchMessage(msg); 188 }); 189 } 190 191 @Test onUserRestrictionsChanged_disallowBluetooth_onlySendDisableMessageOnSystemUser()192 public void onUserRestrictionsChanged_disallowBluetooth_onlySendDisableMessageOnSystemUser() 193 throws InterruptedException { 194 // Mimic the case when restriction settings changed 195 doReturn(true) 196 .when(mUserManager) 197 .hasUserRestrictionForUser(eq(UserManager.DISALLOW_BLUETOOTH), any()); 198 doReturn(false) 199 .when(mUserManager) 200 .hasUserRestrictionForUser(eq(UserManager.DISALLOW_BLUETOOTH_SHARING), any()); 201 202 // Check if disable message sent once for system user only 203 204 // test run on user -1, should not turning Bluetooth off 205 mManagerService.onUserRestrictionsChanged(UserHandle.CURRENT); 206 assertThat(mLooper.nextMessage()).isNull(); 207 208 // called from SYSTEM user, should try to toggle Bluetooth off 209 mManagerService.onUserRestrictionsChanged(UserHandle.SYSTEM); 210 syncHandler(MESSAGE_DISABLE); 211 } 212 213 @Test enable_bindFailure_removesTimeout()214 public void enable_bindFailure_removesTimeout() throws Exception { 215 doReturn(false) 216 .when(mContext) 217 .bindServiceAsUser( 218 any(Intent.class), 219 any(ServiceConnection.class), 220 anyInt(), 221 any(UserHandle.class)); 222 mManagerService.enableBle("enable_bindFailure_removesTimeout", mBinder); 223 syncHandler(MESSAGE_ENABLE); 224 225 // TODO(b/280518177): Failed to start should be noted / reported in metrics 226 // Maybe show a popup or a crash notification 227 // Should we attempt to re-bind ? 228 } 229 230 @Test enable_bindTimeout()231 public void enable_bindTimeout() throws Exception { 232 mManagerService.enableBle("enable_bindTimeout", mBinder); 233 syncHandler(MESSAGE_ENABLE); 234 235 mLooper.moveTimeForward(120_000); // 120 seconds 236 syncHandler(MESSAGE_TIMEOUT_BIND); 237 // Force handling the message now without waiting for the timeout to fire 238 239 // TODO(b/280518177): A lot of stuff is wrong here since when a timeout occur: 240 // * No error is printed to the user 241 // * Code stop trying to start the bluetooth. 242 // * if user ask to enable again, it will start a second bind but the first still run 243 } 244 acceptBluetoothBinding(IBinder binder, String name, int n)245 private void acceptBluetoothBinding(IBinder binder, String name, int n) { 246 ComponentName compName = new ComponentName("", "com.android.bluetooth." + name); 247 248 ArgumentCaptor<BluetoothManagerService.BluetoothServiceConnection> captor = 249 ArgumentCaptor.forClass(BluetoothManagerService.BluetoothServiceConnection.class); 250 verify(mContext, times(n)) 251 .bindServiceAsUser( 252 any(Intent.class), captor.capture(), anyInt(), any(UserHandle.class)); 253 assertThat(captor.getAllValues().size()).isEqualTo(n); 254 255 captor.getAllValues().get(n - 1).onServiceConnected(compName, binder); 256 syncHandler(MESSAGE_BLUETOOTH_SERVICE_CONNECTED); 257 } 258 captureBluetoothCallback(AdapterBinder adapterBinder)259 private static IBluetoothCallback captureBluetoothCallback(AdapterBinder adapterBinder) 260 throws Exception { 261 ArgumentCaptor<IBluetoothCallback> captor = 262 ArgumentCaptor.forClass(IBluetoothCallback.class); 263 verify(adapterBinder).registerCallback(captor.capture(), any()); 264 assertThat(captor.getAllValues().size()).isEqualTo(1); 265 return captor.getValue(); 266 } 267 transition_offToBleOn()268 IBluetoothCallback transition_offToBleOn() throws Exception { 269 // Binding of IBluetooth 270 acceptBluetoothBinding(mBinder, "btservice.AdapterService", 1); 271 272 // TODO(b/280518177): This callback is too early, bt is not ON nor BLE_ON 273 verify(mManagerCallback).onBluetoothServiceUp(any()); 274 275 IBluetoothCallback btCallback = captureBluetoothCallback(mAdapterBinder); 276 verify(mAdapterBinder).enable(anyBoolean(), any()); 277 278 // AdapterService is sending AdapterState.BLE_TURN_ON that will trigger this callback 279 // and in parallel it call its `bringUpBle()` 280 btCallback.onBluetoothStateChange(STATE_OFF, STATE_BLE_TURNING_ON); 281 syncHandler(MESSAGE_BLUETOOTH_STATE_CHANGE); 282 assertThat(mManagerService.getState()).isEqualTo(STATE_BLE_TURNING_ON); 283 284 // assertThat(mManagerService.waitForManagerState(STATE_BLE_TURNING_ON)).isTrue(); 285 286 // GattService has been started by AdapterService and it will enable native side then 287 // trigger the stateChangeCallback from native 288 btCallback.onBluetoothStateChange(STATE_BLE_TURNING_ON, STATE_BLE_ON); 289 syncHandler(MESSAGE_BLUETOOTH_STATE_CHANGE); 290 assertThat(mManagerService.getState()).isEqualTo(STATE_BLE_ON); 291 292 // Check that we sent 2 intent, one for BLE_TURNING_ON, one for BLE_ON 293 // TODO(b/280518177): assert the intent are the correct one 294 verify(mContext, times(2)).sendBroadcastAsUser(any(), any(), any(), any()); 295 return btCallback; 296 } 297 transition_offToOn()298 private IBluetoothCallback transition_offToOn() throws Exception { 299 IBluetoothCallback btCallback = transition_offToBleOn(); 300 verify(mAdapterBinder, times(1)).startBrEdr(any()); 301 302 // AdapterService go to turning_on and start all profile on its own 303 btCallback.onBluetoothStateChange(STATE_BLE_ON, STATE_TURNING_ON); 304 syncHandler(MESSAGE_BLUETOOTH_STATE_CHANGE); 305 // When all the profile are started, adapterService consider it is ON 306 btCallback.onBluetoothStateChange(STATE_TURNING_ON, STATE_ON); 307 syncHandler(MESSAGE_BLUETOOTH_STATE_CHANGE); 308 309 // Check that we sent 6 intent, 4 for BLE: BLE_TURNING_ON + BLE_ON + TURNING_ON + ON 310 // and 2 for classic: TURNING_ON + ON 311 // TODO(b/280518177): assert the intent are the correct one 312 verify(mContext, times(6)).sendBroadcastAsUser(any(), any(), any(), any()); 313 314 return btCallback; 315 } 316 317 @Test offToBleOn()318 public void offToBleOn() throws Exception { 319 mManagerService.enableBle("test_offToBleOn", mBinder); 320 syncHandler(MESSAGE_ENABLE); 321 322 transition_offToBleOn(); 323 324 // Check that there was no transition to STATE_ON 325 verify(mAdapterBinder, times(0)).startBrEdr(any()); 326 assertThat(mManagerService.getState()).isEqualTo(STATE_BLE_ON); 327 } 328 329 @Test offToOn()330 public void offToOn() throws Exception { 331 mManagerService.enable("test_offToOn"); 332 syncHandler(MESSAGE_ENABLE); 333 334 transition_offToOn(); 335 336 assertThat(mManagerService.getState()).isEqualTo(STATE_ON); 337 } 338 } 339