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.googlecode.android_scripting.facade.bluetooth; 18 19 import android.app.Service; 20 import android.bluetooth.BluetoothA2dp; 21 import android.bluetooth.BluetoothA2dpSink; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothHeadset; 25 import android.bluetooth.BluetoothHeadsetClient; 26 import android.bluetooth.BluetoothHidDevice; 27 import android.bluetooth.BluetoothHidHost; 28 import android.bluetooth.BluetoothManager; 29 import android.bluetooth.BluetoothMap; 30 import android.bluetooth.BluetoothMapClient; 31 import android.bluetooth.BluetoothPan; 32 import android.bluetooth.BluetoothPbapClient; 33 import android.bluetooth.BluetoothProfile; 34 import android.bluetooth.BluetoothUuid; 35 import android.content.BroadcastReceiver; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.IntentFilter; 39 import android.os.Bundle; 40 import android.os.ParcelUuid; 41 42 import com.googlecode.android_scripting.Log; 43 import com.googlecode.android_scripting.facade.EventFacade; 44 import com.googlecode.android_scripting.facade.FacadeManager; 45 import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 46 import com.googlecode.android_scripting.rpc.Rpc; 47 import com.googlecode.android_scripting.rpc.RpcDefault; 48 import com.googlecode.android_scripting.rpc.RpcOptional; 49 import com.googlecode.android_scripting.rpc.RpcParameter; 50 51 import org.json.JSONArray; 52 import org.json.JSONException; 53 54 import java.util.ArrayList; 55 import java.util.Arrays; 56 import java.util.Collections; 57 import java.util.HashMap; 58 import java.util.List; 59 import java.util.Map; 60 61 public class BluetoothConnectionFacade extends RpcReceiver { 62 63 private final Service mService; 64 private final Context mContext; 65 private final BluetoothAdapter mBluetoothAdapter; 66 private final BluetoothManager mBluetoothManager; 67 private final BluetoothPairingHelper mPairingHelper; 68 private final Map<String, BroadcastReceiver> listeningDevices; 69 private final EventFacade mEventFacade; 70 71 private final IntentFilter mDiscoverConnectFilter; 72 private final IntentFilter mPairingFilter; 73 private final IntentFilter mBondFilter; 74 private final IntentFilter mA2dpStateChangeFilter; 75 private final IntentFilter mA2dpSinkStateChangeFilter; 76 private final IntentFilter mHidStateChangeFilter; 77 private final IntentFilter mHidDeviceStateChangeFilter; 78 private final IntentFilter mHspStateChangeFilter; 79 private final IntentFilter mHfpClientStateChangeFilter; 80 private final IntentFilter mPbapClientStateChangeFilter; 81 private final IntentFilter mPanStateChangeFilter; 82 private final IntentFilter mMapClientStateChangeFilter; 83 private final IntentFilter mMapStateChangeFilter; 84 85 private final Bundle mGoodNews; 86 private final Bundle mBadNews; 87 88 private BluetoothA2dpFacade mA2dpProfile; 89 private BluetoothA2dpSinkFacade mA2dpSinkProfile; 90 private BluetoothHidFacade mHidProfile; 91 private BluetoothHidDeviceFacade mHidDeviceProfile; 92 private BluetoothHspFacade mHspProfile; 93 private BluetoothHfpClientFacade mHfpClientProfile; 94 private BluetoothPbapClientFacade mPbapClientProfile; 95 private BluetoothPanFacade mPanProfile; 96 private BluetoothMapClientFacade mMapClientProfile; 97 private BluetoothMapFacade mMapProfile; 98 private ArrayList<String> mDeviceMonitorList; 99 100 public BluetoothConnectionFacade(FacadeManager manager) { 101 super(manager); 102 mService = manager.getService(); 103 mContext = mService.getApplicationContext(); 104 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 105 mBluetoothManager = (BluetoothManager) mContext.getSystemService( 106 Service.BLUETOOTH_SERVICE); 107 mDeviceMonitorList = new ArrayList<String>(); 108 // Use a synchronized map to avoid racing problems 109 listeningDevices = Collections.synchronizedMap(new HashMap<String, BroadcastReceiver>()); 110 111 mEventFacade = manager.getReceiver(EventFacade.class); 112 mPairingHelper = new BluetoothPairingHelper(mEventFacade); 113 mA2dpProfile = manager.getReceiver(BluetoothA2dpFacade.class); 114 mA2dpSinkProfile = manager.getReceiver(BluetoothA2dpSinkFacade.class); 115 mHidProfile = manager.getReceiver(BluetoothHidFacade.class); 116 mHidDeviceProfile = manager.getReceiver(BluetoothHidDeviceFacade.class); 117 mHspProfile = manager.getReceiver(BluetoothHspFacade.class); 118 mHfpClientProfile = manager.getReceiver(BluetoothHfpClientFacade.class); 119 mPbapClientProfile = manager.getReceiver(BluetoothPbapClientFacade.class); 120 mPanProfile = manager.getReceiver(BluetoothPanFacade.class); 121 mMapClientProfile = manager.getReceiver(BluetoothMapClientFacade.class); 122 mMapProfile = manager.getReceiver(BluetoothMapFacade.class); 123 124 mDiscoverConnectFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND); 125 mDiscoverConnectFilter.addAction(BluetoothDevice.ACTION_UUID); 126 mDiscoverConnectFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 127 128 mPairingFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST); 129 mPairingFilter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); 130 mPairingFilter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); 131 mPairingFilter.setPriority(999); 132 133 mBondFilter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 134 mBondFilter.addAction(BluetoothDevice.ACTION_FOUND); 135 mBondFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 136 137 mA2dpStateChangeFilter = new IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 138 mA2dpSinkStateChangeFilter = 139 new IntentFilter(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED); 140 mHidStateChangeFilter = 141 new IntentFilter(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED); 142 mHidDeviceStateChangeFilter = 143 new IntentFilter(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED); 144 mHspStateChangeFilter = new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 145 mHfpClientStateChangeFilter = 146 new IntentFilter(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 147 mPbapClientStateChangeFilter = 148 new IntentFilter(BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED); 149 mPanStateChangeFilter = 150 new IntentFilter(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); 151 mMapClientStateChangeFilter = 152 new IntentFilter(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED); 153 mMapStateChangeFilter = 154 new IntentFilter(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); 155 156 mGoodNews = new Bundle(); 157 mGoodNews.putBoolean("Status", true); 158 mBadNews = new Bundle(); 159 mBadNews.putBoolean("Status", false); 160 } 161 162 private void unregisterCachedListener(String listenerId) { 163 BroadcastReceiver listener = listeningDevices.remove(listenerId); 164 if (listener != null) { 165 mService.unregisterReceiver(listener); 166 } 167 } 168 169 /** 170 * Connect to a specific device upon its discovery 171 */ 172 public class DiscoverConnectReceiver extends BroadcastReceiver { 173 private final String mDeviceID; 174 private BluetoothDevice mDevice; 175 176 /** 177 * Constructor 178 * 179 * @param deviceID Either the device alias name or mac address. 180 * @param bond If true, bond the device only. 181 */ 182 public DiscoverConnectReceiver(String deviceID) { 183 super(); 184 mDeviceID = deviceID; 185 } 186 187 @Override 188 public void onReceive(Context context, Intent intent) { 189 String action = intent.getAction(); 190 // The specified device is found. 191 if (action.equals(BluetoothDevice.ACTION_FOUND)) { 192 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 193 if (BluetoothFacade.deviceMatch(device, mDeviceID)) { 194 Log.d("Found device " + device.getAliasName() + " for connection."); 195 mBluetoothAdapter.cancelDiscovery(); 196 mDevice = device; 197 } 198 // After discovery stops. 199 } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { 200 if (mDevice == null) { 201 Log.d("Device " + mDeviceID + " not discovered."); 202 mEventFacade.postEvent("Bond" + mDeviceID, mBadNews); 203 return; 204 } 205 boolean status = mDevice.fetchUuidsWithSdp(); 206 Log.d("Initiated ACL connection: " + status); 207 } else if (action.equals(BluetoothDevice.ACTION_UUID)) { 208 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 209 if (BluetoothFacade.deviceMatch(device, mDeviceID)) { 210 Log.d("Initiating connections."); 211 connectProfile(device, mDeviceID); 212 mService.unregisterReceiver(listeningDevices.remove("Connect" + mDeviceID)); 213 } 214 } 215 } 216 } 217 218 /** 219 * Connect to a specific device upon its discovery 220 */ 221 public class DiscoverBondReceiver extends BroadcastReceiver { 222 private final String mDeviceID; 223 private BluetoothDevice mDevice = null; 224 private boolean started = false; 225 226 /** 227 * Constructor 228 * 229 * @param deviceID Either the device alias name or Mac address. 230 */ 231 public DiscoverBondReceiver(String deviceID) { 232 super(); 233 mDeviceID = deviceID; 234 } 235 236 @Override 237 public void onReceive(Context context, Intent intent) { 238 String action = intent.getAction(); 239 // The specified device is found. 240 if (action.equals(BluetoothDevice.ACTION_FOUND)) { 241 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 242 if (BluetoothFacade.deviceMatch(device, mDeviceID)) { 243 Log.d("Found device " + device.getAliasName() + " for connection."); 244 mBluetoothAdapter.cancelDiscovery(); 245 mDevice = device; 246 } 247 // After discovery stops. 248 } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { 249 if (mDevice == null) { 250 Log.d("Device " + mDeviceID + " was not discovered."); 251 mEventFacade.postEvent("Bond", mBadNews); 252 return; 253 } 254 // Attempt to initiate bonding. 255 if (!started) { 256 Log.d("Bond with " + mDevice.getAliasName()); 257 if (mDevice.createBond()) { 258 started = true; 259 Log.d("Bonding started."); 260 } else { 261 Log.e("Failed to bond with " + mDevice.getAliasName()); 262 mEventFacade.postEvent("Bond", mBadNews); 263 mService.unregisterReceiver(listeningDevices.remove("Bond" + mDeviceID)); 264 } 265 } 266 } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { 267 Log.d("Bond state changing."); 268 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 269 if (BluetoothFacade.deviceMatch(device, mDeviceID)) { 270 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); 271 Log.d("New state is " + state); 272 if (state == BluetoothDevice.BOND_BONDED) { 273 Log.d("Bonding with " + mDeviceID + " successful."); 274 mEventFacade.postEvent("Bond" + mDeviceID, mGoodNews); 275 mService.unregisterReceiver(listeningDevices.remove("Bond" + mDeviceID)); 276 } 277 } 278 } 279 } 280 } 281 282 public class ConnectStateChangeReceiver extends BroadcastReceiver { 283 private final String mDeviceID; 284 285 public ConnectStateChangeReceiver(String deviceID) { 286 mDeviceID = deviceID; 287 } 288 289 @Override 290 public void onReceive(Context context, Intent intent) { 291 // no matter what the action, just push it... 292 String action = intent.getAction(); 293 Log.d("Action received: " + action); 294 295 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 296 // Check if received the specified device 297 if (!BluetoothFacade.deviceMatch(device, mDeviceID)) { 298 Log.e("Action devices does match act: " + device + " exp " + mDeviceID); 299 return; 300 } 301 // Find the state. 302 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 303 if (state == -1) { 304 Log.e("Action does not have a state."); 305 return; 306 } 307 308 // Switch Only Necessary for Old implementation. Left in for backwards compatability. 309 int profile = -1; 310 switch (action) { 311 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: 312 profile = BluetoothProfile.A2DP; 313 break; 314 case BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED: 315 profile = BluetoothProfile.HID_HOST; 316 break; 317 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: 318 profile = BluetoothProfile.HEADSET; 319 break; 320 case BluetoothPan.ACTION_CONNECTION_STATE_CHANGED: 321 profile = BluetoothProfile.PAN; 322 break; 323 case BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED: 324 profile = BluetoothProfile.HEADSET_CLIENT; 325 break; 326 case BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED: 327 profile = BluetoothProfile.A2DP_SINK; 328 break; 329 case BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED: 330 profile = BluetoothProfile.PBAP_CLIENT; 331 break; 332 case BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED: 333 profile = BluetoothProfile.MAP_CLIENT; 334 break; 335 } 336 337 if (profile == -1) { 338 Log.e("Action does not match any given profiles " + action); 339 } 340 341 // The newer implementation will just post the Bundle with the literal event 342 // intead of the old implemenatation of posting BluetoothProfileConnectionStateChanged 343 // with the action inside of the Bundle. This makes for cleaner connection handling 344 // from test frameworks. Left the old implemenation in for backwards compatability. 345 346 // Post an event to Facade. 347 Bundle news = new Bundle(); 348 news.putInt("state", state); 349 news.putString("addr", device.getAddress()); 350 mEventFacade.postEvent(action, news); 351 352 news.putInt("profile", profile); 353 news.putString("action", action); 354 mEventFacade.postEvent("BluetoothProfileConnectionStateChanged", news); 355 } 356 } 357 358 /** 359 * Converts a given JSONArray to an ArrayList of Integers 360 * 361 * @param jsonArray the JSONArray to be converted 362 * @return <code>List<Integer></></code> the converted list of Integers 363 */ 364 private List<Integer> jsonArrayToIntegerList(JSONArray jsonArray) throws JSONException { 365 if (jsonArray == null) { 366 return null; 367 } 368 List<Integer> intArray = new ArrayList<Integer>(); 369 for (int i = 0; i < jsonArray.length(); i++) { 370 intArray.add(jsonArray.getInt(i)); 371 } 372 return intArray; 373 374 } 375 376 @Rpc(description = "Start monitoring state changes for input device.") 377 public void bluetoothStartConnectionStateChangeMonitor( 378 @RpcParameter(name = "deviceID", 379 description = "Name or MAC address of a bluetooth device.") 380 String deviceID) { 381 if (!mDeviceMonitorList.contains(deviceID)) { 382 ConnectStateChangeReceiver receiver = new ConnectStateChangeReceiver(deviceID); 383 mService.registerReceiver(receiver, mA2dpStateChangeFilter); 384 mService.registerReceiver(receiver, mA2dpSinkStateChangeFilter); 385 mService.registerReceiver(receiver, mHidStateChangeFilter); 386 mService.registerReceiver(receiver, mHspStateChangeFilter); 387 mService.registerReceiver(receiver, mHfpClientStateChangeFilter); 388 mService.registerReceiver(receiver, mPbapClientStateChangeFilter); 389 mService.registerReceiver(receiver, mPanStateChangeFilter); 390 mService.registerReceiver(receiver, mMapClientStateChangeFilter); 391 mService.registerReceiver(receiver, mMapStateChangeFilter); 392 listeningDevices.put("StateChangeListener:" + deviceID, receiver); 393 } 394 } 395 396 /** 397 * Connect on all the profiles to the given Bluetooth device 398 * 399 * @param device The <code>BluetoothDevice</code> to connect to 400 * @param deviceID Name (String) of the device to connect to 401 */ 402 private void connectProfile(BluetoothDevice device, String deviceID) { 403 mService.registerReceiver(mPairingHelper, mPairingFilter); 404 ParcelUuid[] deviceUuids = device.getUuids(); 405 Log.d("Device uuid is " + Arrays.toString(deviceUuids)); 406 if (deviceUuids == null) { 407 mEventFacade.postEvent("BluetoothProfileConnectionEvent", mBadNews); 408 } 409 Log.d("Connecting to " + device.getAliasName()); 410 if (BluetoothUuid.containsAnyUuid(BluetoothA2dpFacade.SINK_UUIDS, deviceUuids)) { 411 mA2dpProfile.a2dpConnect(device); 412 } 413 if (BluetoothUuid.containsAnyUuid(BluetoothA2dpSinkFacade.SOURCE_UUIDS, deviceUuids)) { 414 mA2dpSinkProfile.a2dpSinkConnect(device); 415 } 416 if (BluetoothUuid.containsAnyUuid(BluetoothHidFacade.UUIDS, deviceUuids)) { 417 mHidProfile.hidConnect(device); 418 } 419 if (BluetoothUuid.containsAnyUuid(BluetoothHspFacade.UUIDS, deviceUuids)) { 420 mHspProfile.hspConnect(device); 421 } 422 if (BluetoothUuid.containsAnyUuid(BluetoothHfpClientFacade.UUIDS, deviceUuids)) { 423 mHfpClientProfile.hfpClientConnect(device); 424 } 425 if (BluetoothUuid.containsAnyUuid(BluetoothMapClientFacade.MAP_UUIDS, deviceUuids)) { 426 mMapClientProfile.mapClientConnect(device); 427 } 428 if (BluetoothUuid.containsAnyUuid(BluetoothPanFacade.UUIDS, deviceUuids)) { 429 mPanProfile.panConnect(device); 430 } 431 if (BluetoothUuid.containsAnyUuid(BluetoothPbapClientFacade.UUIDS, deviceUuids)) { 432 mPbapClientProfile.pbapClientConnect(device); 433 } 434 mService.unregisterReceiver(mPairingHelper); 435 } 436 437 /** 438 * Disconnect on all available profiles from the given device 439 * 440 * @param device The <code>BluetoothDevice</code> to disconnect from 441 * @param deviceID Name (String) of the device to disconnect from 442 */ 443 private void disconnectProfiles(BluetoothDevice device, String deviceID) { 444 Log.d("Disconnecting device " + device); 445 // Blindly disconnect all profiles. We may not have some of them connected so that will be a 446 // null op. 447 mA2dpProfile.a2dpDisconnect(device); 448 mA2dpSinkProfile.a2dpSinkDisconnect(device); 449 mHidProfile.hidDisconnect(device); 450 mHidDeviceProfile.hidDeviceDisconnect(device); 451 mHspProfile.hspDisconnect(device); 452 mHfpClientProfile.hfpClientDisconnect(device); 453 mPbapClientProfile.pbapClientDisconnect(device); 454 mPanProfile.panDisconnect(device); 455 mMapClientProfile.mapClientDisconnect(device); 456 } 457 458 /** 459 * Disconnect from specific profiles provided in the given List of profiles. 460 * 461 * @param device The {@link BluetoothDevice} to disconnect from 462 * @param deviceID Name/BDADDR (String) of the device to disconnect from 463 * @param profileIds The list of profiles we want to disconnect on. 464 */ 465 private void disconnectProfiles(BluetoothDevice device, String deviceID, 466 List<Integer> profileIds) { 467 boolean result; 468 for (int profileId : profileIds) { 469 switch (profileId) { 470 case BluetoothProfile.A2DP_SINK: 471 mA2dpSinkProfile.a2dpSinkDisconnect(device); 472 break; 473 case BluetoothProfile.A2DP: 474 mA2dpProfile.a2dpDisconnect(device); 475 break; 476 case BluetoothProfile.HID_HOST: 477 mHidProfile.hidDisconnect(device); 478 break; 479 case BluetoothProfile.HID_DEVICE: 480 mHidDeviceProfile.hidDeviceDisconnect(device); 481 break; 482 case BluetoothProfile.HEADSET: 483 mHspProfile.hspDisconnect(device); 484 break; 485 case BluetoothProfile.HEADSET_CLIENT: 486 mHfpClientProfile.hfpClientDisconnect(device); 487 break; 488 case BluetoothProfile.PAN: 489 mPanProfile.panDisconnect(device); 490 break; 491 case BluetoothProfile.PBAP_CLIENT: 492 mPbapClientProfile.pbapClientDisconnect(device); 493 break; 494 case BluetoothProfile.MAP_CLIENT: 495 mMapClientProfile.mapDisconnect(device); 496 break; 497 default: 498 Log.d("Unknown Profile Id to disconnect from. Quitting"); 499 return; // returns on the first unknown profile it encounters. 500 } 501 } 502 } 503 504 @Rpc(description = "Start intercepting all bluetooth connection pop-ups.") 505 public void bluetoothStartPairingHelper( 506 @RpcParameter(name = "autoConfirm", 507 description = "Whether connection should be auto confirmed") 508 @RpcDefault("true") @RpcOptional 509 Boolean autoConfirm) { 510 Log.d("Staring pairing helper"); 511 mPairingHelper.setAutoConfirm(autoConfirm); 512 mService.registerReceiver(mPairingHelper, mPairingFilter); 513 } 514 515 @Rpc(description = "Return a list of devices connected through bluetooth") 516 public List<BluetoothDevice> bluetoothGetConnectedDevices() { 517 ArrayList<BluetoothDevice> results = new ArrayList<BluetoothDevice>(); 518 for (BluetoothDevice bd : mBluetoothAdapter.getBondedDevices()) { 519 if (bd.isConnected()) { 520 results.add(bd); 521 } 522 } 523 return results; 524 } 525 526 /** 527 * Return a list of service UUIDS supported by the bonded device. 528 * @param macAddress the String mac address of the bonded device. 529 * 530 * @return the String list of supported UUIDS. 531 * @throws Exception 532 */ 533 @Rpc(description = "Return a list of service UUIDS supported by the bonded device") 534 public List<String> bluetoothGetBondedDeviceUuids( 535 @RpcParameter(name = "macAddress") String macAddress) throws Exception { 536 BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(), 537 macAddress); 538 ArrayList<String> uuidStrings = new ArrayList<>(); 539 for (ParcelUuid parcelUuid : mDevice.getUuids()) { 540 uuidStrings.add(parcelUuid.toString()); 541 } 542 return uuidStrings; 543 } 544 545 @Rpc(description = "Return a list of devices connected through bluetooth LE") 546 public List<BluetoothDevice> bluetoothGetConnectedLeDevices(Integer profile) { 547 return mBluetoothManager.getConnectedDevices(profile); 548 } 549 550 @Rpc(description = "Bluetooth init Bond by Mac Address") 551 public boolean bluetoothBond(@RpcParameter(name = "macAddress") String macAddress) { 552 return mBluetoothAdapter.getRemoteDevice(macAddress).createBond(); 553 } 554 555 @Rpc(description = "Return true if a bluetooth device is connected.") 556 public Boolean bluetoothIsDeviceConnected(String deviceID) { 557 for (BluetoothDevice bd : mBluetoothAdapter.getBondedDevices()) { 558 if (BluetoothFacade.deviceMatch(bd, deviceID)) { 559 return bd.isConnected(); 560 } 561 } 562 return false; 563 } 564 565 @Rpc(description = "Return list of connected bluetooth devices over a profile", 566 returns = "List of devices connected over the profile") 567 public List<BluetoothDevice> bluetoothGetConnectedDevicesOnProfile( 568 @RpcParameter(name = "profileId", 569 description = "profileId same as BluetoothProfile") 570 Integer profileId) { 571 BluetoothProfile profile = null; 572 switch (profileId) { 573 case BluetoothProfile.A2DP_SINK: 574 return mA2dpSinkProfile.bluetoothA2dpSinkGetConnectedDevices(); 575 case BluetoothProfile.HEADSET_CLIENT: 576 return mHfpClientProfile.bluetoothHfpClientGetConnectedDevices(); 577 case BluetoothProfile.PBAP_CLIENT: 578 return mPbapClientProfile.bluetoothPbapClientGetConnectedDevices(); 579 case BluetoothProfile.MAP_CLIENT: 580 return mMapClientProfile.bluetoothMapClientGetConnectedDevices(); 581 default: 582 Log.w("Profile id " + profileId + " is not yet supported."); 583 return new ArrayList<BluetoothDevice>(); 584 } 585 } 586 587 @Rpc(description = "Connect to a specified device once it's discovered.", 588 returns = "Whether discovery started successfully.") 589 public Boolean bluetoothDiscoverAndConnect( 590 @RpcParameter(name = "deviceID", 591 description = "Name or MAC address of a bluetooth device.") 592 String deviceID) { 593 mBluetoothAdapter.cancelDiscovery(); 594 if (listeningDevices.containsKey(deviceID)) { 595 Log.d("This device is already in the process of discovery and connecting."); 596 return true; 597 } 598 DiscoverConnectReceiver receiver = new DiscoverConnectReceiver(deviceID); 599 listeningDevices.put("Connect" + deviceID, receiver); 600 mService.registerReceiver(receiver, mDiscoverConnectFilter); 601 return mBluetoothAdapter.startDiscovery(); 602 } 603 604 @Rpc(description = "Bond to a specified device once it's discovered.", 605 returns = "Whether discovery started successfully. ") 606 public Boolean bluetoothDiscoverAndBond( 607 @RpcParameter(name = "deviceID", 608 description = "Name or MAC address of a bluetooth device.") 609 String deviceID) { 610 mBluetoothAdapter.cancelDiscovery(); 611 if (listeningDevices.containsKey(deviceID)) { 612 Log.d("This device is already in the process of discovery and bonding."); 613 return true; 614 } 615 if (BluetoothFacade.deviceExists(mBluetoothAdapter.getBondedDevices(), deviceID)) { 616 Log.d("Device " + deviceID + " is already bonded."); 617 mEventFacade.postEvent("Bond" + deviceID, mGoodNews); 618 return true; 619 } 620 DiscoverBondReceiver receiver = new DiscoverBondReceiver(deviceID); 621 if (listeningDevices.containsKey("Bond" + deviceID)) { 622 mService.unregisterReceiver(listeningDevices.remove("Bond" + deviceID)); 623 } 624 listeningDevices.put("Bond" + deviceID, receiver); 625 mService.registerReceiver(receiver, mBondFilter); 626 Log.d("Start discovery for bonding."); 627 return mBluetoothAdapter.startDiscovery(); 628 } 629 630 @Rpc(description = "Unbond a device.", 631 returns = "Whether the device was successfully unbonded.") 632 public Boolean bluetoothUnbond( 633 @RpcParameter(name = "deviceID", 634 description = "Name or MAC address of a bluetooth device.") 635 String deviceID) throws Exception { 636 BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(), 637 deviceID); 638 return mDevice.removeBond(); 639 } 640 641 @Rpc(description = "Connect to a device that is already bonded.") 642 public void bluetoothConnectBonded( 643 @RpcParameter(name = "deviceID", 644 description = "Name or MAC address of a bluetooth device.") 645 String deviceID) throws Exception { 646 BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(), 647 deviceID); 648 connectProfile(mDevice, deviceID); 649 } 650 651 @Rpc(description = "Disconnect from a device that is already connected.") 652 public void bluetoothDisconnectConnected( 653 @RpcParameter(name = "deviceID", 654 description = "Name or MAC address of a bluetooth device.") 655 String deviceID) throws Exception { 656 BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(), 657 deviceID); 658 disconnectProfiles(mDevice, deviceID); 659 } 660 661 @Rpc(description = "Disconnect on a profile from a device that is already connected.") 662 public void bluetoothDisconnectConnectedProfile( 663 @RpcParameter(name = "deviceID", 664 description = "Name or MAC address of a bluetooth device.") 665 String deviceID, 666 @RpcParameter(name = "profileSet", 667 description = "List of profiles to disconnect from.") 668 JSONArray profileSet 669 ) throws Exception { 670 BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(), 671 deviceID); 672 disconnectProfiles(mDevice, deviceID, jsonArrayToIntegerList(profileSet)); 673 } 674 675 @Rpc(description = "Change permissions for a profile.") 676 public void bluetoothChangeProfileAccessPermission( 677 @RpcParameter(name = "deviceID", 678 description = "Name or MAC address of a bluetooth device.") 679 String deviceID, 680 @RpcParameter(name = "profileID", 681 description = "Number of Profile to change access permission") 682 Integer profileID, 683 @RpcParameter(name = "access", 684 description = "Access level 0 = Unknown, 1 = Allowed, 2 = Rejected") 685 Integer access 686 ) throws Exception { 687 if (access < 0 || access > 2) { 688 Log.w("Unsupported access level."); 689 return; 690 } 691 BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(), 692 deviceID); 693 switch (profileID) { 694 case BluetoothProfile.PBAP: 695 mDevice.setPhonebookAccessPermission(access); 696 break; 697 default: 698 Log.w("Unsupported profile access change."); 699 } 700 } 701 702 703 @Override 704 public void shutdown() { 705 for (BroadcastReceiver receiver : listeningDevices.values()) { 706 try { 707 mService.unregisterReceiver(receiver); 708 } catch (IllegalArgumentException ex) { 709 Log.e("Failed to unregister " + ex); 710 } 711 } 712 listeningDevices.clear(); 713 mService.unregisterReceiver(mPairingHelper); 714 } 715 } 716 717