1 /* 2 * Copyright (C) 2017 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.bluetooth.btservice; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothClass; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothHeadset; 23 import android.bluetooth.BluetoothProfile; 24 import android.bluetooth.BluetoothUuid; 25 import android.content.BroadcastReceiver; 26 import android.content.Intent; 27 import android.os.Handler; 28 import android.os.HandlerThread; 29 import android.os.Looper; 30 import android.os.Message; 31 import android.os.ParcelUuid; 32 import android.util.Log; 33 import java.util.ArrayList; 34 import java.util.HashMap; 35 import java.util.LinkedList; 36 import java.util.Queue; 37 import java.lang.Thread; 38 39 import android.test.AndroidTestCase; 40 41 import com.android.bluetooth.hfp.HeadsetService; 42 import com.android.bluetooth.a2dp.A2dpService; 43 import com.android.bluetooth.btservice.PhonePolicy; 44 import com.android.bluetooth.btservice.ServiceFactory; 45 import com.android.bluetooth.Utils; 46 47 import static org.mockito.Mockito.*; 48 49 public class PhonePolicyTest extends AndroidTestCase { 50 private static final String TAG = "PhonePolicyTest"; 51 private static final int ASYNC_CALL_TIMEOUT = 2000; // 2s 52 private static final int RETRY_TIMEOUT = 10000; // 10s 53 54 private HandlerThread mHandlerThread; 55 private BluetoothAdapter mAdapter; 56 57 @Override setUp()58 protected void setUp() { 59 mHandlerThread = new HandlerThread("PhonePolicyTest"); 60 mHandlerThread.start(); 61 mAdapter = BluetoothAdapter.getDefaultAdapter(); 62 } 63 64 @Override tearDown()65 protected void tearDown() { 66 mHandlerThread.quit(); 67 } 68 69 // Test that when new UUIDs are refreshed for a device then we set the priorities for various 70 // profiles accurately. The following profiles should have ON priorities: 71 // A2DP, HFP, HID and PAN testProcessInitProfilePriorities()72 public void testProcessInitProfilePriorities() { 73 BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter(); 74 BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05"); 75 76 // Create the mock objects required 77 AdapterService mockAdapterService = mock(AdapterService.class); 78 ServiceFactory mockServiceFactory = mock(ServiceFactory.class); 79 HeadsetService mockHeadsetService = mock(HeadsetService.class); 80 A2dpService mockA2dpService = mock(A2dpService.class); 81 82 // Mock the HeadsetService 83 when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService); 84 when(mockHeadsetService.getPriority(device)) 85 .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED); 86 87 // Mock the A2DP service 88 when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService); 89 when(mockA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED); 90 91 // Mock the looper 92 when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper()); 93 94 // Tell the AdapterService that it is a mock (see isMock documentation) 95 when(mockAdapterService.isMock()).thenReturn(true); 96 97 PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory); 98 99 // Get the broadcast receiver to inject events. 100 BroadcastReceiver injector = phPol.getBroadcastReceiver(); 101 102 // Inject an event for UUIDs updated for a remote device with only HFP enabled 103 Intent intent = new Intent(BluetoothDevice.ACTION_UUID); 104 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 105 ParcelUuid[] uuids = new ParcelUuid[2]; 106 uuids[0] = BluetoothUuid.Handsfree; 107 uuids[1] = BluetoothUuid.AudioSink; 108 109 intent.putExtra(BluetoothDevice.EXTRA_UUID, uuids); 110 injector.onReceive(null /* context */, intent); 111 112 // Check that the priorities of the devices for preferred profiles are set to ON 113 verify(mockHeadsetService, timeout(ASYNC_CALL_TIMEOUT).times(1)) 114 .setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON)); 115 verify(mockA2dpService, timeout(ASYNC_CALL_TIMEOUT).times(1)) 116 .setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON)); 117 } 118 119 // Test that when the adapter is turned ON then we call autoconnect on devices that have HFP and 120 // A2DP enabled. NOTE that the assumption is that we have already done the pairing previously 121 // and hence the priorities for the device is already set to AUTO_CONNECT over HFP and A2DP (as 122 // part of post pairing process). testAdapterOnAutoConnect()123 public void testAdapterOnAutoConnect() { 124 BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter(); 125 BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05"); 126 127 // Create the mock objects required 128 AdapterService mockAdapterService = mock(AdapterService.class); 129 ServiceFactory mockServiceFactory = mock(ServiceFactory.class); 130 HeadsetService mockHeadsetService = mock(HeadsetService.class); 131 A2dpService mockA2dpService = mock(A2dpService.class); 132 133 // Return desired values from the mocked object(s) 134 when(mockAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); 135 when(mockAdapterService.isQuietModeEnabled()).thenReturn(false); 136 when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService); 137 when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService); 138 139 // Return a list of bonded devices (just one) 140 BluetoothDevice[] bondedDevices = new BluetoothDevice[1]; 141 bondedDevices[0] = device; 142 when(mockAdapterService.getBondedDevices()).thenReturn(bondedDevices); 143 144 // Return PRIORITY_AUTO_CONNECT over HFP and A2DP 145 when(mockHeadsetService.getPriority(device)) 146 .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT); 147 when(mockA2dpService.getPriority(device)) 148 .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT); 149 150 // Mock the looper 151 when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper()); 152 153 // Tell the AdapterService that it is a mock (see isMock documentation) 154 when(mockAdapterService.isMock()).thenReturn(true); 155 156 PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory); 157 158 // Get the broadcast receiver to inject events 159 BroadcastReceiver injector = phPol.getBroadcastReceiver(); 160 161 // Inject an event that the adapter is turned on. 162 Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); 163 intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON); 164 injector.onReceive(null /* context */, intent); 165 166 // Check that we got a request to connect over HFP and A2DP 167 verify(mockHeadsetService, timeout(ASYNC_CALL_TIMEOUT).times(1)).connect(eq(device)); 168 verify(mockA2dpService, timeout(ASYNC_CALL_TIMEOUT).times(1)).connect(eq(device)); 169 } 170 171 // Test that we will try to re-connect to a profile on a device if an attempt failed previously. 172 // This is to add robustness to the connection mechanism testReconnectOnPartialConnect()173 public void testReconnectOnPartialConnect() { 174 BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter(); 175 BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05"); 176 177 // Create the mock objects required 178 AdapterService mockAdapterService = mock(AdapterService.class); 179 ServiceFactory mockServiceFactory = mock(ServiceFactory.class); 180 HeadsetService mockHeadsetService = mock(HeadsetService.class); 181 A2dpService mockA2dpService = mock(A2dpService.class); 182 183 // Setup the mocked factory to return mocked services 184 when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService); 185 when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService); 186 187 // Return a list of bonded devices (just one) 188 BluetoothDevice[] bondedDevices = new BluetoothDevice[1]; 189 bondedDevices[0] = device; 190 when(mockAdapterService.getBondedDevices()).thenReturn(bondedDevices); 191 192 // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are 193 // auto-connectable. 194 when(mockHeadsetService.getPriority(device)) 195 .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT); 196 when(mockA2dpService.getPriority(device)) 197 .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT); 198 199 when(mockAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); 200 201 // Mock the looper 202 when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper()); 203 204 // Tell the AdapterService that it is a mock (see isMock documentation) 205 when(mockAdapterService.isMock()).thenReturn(true); 206 207 PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory); 208 209 // Get the broadcast receiver to inject events 210 BroadcastReceiver injector = phPol.getBroadcastReceiver(); 211 212 // We send a connection successful for one profile since the re-connect *only* works if we 213 // have already connected successfully over one of the profiles 214 Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 215 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 216 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED); 217 intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); 218 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 219 injector.onReceive(null /* context */, intent); 220 221 // We should see (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP 222 // To enable that we need to make sure that HeadsetService returns the device as list of 223 // connected devices 224 ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>(); 225 hsConnectedDevices.add(device); 226 when(mockHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices); 227 // Also the A2DP should say that its not connected for same device 228 when(mockA2dpService.getConnectionState(device)) 229 .thenReturn(BluetoothProfile.STATE_DISCONNECTED); 230 231 // Check that we get a call to A2DP connect 232 verify(mockA2dpService, timeout(RETRY_TIMEOUT).times(1)).connect(eq(device)); 233 } 234 235 // Test that we will not try to reconnect on a profile if all the connections failed testNoReconnectOnNoConnect()236 public void testNoReconnectOnNoConnect() { 237 BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter(); 238 BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05"); 239 240 // Create the mock objects required 241 AdapterService mockAdapterService = mock(AdapterService.class); 242 ServiceFactory mockServiceFactory = mock(ServiceFactory.class); 243 HeadsetService mockHeadsetService = mock(HeadsetService.class); 244 A2dpService mockA2dpService = mock(A2dpService.class); 245 246 // Setup the mocked factory to return mocked services 247 when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService); 248 when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService); 249 250 // Return a list of bonded devices (just one) 251 BluetoothDevice[] bondedDevices = new BluetoothDevice[1]; 252 bondedDevices[0] = device; 253 when(mockAdapterService.getBondedDevices()).thenReturn(bondedDevices); 254 255 // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are 256 // auto-connectable. 257 when(mockHeadsetService.getPriority(device)) 258 .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT); 259 when(mockA2dpService.getPriority(device)) 260 .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT); 261 262 when(mockAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); 263 264 // Mock the looper 265 when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper()); 266 267 // Tell the AdapterService that it is a mock (see isMock documentation) 268 when(mockAdapterService.isMock()).thenReturn(true); 269 270 PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory); 271 272 // Get the broadcast receiver to inject events 273 BroadcastReceiver injector = phPol.getBroadcastReceiver(); 274 275 // We send a connection successful for one profile since the re-connect *only* works if we 276 // have already connected successfully over one of the profiles 277 Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 278 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 279 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED); 280 intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); 281 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 282 injector.onReceive(null /* context */, intent); 283 284 // Return an empty list simulating that the above connection successful was nullified 285 ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>(); 286 when(mockHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices); 287 288 // Also the A2DP should say that its not connected for same device 289 when(mockA2dpService.getConnectionState(device)) 290 .thenReturn(BluetoothProfile.STATE_DISCONNECTED); 291 292 // To check that we have processed all the messages we need to have a hard sleep here. The 293 // reason being mockito can only verify synchronous calls, asynchronous calls are hidden 294 // from its mocking framework. Also, Looper does not provide a way to wait until all future 295 // messages are proceed. 296 try { 297 Thread.sleep(RETRY_TIMEOUT); 298 } catch (Exception ex) { 299 } 300 301 // Check that we don't get any calls to reconnect 302 verify(mockA2dpService, never()).connect(eq(device)); 303 verify(mockHeadsetService, never()).connect(eq(device)); 304 } 305 306 // Test that a device with no supported uuids is initialized properly and does not crash the 307 // stack testNoSupportedUuids()308 public void testNoSupportedUuids() { 309 BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter(); 310 BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05"); 311 312 // Create the mock objects required 313 AdapterService mockAdapterService = mock(AdapterService.class); 314 ServiceFactory mockServiceFactory = mock(ServiceFactory.class); 315 HeadsetService mockHeadsetService = mock(HeadsetService.class); 316 A2dpService mockA2dpService = mock(A2dpService.class); 317 318 // Mock the HeadsetService 319 when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService); 320 when(mockHeadsetService.getPriority(device)) 321 .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED); 322 323 // Mock the A2DP service 324 when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService); 325 when(mockA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED); 326 327 // Mock the looper 328 when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper()); 329 330 PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory); 331 332 // Get the broadcast receiver to inject events. 333 BroadcastReceiver injector = phPol.getBroadcastReceiver(); 334 335 // Inject an event for UUIDs updated for a remote device with only HFP enabled 336 Intent intent = new Intent(BluetoothDevice.ACTION_UUID); 337 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 338 339 // Put no UUIDs 340 injector.onReceive(null /* context */, intent); 341 342 // To check that we have processed all the messages we need to have a hard sleep here. The 343 // reason being mockito can only verify synchronous calls, asynchronous calls are hidden 344 // from its mocking framework. Also, Looper does not provide a way to wait until all future 345 // messages are proceed. 346 try { 347 Thread.sleep(RETRY_TIMEOUT); 348 } catch (Exception ex) { 349 } 350 351 // Check that we do not crash and not call any setPriority methods 352 verify(mockHeadsetService, never()) 353 .setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON)); 354 verify(mockA2dpService, never()).setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON)); 355 } 356 } 357