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.BluetoothActivityEnergyInfo; 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.os.Bundle; 28 import android.os.ParcelUuid; 29 30 import com.googlecode.android_scripting.Log; 31 import com.googlecode.android_scripting.MainThread; 32 import com.googlecode.android_scripting.facade.EventFacade; 33 import com.googlecode.android_scripting.facade.FacadeManager; 34 import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 35 import com.googlecode.android_scripting.rpc.Rpc; 36 import com.googlecode.android_scripting.rpc.RpcDefault; 37 import com.googlecode.android_scripting.rpc.RpcOptional; 38 import com.googlecode.android_scripting.rpc.RpcParameter; 39 40 import java.util.Collection; 41 import java.util.HashMap; 42 import java.util.Map; 43 import java.util.Set; 44 import java.util.concurrent.Callable; 45 import java.util.concurrent.ConcurrentHashMap; 46 47 /** 48 * Basic Bluetooth functions. 49 */ 50 public class BluetoothFacade extends RpcReceiver { 51 private final Service mService; 52 private final BroadcastReceiver mDiscoveryReceiver; 53 private final IntentFilter discoveryFilter; 54 private final EventFacade mEventFacade; 55 private final BluetoothStateReceiver mStateReceiver; 56 private static final Object mReceiverLock = new Object(); 57 private BluetoothStateReceiver mMultiStateReceiver; 58 private final BleStateReceiver mBleStateReceiver; 59 private Map<String, BluetoothConnection> connections = 60 new HashMap<String, BluetoothConnection>(); 61 private BluetoothAdapter mBluetoothAdapter; 62 63 public static ConcurrentHashMap<String, BluetoothDevice> DiscoveredDevices; 64 65 public BluetoothFacade(FacadeManager manager) { 66 super(manager); 67 mBluetoothAdapter = MainThread.run(manager.getService(), 68 new Callable<BluetoothAdapter>() { 69 @Override 70 public BluetoothAdapter call() throws Exception { 71 return BluetoothAdapter.getDefaultAdapter(); 72 } 73 }); 74 mEventFacade = manager.getReceiver(EventFacade.class); 75 mService = manager.getService(); 76 77 DiscoveredDevices = new ConcurrentHashMap<String, BluetoothDevice>(); 78 discoveryFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND); 79 discoveryFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 80 mDiscoveryReceiver = new DiscoveryCacheReceiver(); 81 mStateReceiver = new BluetoothStateReceiver(); 82 mMultiStateReceiver = null; 83 mBleStateReceiver = new BleStateReceiver(); 84 } 85 86 class DiscoveryCacheReceiver extends BroadcastReceiver { 87 @Override 88 public void onReceive(Context context, Intent intent) { 89 String action = intent.getAction(); 90 if (action.equals(BluetoothDevice.ACTION_FOUND)) { 91 BluetoothDevice device = intent.getParcelableExtra( 92 BluetoothDevice.EXTRA_DEVICE); 93 Log.d("Found device " + device.getAliasName()); 94 if (!DiscoveredDevices.containsKey(device.getAddress())) { 95 String name = device.getAliasName(); 96 if (name != null) { 97 DiscoveredDevices.put(device.getAliasName(), device); 98 } 99 DiscoveredDevices.put(device.getAddress(), device); 100 } 101 } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { 102 mEventFacade.postEvent("BluetoothDiscoveryFinished", new Bundle()); 103 mService.unregisterReceiver(mDiscoveryReceiver); 104 } 105 } 106 } 107 108 class BluetoothStateReceiver extends BroadcastReceiver { 109 110 private final boolean mIsMultiBroadcast; 111 112 public BluetoothStateReceiver() { 113 mIsMultiBroadcast = false; 114 } 115 116 public BluetoothStateReceiver(boolean isMultiBroadcast) { 117 mIsMultiBroadcast = isMultiBroadcast; 118 } 119 120 @Override 121 public void onReceive(Context context, Intent intent) { 122 String action = intent.getAction(); 123 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 124 final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); 125 Bundle msg = new Bundle(); 126 if (state == BluetoothAdapter.STATE_ON) { 127 msg.putString("State", "ON"); 128 mEventFacade.postEvent("BluetoothStateChangedOn", msg); 129 if (!mIsMultiBroadcast) { 130 mService.unregisterReceiver(mStateReceiver); 131 } 132 } else if(state == BluetoothAdapter.STATE_OFF) { 133 msg.putString("State", "OFF"); 134 mEventFacade.postEvent("BluetoothStateChangedOff", msg); 135 if (!mIsMultiBroadcast) { 136 mService.unregisterReceiver(mStateReceiver); 137 } 138 } 139 msg.clear(); 140 } 141 } 142 } 143 144 class BleStateReceiver extends BroadcastReceiver { 145 146 @Override 147 public void onReceive(Context context, Intent intent) { 148 String action = intent.getAction(); 149 if (action.equals(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)) { 150 int state = mBluetoothAdapter.getLeState(); 151 if (state == BluetoothAdapter.STATE_BLE_ON) { 152 mEventFacade.postEvent("BleStateChangedOn", new Bundle()); 153 mService.unregisterReceiver(mBleStateReceiver); 154 } else if (state == BluetoothAdapter.STATE_OFF) { 155 mEventFacade.postEvent("BleStateChangedOff", new Bundle()); 156 mService.unregisterReceiver(mBleStateReceiver); 157 } 158 } 159 } 160 } 161 162 163 public static boolean deviceMatch(BluetoothDevice device, String deviceID) { 164 return deviceID.equals(device.getAliasName()) || deviceID.equals( 165 device.getAddress()); 166 } 167 168 /** 169 * Get Bluetooth device. 170 * @param devices - HashMap of Device Address and Bluetooth device name. 171 * @param device - name of the device. 172 * @return the device name if it exits. 173 */ 174 public static <T> BluetoothDevice getDevice( 175 ConcurrentHashMap<String, T> devices, String device) 176 throws Exception { 177 if (devices.containsKey(device)) { 178 return (BluetoothDevice) devices.get(device); 179 } else { 180 throw new Exception("Can't find device " + device); 181 } 182 } 183 184 /** 185 * Get Bluetooth device. 186 * @param devices - Collection of device IDs. 187 * @param deviceID - ID of the desired device. 188 * @return the Bluetooth device if the device ID is matched. 189 */ 190 public static BluetoothDevice getDevice( 191 Collection<BluetoothDevice> devices, String deviceID) 192 throws Exception { 193 Log.d("Looking for " + deviceID); 194 for (BluetoothDevice bd : devices) { 195 Log.d(bd.getAliasName() + " " + bd.getAddress()); 196 if (deviceMatch(bd, deviceID)) { 197 Log.d("Found match " + bd.getAliasName() + " " + bd.getAddress()); 198 return bd; 199 } 200 } 201 throw new Exception("Can't find device " + deviceID); 202 } 203 204 /** 205 * Verify device existence. 206 * @param devices - Collection of device IDs. 207 * @param deviceID - ID of the desired device. 208 * @return if the device Exists or not. 209 */ 210 public static boolean deviceExists( 211 Collection<BluetoothDevice> devices, String deviceID) { 212 for (BluetoothDevice bd : devices) { 213 if (deviceMatch(bd, deviceID)) { 214 Log.d("Found match " + bd.getAliasName() + " " + bd.getAddress()); 215 return true; 216 } 217 } 218 return false; 219 } 220 221 @Rpc(description = "Requests that the device be made connectable.") 222 public void bluetoothMakeConnectable() { 223 mBluetoothAdapter 224 .setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); 225 } 226 227 @Rpc(description = "Requests that the device be discoverable for Bluetooth connections.") 228 public void bluetoothMakeDiscoverable( 229 @RpcParameter(name = "duration", 230 description = "period of time, in seconds," 231 + "during which the device should be discoverable") 232 @RpcDefault("300") 233 Integer duration) { 234 Log.d("Making discoverable for " + duration + " seconds.\n"); 235 mBluetoothAdapter.setScanMode( 236 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, duration); 237 } 238 239 @Rpc(description = "Requests that the device be not discoverable.") 240 public void bluetoothMakeUndiscoverable() { 241 Log.d("Making undiscoverable\n"); 242 mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_NONE); 243 } 244 245 @Rpc(description = "Queries a remote device for it's name or null if it can't be resolved") 246 public String bluetoothGetRemoteDeviceName( 247 @RpcParameter(name = "address", description = "Bluetooth Address For Target Device") 248 String address) { 249 try { 250 BluetoothDevice mDevice; 251 mDevice = mBluetoothAdapter.getRemoteDevice(address); 252 return mDevice.getName(); 253 } catch (Exception e) { 254 return null; 255 } 256 } 257 258 @Rpc(description = "Fetch UUIDS with SDP") 259 public boolean bluetoothFetchUuidsWithSdp( 260 @RpcParameter(name = "address", description = "Bluetooth Address For Target Device") 261 String address) { 262 try { 263 BluetoothDevice mDevice; 264 mDevice = mBluetoothAdapter.getRemoteDevice(address); 265 return mDevice.fetchUuidsWithSdp(); 266 } catch (Exception e) { 267 return false; 268 } 269 } 270 271 @Rpc(description = "Get local Bluetooth device name") 272 public String bluetoothGetLocalName() { 273 return mBluetoothAdapter.getName(); 274 } 275 276 @Rpc(description = "Sets the Bluetooth visible device name", returns = "true on success") 277 public boolean bluetoothSetLocalName( 278 @RpcParameter(name = "name", description = "New local name") 279 String name) { 280 return mBluetoothAdapter.setName(name); 281 } 282 283 @Rpc(description = "Returns the hardware address of the local Bluetooth adapter. ") 284 public String bluetoothGetLocalAddress() { 285 return mBluetoothAdapter.getAddress(); 286 } 287 288 @Rpc(description = "Returns the UUIDs supported by local Bluetooth adapter.") 289 public ParcelUuid[] bluetoothGetLocalUuids() { 290 return mBluetoothAdapter.getUuids(); 291 } 292 293 @Rpc(description = "Gets the scan mode for the local dongle.\r\n" + "Return values:\r\n" 294 + "\t-1 when Bluetooth is disabled.\r\n" 295 + "\t0 if non discoverable and non connectable.\r\n" 296 + "\r1 connectable non discoverable." + "\r3 connectable and discoverable.") 297 public int bluetoothGetScanMode() { 298 if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF 299 || mBluetoothAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF) { 300 return -1; 301 } 302 switch (mBluetoothAdapter.getScanMode()) { 303 case BluetoothAdapter.SCAN_MODE_NONE: 304 return 0; 305 case BluetoothAdapter.SCAN_MODE_CONNECTABLE: 306 return 1; 307 case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: 308 return 3; 309 default: 310 return mBluetoothAdapter.getScanMode() - 20; 311 } 312 } 313 314 @Rpc(description = "Return the set of BluetoothDevice that are paired to the local adapter.") 315 public Set<BluetoothDevice> bluetoothGetBondedDevices() { 316 return mBluetoothAdapter.getBondedDevices(); 317 } 318 319 @Rpc(description = "Checks Bluetooth state.", returns = "True if Bluetooth is enabled.") 320 public Boolean bluetoothCheckState() { 321 return mBluetoothAdapter.isEnabled(); 322 } 323 324 @Rpc(description = "Factory reset bluetooth settings.", returns = "True if successful.") 325 public boolean bluetoothFactoryReset() { 326 return mBluetoothAdapter.factoryReset(); 327 } 328 329 @Rpc(description = "Toggle Bluetooth on and off.", returns = "True if Bluetooth is enabled.") 330 public Boolean bluetoothToggleState(@RpcParameter(name = "enabled") 331 @RpcOptional 332 Boolean enabled, 333 @RpcParameter(name = "prompt", 334 description = "Prompt the user to confirm changing the Bluetooth state.") 335 @RpcDefault("false") 336 Boolean prompt) { 337 mService.registerReceiver(mStateReceiver, 338 new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); 339 if (enabled == null) { 340 enabled = !bluetoothCheckState(); 341 } 342 if (enabled) { 343 return mBluetoothAdapter.enable(); 344 } else { 345 shutdown(); 346 return mBluetoothAdapter.disable(); 347 } 348 } 349 350 351 @Rpc(description = "Start the remote device discovery process. ", 352 returns = "true on success, false on error") 353 public Boolean bluetoothStartDiscovery() { 354 DiscoveredDevices.clear(); 355 mService.registerReceiver(mDiscoveryReceiver, discoveryFilter); 356 return mBluetoothAdapter.startDiscovery(); 357 } 358 359 @Rpc(description = "Cancel the current device discovery process.", 360 returns = "true on success, false on error") 361 public Boolean bluetoothCancelDiscovery() { 362 try { 363 mService.unregisterReceiver(mDiscoveryReceiver); 364 } catch (IllegalArgumentException e) { 365 Log.d("IllegalArgumentExeption found when trying to unregister reciever"); 366 } 367 return mBluetoothAdapter.cancelDiscovery(); 368 } 369 370 @Rpc(description = "If the local Bluetooth adapter is currently" 371 + "in the device discovery process.") 372 public Boolean bluetoothIsDiscovering() { 373 return mBluetoothAdapter.isDiscovering(); 374 } 375 376 @Rpc(description = "Get all the discovered bluetooth devices.") 377 public Collection<BluetoothDevice> bluetoothGetDiscoveredDevices() { 378 while (bluetoothIsDiscovering()) 379 ; 380 return DiscoveredDevices.values(); 381 } 382 383 @Rpc(description = "Get Bluetooth controller activity energy info.") 384 public String bluetoothGetControllerActivityEnergyInfo( 385 @RpcParameter(name = "value") 386 Integer value 387 ) { 388 BluetoothActivityEnergyInfo energyInfo = mBluetoothAdapter 389 .getControllerActivityEnergyInfo(value); 390 while (energyInfo == null) { 391 energyInfo = mBluetoothAdapter.getControllerActivityEnergyInfo(value); 392 } 393 return energyInfo.toString(); 394 } 395 396 @Rpc(description = "Return true if hardware has entries" + 397 "available for matching beacons.") 398 public boolean bluetoothIsHardwareTrackingFiltersAvailable() { 399 return mBluetoothAdapter.isHardwareTrackingFiltersAvailable(); 400 } 401 402 /** 403 * Return true if LE 2M PHY feature is supported. 404 * 405 * @return true if chipset supports LE 2M PHY feature 406 */ 407 @Rpc(description = "Return true if LE 2M PHY feature is supported") 408 public boolean bluetoothIsLe2MPhySupported() { 409 return mBluetoothAdapter.isLe2MPhySupported(); 410 } 411 412 /** 413 * Return true if LE Coded PHY feature is supported. 414 * 415 * @return true if chipset supports LE Coded PHY feature 416 */ 417 @Rpc(description = "Return true if LE Coded PHY feature is supported") 418 public boolean bluetoothIsLeCodedPhySupported() { 419 return mBluetoothAdapter.isLeCodedPhySupported(); 420 } 421 422 /** 423 * Return true if LE Extended Advertising feature is supported. 424 * 425 * @return true if chipset supports LE Extended Advertising feature 426 */ 427 @Rpc(description = "Return true if LE Extended Advertising is supported") 428 public boolean bluetoothIsLeExtendedAdvertisingSupported() { 429 return mBluetoothAdapter.isLeExtendedAdvertisingSupported(); 430 } 431 432 /** 433 * Return true if LE Periodic Advertising feature is supported. 434 * 435 * @return true if chipset supports LE Periodic Advertising feature 436 */ 437 @Rpc(description = "Return true if LE Periodic Advertising is supported") 438 public boolean bluetoothIsLePeriodicAdvertisingSupported() { 439 return mBluetoothAdapter.isLePeriodicAdvertisingSupported(); 440 } 441 442 /** 443 * Return the maximum LE advertising data length, 444 * if LE Extended Advertising feature is supported. 445 * 446 * @return the maximum LE advertising data length. 447 */ 448 @Rpc(description = "Return the maximum LE advertising data length") 449 public int bluetoothGetLeMaximumAdvertisingDataLength() { 450 return mBluetoothAdapter.getLeMaximumAdvertisingDataLength(); 451 } 452 453 @Rpc(description = "Gets the current state of LE.") 454 public int bluetoothGetLeState() { 455 return mBluetoothAdapter.getLeState(); 456 } 457 458 @Rpc(description = "Enables BLE functionalities.") 459 public boolean bluetoothEnableBLE() { 460 mService.registerReceiver(mBleStateReceiver, 461 new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)); 462 return mBluetoothAdapter.enableBLE(); 463 } 464 465 @Rpc(description = "Disables BLE functionalities.") 466 public boolean bluetoothDisableBLE() { 467 mService.registerReceiver(mBleStateReceiver, 468 new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)); 469 return mBluetoothAdapter.disableBLE(); 470 } 471 472 @Rpc(description = "Listen for a Bluetooth LE State Change.") 473 public boolean bluetoothListenForBleStateChange() { 474 mService.registerReceiver(mBleStateReceiver, 475 new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)); 476 return true; 477 } 478 479 @Rpc(description = "Stop Listening for a Bluetooth LE State Change.") 480 public boolean bluetoothStopListeningForBleStateChange() { 481 mService.unregisterReceiver(mBleStateReceiver); 482 return true; 483 } 484 485 @Rpc(description = "Listen for Bluetooth State Changes.") 486 public boolean bluetoothStartListeningForAdapterStateChange() { 487 synchronized (mReceiverLock) { 488 if (mMultiStateReceiver != null) { 489 Log.e("Persistent Bluetooth Receiver State Change Listener Already Active"); 490 return false; 491 } 492 mMultiStateReceiver = new BluetoothStateReceiver(true); 493 mService.registerReceiver(mMultiStateReceiver, 494 new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); 495 } 496 return true; 497 } 498 499 @Rpc(description = "Stop Listening for Bluetooth State Changes.") 500 public boolean bluetoothStopListeningForAdapterStateChange() { 501 synchronized (mReceiverLock) { 502 if (mMultiStateReceiver == null) { 503 Log.d("No Persistent Bluetooth Receiever State Change Listener Found to Stop"); 504 return false; 505 } 506 mService.unregisterReceiver(mMultiStateReceiver); 507 mMultiStateReceiver = null; 508 } 509 return true; 510 } 511 512 @Override 513 public void shutdown() { 514 for (Map.Entry<String, 515 BluetoothConnection> entry : connections.entrySet()) { 516 entry.getValue().stop(); 517 } 518 if (mMultiStateReceiver != null ) bluetoothStopListeningForAdapterStateChange(); 519 connections.clear(); 520 } 521 } 522