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