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