1 /* 2 * Copyright (C) 2016 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.googlecode.android_scripting.facade.bluetooth; 18 19 import java.util.ArrayList; 20 import java.util.Collections; 21 import java.util.HashMap; 22 import java.util.List; 23 import java.util.Map; 24 25 import android.app.Service; 26 import android.bluetooth.BluetoothA2dp; 27 import android.bluetooth.BluetoothA2dpSink; 28 import android.bluetooth.BluetoothAdapter; 29 import android.bluetooth.BluetoothDevice; 30 import android.bluetooth.BluetoothManager; 31 import android.bluetooth.BluetoothHeadset; 32 import android.bluetooth.BluetoothHeadsetClient; 33 import android.bluetooth.BluetoothInputDevice; 34 import android.bluetooth.BluetoothPan; 35 import android.bluetooth.BluetoothUuid; 36 import android.content.BroadcastReceiver; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.IntentFilter; 40 import android.os.Bundle; 41 import android.os.ParcelUuid; 42 43 import com.googlecode.android_scripting.Log; 44 import com.googlecode.android_scripting.facade.EventFacade; 45 import com.googlecode.android_scripting.facade.FacadeManager; 46 import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 47 import com.googlecode.android_scripting.rpc.Rpc; 48 import com.googlecode.android_scripting.rpc.RpcParameter; 49 50 public class BluetoothConnectionFacade extends RpcReceiver { 51 52 private final Service mService; 53 private final Context mContext; 54 private final BluetoothAdapter mBluetoothAdapter; 55 private final BluetoothManager mBluetoothManager; 56 private final BluetoothPairingHelper mPairingHelper; 57 private final Map<String, BroadcastReceiver> listeningDevices; 58 private final EventFacade mEventFacade; 59 60 private final IntentFilter mDiscoverConnectFilter; 61 private final IntentFilter mPairingFilter; 62 private final IntentFilter mBondFilter; 63 private final IntentFilter mA2dpStateChangeFilter; 64 private final IntentFilter mA2dpSinkStateChangeFilter; 65 private final IntentFilter mHidStateChangeFilter; 66 private final IntentFilter mHspStateChangeFilter; 67 private final IntentFilter mHfpClientStateChangeFilter; 68 private final IntentFilter mPanStateChangeFilter; 69 70 private final Bundle mGoodNews; 71 private final Bundle mBadNews; 72 73 private BluetoothA2dpFacade mA2dpProfile; 74 private BluetoothA2dpSinkFacade mA2dpSinkProfile; 75 private BluetoothHidFacade mHidProfile; 76 private BluetoothHspFacade mHspProfile; 77 private BluetoothHfpClientFacade mHfpClientProfile; 78 private BluetoothPanFacade mPanProfile; 79 BluetoothConnectionFacade(FacadeManager manager)80 public BluetoothConnectionFacade(FacadeManager manager) { 81 super(manager); 82 mService = manager.getService(); 83 mContext = mService.getApplicationContext(); 84 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 85 mBluetoothManager = (BluetoothManager) mContext.getSystemService( 86 Service.BLUETOOTH_SERVICE); 87 // Use a synchronized map to avoid racing problems 88 listeningDevices = Collections.synchronizedMap(new HashMap<String, BroadcastReceiver>()); 89 90 mEventFacade = manager.getReceiver(EventFacade.class); 91 mPairingHelper = new BluetoothPairingHelper(mEventFacade); 92 mA2dpProfile = manager.getReceiver(BluetoothA2dpFacade.class); 93 mA2dpSinkProfile = manager.getReceiver(BluetoothA2dpSinkFacade.class); 94 mHidProfile = manager.getReceiver(BluetoothHidFacade.class); 95 mHspProfile = manager.getReceiver(BluetoothHspFacade.class); 96 mHfpClientProfile = manager.getReceiver(BluetoothHfpClientFacade.class); 97 mPanProfile = manager.getReceiver(BluetoothPanFacade.class); 98 99 mDiscoverConnectFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND); 100 mDiscoverConnectFilter.addAction(BluetoothDevice.ACTION_UUID); 101 mDiscoverConnectFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 102 103 mPairingFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST); 104 mPairingFilter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); 105 mPairingFilter.setPriority(999); 106 107 mBondFilter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 108 mBondFilter.addAction(BluetoothDevice.ACTION_FOUND); 109 mBondFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 110 111 mA2dpStateChangeFilter = new IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 112 mA2dpSinkStateChangeFilter = 113 new IntentFilter(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED); 114 mHidStateChangeFilter = 115 new IntentFilter(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); 116 mHspStateChangeFilter = new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 117 mHfpClientStateChangeFilter = 118 new IntentFilter(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 119 mPanStateChangeFilter = 120 new IntentFilter(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); 121 122 mGoodNews = new Bundle(); 123 mGoodNews.putBoolean("Status", true); 124 mBadNews = new Bundle(); 125 mBadNews.putBoolean("Status", false); 126 } 127 unregisterCachedListener(String listenerId)128 private void unregisterCachedListener(String listenerId) { 129 BroadcastReceiver listener = listeningDevices.remove(listenerId); 130 if (listener != null) { 131 mService.unregisterReceiver(listener); 132 } 133 } 134 135 /** 136 * Connect to a specific device upon its discovery 137 */ 138 public class DiscoverConnectReceiver extends BroadcastReceiver { 139 private final String mDeviceID; 140 private BluetoothDevice mDevice; 141 142 /** 143 * Constructor 144 * 145 * @param deviceID Either the device alias name or mac address. 146 * @param bond If true, bond the device only. 147 */ DiscoverConnectReceiver(String deviceID)148 public DiscoverConnectReceiver(String deviceID) { 149 super(); 150 mDeviceID = deviceID; 151 } 152 153 @Override onReceive(Context context, Intent intent)154 public void onReceive(Context context, Intent intent) { 155 String action = intent.getAction(); 156 // The specified device is found. 157 if (action.equals(BluetoothDevice.ACTION_FOUND)) { 158 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 159 if (BluetoothFacade.deviceMatch(device, mDeviceID)) { 160 Log.d("Found device " + device.getAliasName() + " for connection."); 161 mBluetoothAdapter.cancelDiscovery(); 162 mDevice = device; 163 } 164 // After discovery stops. 165 } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { 166 if (mDevice == null) { 167 Log.d("Device " + mDeviceID + " not discovered."); 168 mEventFacade.postEvent("Bond" + mDeviceID, mBadNews); 169 return; 170 } 171 boolean status = mDevice.fetchUuidsWithSdp(); 172 Log.d("Initiated ACL connection: " + status); 173 } else if (action.equals(BluetoothDevice.ACTION_UUID)) { 174 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 175 if (BluetoothFacade.deviceMatch(device, mDeviceID)) { 176 Log.d("Initiating connections."); 177 connectProfile(device, mDeviceID); 178 mService.unregisterReceiver(listeningDevices.remove("Connect" + mDeviceID)); 179 } 180 } 181 } 182 } 183 184 /** 185 * Connect to a specific device upon its discovery 186 */ 187 public class DiscoverBondReceiver extends BroadcastReceiver { 188 private final String mDeviceID; 189 private BluetoothDevice mDevice = null; 190 private boolean started = false; 191 192 /** 193 * Constructor 194 * 195 * @param deviceID Either the device alias name or Mac address. 196 */ DiscoverBondReceiver(String deviceID)197 public DiscoverBondReceiver(String deviceID) { 198 super(); 199 mDeviceID = deviceID; 200 } 201 202 @Override onReceive(Context context, Intent intent)203 public void onReceive(Context context, Intent intent) { 204 String action = intent.getAction(); 205 // The specified device is found. 206 if (action.equals(BluetoothDevice.ACTION_FOUND)) { 207 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 208 if (BluetoothFacade.deviceMatch(device, mDeviceID)) { 209 Log.d("Found device " + device.getAliasName() + " for connection."); 210 mBluetoothAdapter.cancelDiscovery(); 211 mDevice = device; 212 } 213 // After discovery stops. 214 } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { 215 if (mDevice == null) { 216 Log.d("Device " + mDeviceID + " was not discovered."); 217 mEventFacade.postEvent("Bond", mBadNews); 218 return; 219 } 220 // Attempt to initiate bonding. 221 if (!started) { 222 Log.d("Bond with " + mDevice.getAliasName()); 223 if (mDevice.createBond()) { 224 started = true; 225 Log.d("Bonding started."); 226 } else { 227 Log.e("Failed to bond with " + mDevice.getAliasName()); 228 mEventFacade.postEvent("Bond", mBadNews); 229 mService.unregisterReceiver(listeningDevices.remove("Bond" + mDeviceID)); 230 } 231 } 232 } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { 233 Log.d("Bond state changing."); 234 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 235 if (BluetoothFacade.deviceMatch(device, mDeviceID)) { 236 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); 237 Log.d("New state is " + state); 238 if (state == BluetoothDevice.BOND_BONDED) { 239 Log.d("Bonding with " + mDeviceID + " successful."); 240 mEventFacade.postEvent("Bond" + mDeviceID, mGoodNews); 241 mService.unregisterReceiver(listeningDevices.remove("Bond" + mDeviceID)); 242 } 243 } 244 } 245 } 246 } 247 248 public class ConnectStateChangeReceiver extends BroadcastReceiver { 249 private final String mDeviceID; 250 ConnectStateChangeReceiver(String deviceID)251 public ConnectStateChangeReceiver(String deviceID) { 252 mDeviceID = deviceID; 253 } 254 255 @Override onReceive(Context context, Intent intent)256 public void onReceive(Context context, Intent intent) { 257 String action = intent.getAction(); 258 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 259 // Check if received the specified device 260 if (!BluetoothFacade.deviceMatch(device, mDeviceID)) { 261 return; 262 } 263 if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) { 264 int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1); 265 if (state == BluetoothA2dp.STATE_CONNECTED) { 266 Bundle a2dpGoodNews = (Bundle) mGoodNews.clone(); 267 a2dpGoodNews.putString("Type", "a2dp"); 268 mEventFacade.postEvent("A2dpConnect" + mDeviceID, a2dpGoodNews); 269 unregisterCachedListener("A2dpConnecting" + mDeviceID); 270 } 271 } else if (action.equals(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED)) { 272 int state = intent.getIntExtra(BluetoothInputDevice.EXTRA_STATE, -1); 273 if (state == BluetoothInputDevice.STATE_CONNECTED) { 274 mEventFacade.postEvent("HidConnect" + mDeviceID, mGoodNews); 275 unregisterCachedListener("HidConnecting" + mDeviceID); 276 } 277 } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { 278 int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, -1); 279 if (state == BluetoothHeadset.STATE_CONNECTED) { 280 mEventFacade.postEvent("HspConnect" + mDeviceID, mGoodNews); 281 unregisterCachedListener("HspConnecting" + mDeviceID); 282 } 283 } else if (action.equals(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED)) { 284 int state = intent.getIntExtra(BluetoothPan.EXTRA_STATE, -1); 285 if (state == BluetoothPan.STATE_CONNECTED) { 286 mEventFacade.postEvent("PanConnect" + mDeviceID, mGoodNews); 287 unregisterCachedListener("PanConnecting" + mDeviceID); 288 } 289 } 290 } 291 } 292 connectProfile(BluetoothDevice device, String deviceID)293 private void connectProfile(BluetoothDevice device, String deviceID) { 294 mService.registerReceiver(mPairingHelper, mPairingFilter); 295 ParcelUuid[] deviceUuids = device.getUuids(); 296 Log.d("Device uuid is " + deviceUuids); 297 if (deviceUuids == null) { 298 mEventFacade.postEvent("BluetoothProfileConnectionEvent", mBadNews); 299 } 300 Log.d("Connecting to " + device.getAliasName()); 301 if (BluetoothUuid.containsAnyUuid(BluetoothA2dpFacade.SINK_UUIDS, deviceUuids)) { 302 boolean status = mA2dpProfile.a2dpConnect(device); 303 if (status) { 304 Log.d("Connecting A2dp..."); 305 ConnectStateChangeReceiver receiver = new ConnectStateChangeReceiver(deviceID); 306 mService.registerReceiver(receiver, mA2dpStateChangeFilter); 307 listeningDevices.put("A2dpConnecting" + deviceID, receiver); 308 } else { 309 Log.d("Failed starting A2dp connection."); 310 Bundle a2dpBadNews = (Bundle) mBadNews.clone(); 311 a2dpBadNews.putString("Type", "a2dp"); 312 mEventFacade.postEvent("Connect", a2dpBadNews); 313 } 314 } 315 if (BluetoothUuid.containsAnyUuid(BluetoothA2dpSinkFacade.SOURCE_UUIDS, deviceUuids)) { 316 boolean status = mA2dpSinkProfile.a2dpSinkConnect(device); 317 if (status) { 318 Log.d("Connecting A2dp Sink..."); 319 ConnectStateChangeReceiver receiver = new ConnectStateChangeReceiver(deviceID); 320 mService.registerReceiver(receiver, mA2dpSinkStateChangeFilter); 321 listeningDevices.put("A2dpSinkConnecting" + deviceID, receiver); 322 } else { 323 Log.d("Failed starting A2dp Sink connection."); 324 Bundle a2dpSinkBadNews = (Bundle) mBadNews.clone(); 325 a2dpSinkBadNews.putString("Type", "a2dpsink"); 326 mEventFacade.postEvent("Connect", a2dpSinkBadNews); 327 } 328 } 329 if (BluetoothUuid.containsAnyUuid(BluetoothHidFacade.UUIDS, deviceUuids)) { 330 boolean status = mHidProfile.hidConnect(device); 331 if (status) { 332 Log.d("Connecting Hid..."); 333 ConnectStateChangeReceiver receiver = new ConnectStateChangeReceiver(deviceID); 334 mService.registerReceiver(receiver, mHidStateChangeFilter); 335 listeningDevices.put("HidConnecting" + deviceID, receiver); 336 } else { 337 Log.d("Failed starting Hid connection."); 338 mEventFacade.postEvent("HidConnect" + deviceID, mBadNews); 339 } 340 } 341 if (BluetoothUuid.containsAnyUuid(BluetoothHspFacade.UUIDS, deviceUuids)) { 342 boolean status = mHspProfile.hspConnect(device); 343 if (status) { 344 Log.d("Connecting Hsp..."); 345 ConnectStateChangeReceiver receiver = new ConnectStateChangeReceiver(deviceID); 346 mService.registerReceiver(receiver, mHspStateChangeFilter); 347 listeningDevices.put("HspConnecting" + deviceID, receiver); 348 } else { 349 Log.d("Failed starting Hsp connection."); 350 mEventFacade.postEvent("HspConnect" + deviceID, mBadNews); 351 } 352 } 353 if (BluetoothUuid.containsAnyUuid(BluetoothHfpClientFacade.UUIDS, deviceUuids)) { 354 boolean status = mHfpClientProfile.hfpClientConnect(device); 355 if (status) { 356 Log.d("Connecting HFP Client ..."); 357 ConnectStateChangeReceiver receiver = new ConnectStateChangeReceiver(deviceID); 358 mService.registerReceiver(receiver, mHfpClientStateChangeFilter); 359 listeningDevices.put("HfpClientConnecting" + deviceID, receiver); 360 } else { 361 Log.d("Failed starting Hfp Client connection."); 362 mEventFacade.postEvent("HfpClientConnect" + deviceID, mBadNews); 363 } 364 } 365 if (BluetoothUuid.containsAnyUuid(BluetoothPanFacade.UUIDS, deviceUuids)) { 366 boolean status = mPanProfile.panConnect(device); 367 if (status) { 368 Log.d("Connecting Pan..."); 369 ConnectStateChangeReceiver receiver = new ConnectStateChangeReceiver(deviceID); 370 mService.registerReceiver(receiver, mPanStateChangeFilter); 371 listeningDevices.put("PanConnecting" + deviceID, receiver); 372 } else { 373 Log.d("Failed starting Pan connection."); 374 mEventFacade.postEvent("PanConnect" + deviceID, mBadNews); 375 } 376 } 377 mService.unregisterReceiver(mPairingHelper); 378 } 379 disconnectProfiles(BluetoothDevice device, String deviceID)380 private void disconnectProfiles(BluetoothDevice device, String deviceID) { 381 Log.d("Disconnecting device " + device); 382 // Blindly disconnect all profiles. We may not have some of them connected so that will be a 383 // null op. 384 mA2dpProfile.a2dpDisconnect(device); 385 mA2dpSinkProfile.a2dpSinkDisconnect(device); 386 mHidProfile.hidDisconnect(device); 387 mHspProfile.hspDisconnect(device); 388 mHfpClientProfile.hfpClientDisconnect(device); 389 mPanProfile.panDisconnect(device); 390 } 391 392 @Rpc(description = "Start intercepting all bluetooth connection pop-ups.") bluetoothStartPairingHelper()393 public void bluetoothStartPairingHelper() { 394 mService.registerReceiver(mPairingHelper, mPairingFilter); 395 } 396 397 @Rpc(description = "Return a list of devices connected through bluetooth") bluetoothGetConnectedDevices()398 public List<BluetoothDevice> bluetoothGetConnectedDevices() { 399 ArrayList<BluetoothDevice> results = new ArrayList<BluetoothDevice>(); 400 for (BluetoothDevice bd : mBluetoothAdapter.getBondedDevices()) { 401 if (bd.isConnected()) { 402 results.add(bd); 403 } 404 } 405 return results; 406 } 407 408 @Rpc(description = "Return a list of devices connected through bluetooth LE") bluetoothGetConnectedLeDevices(Integer profile)409 public List<BluetoothDevice> bluetoothGetConnectedLeDevices(Integer profile) { 410 return mBluetoothManager.getConnectedDevices(profile); 411 } 412 413 @Rpc(description = "Return true if a bluetooth device is connected.") bluetoothIsDeviceConnected(String deviceID)414 public Boolean bluetoothIsDeviceConnected(String deviceID) { 415 for (BluetoothDevice bd : mBluetoothAdapter.getBondedDevices()) { 416 if (BluetoothFacade.deviceMatch(bd, deviceID)) { 417 return bd.isConnected(); 418 } 419 } 420 return false; 421 } 422 423 @Rpc(description = "Connect to a specified device once it's discovered.", 424 returns = "Whether discovery started successfully.") bluetoothDiscoverAndConnect( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID)425 public Boolean bluetoothDiscoverAndConnect( 426 @RpcParameter(name = "deviceID", 427 description = "Name or MAC address of a bluetooth device.") 428 String deviceID) { 429 mBluetoothAdapter.cancelDiscovery(); 430 if (listeningDevices.containsKey(deviceID)) { 431 Log.d("This device is already in the process of discovery and connecting."); 432 return true; 433 } 434 DiscoverConnectReceiver receiver = new DiscoverConnectReceiver(deviceID); 435 listeningDevices.put("Connect" + deviceID, receiver); 436 mService.registerReceiver(receiver, mDiscoverConnectFilter); 437 return mBluetoothAdapter.startDiscovery(); 438 } 439 440 @Rpc(description = "Bond to a specified device once it's discovered.", 441 returns = "Whether discovery started successfully. ") bluetoothDiscoverAndBond( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID)442 public Boolean bluetoothDiscoverAndBond( 443 @RpcParameter(name = "deviceID", 444 description = "Name or MAC address of a bluetooth device.") 445 String deviceID) { 446 mBluetoothAdapter.cancelDiscovery(); 447 if (listeningDevices.containsKey(deviceID)) { 448 Log.d("This device is already in the process of discovery and bonding."); 449 return true; 450 } 451 if (BluetoothFacade.deviceExists(mBluetoothAdapter.getBondedDevices(), deviceID)) { 452 Log.d("Device " + deviceID + " is already bonded."); 453 mEventFacade.postEvent("Bond" + deviceID, mGoodNews); 454 return true; 455 } 456 DiscoverBondReceiver receiver = new DiscoverBondReceiver(deviceID); 457 if (listeningDevices.containsKey("Bond" + deviceID)) { 458 mService.unregisterReceiver(listeningDevices.remove("Bond" + deviceID)); 459 } 460 listeningDevices.put("Bond" + deviceID, receiver); 461 mService.registerReceiver(receiver, mBondFilter); 462 Log.d("Start discovery for bonding."); 463 return mBluetoothAdapter.startDiscovery(); 464 } 465 466 @Rpc(description = "Unbond a device.", 467 returns = "Whether the device was successfully unbonded.") bluetoothUnbond( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID)468 public Boolean bluetoothUnbond( 469 @RpcParameter(name = "deviceID", 470 description = "Name or MAC address of a bluetooth device.") 471 String deviceID) throws Exception { 472 BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(), 473 deviceID); 474 return mDevice.removeBond(); 475 } 476 477 @Rpc(description = "Connect to a device that is already bonded.") bluetoothConnectBonded( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID)478 public void bluetoothConnectBonded( 479 @RpcParameter(name = "deviceID", 480 description = "Name or MAC address of a bluetooth device.") 481 String deviceID) throws Exception { 482 BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(), 483 deviceID); 484 connectProfile(mDevice, deviceID); 485 } 486 487 // TODO: Split the disconnect RPC by profiles as well for granular control over the ACL 488 @Rpc(description = "Disconnect from a device that is already connected.") bluetoothDisconnectConnected( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID)489 public void bluetoothDisconnectConnected( 490 @RpcParameter(name = "deviceID", 491 description = "Name or MAC address of a bluetooth device.") 492 String deviceID) throws Exception { 493 BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(), 494 deviceID); 495 disconnectProfiles(mDevice, deviceID); 496 } 497 498 @Override shutdown()499 public void shutdown() { 500 for(BroadcastReceiver receiver : listeningDevices.values()) { 501 mService.unregisterReceiver(receiver); 502 } 503 listeningDevices.clear(); 504 mService.unregisterReceiver(mPairingHelper); 505 } 506 } 507