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