BluetoothFacade.java revision f04335f899f2cce69f843692a3cb9cec229683c2
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 17package com.googlecode.android_scripting.facade.bluetooth; 18 19import android.app.Service; 20import android.bluetooth.BluetoothActivityEnergyInfo; 21import android.bluetooth.BluetoothAdapter; 22import android.bluetooth.BluetoothDevice; 23import android.content.BroadcastReceiver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.os.Bundle; 28import android.os.ParcelUuid; 29 30import com.googlecode.android_scripting.Log; 31import com.googlecode.android_scripting.MainThread; 32import com.googlecode.android_scripting.facade.EventFacade; 33import com.googlecode.android_scripting.facade.FacadeManager; 34import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 35import com.googlecode.android_scripting.rpc.Rpc; 36import com.googlecode.android_scripting.rpc.RpcDefault; 37import com.googlecode.android_scripting.rpc.RpcOptional; 38import com.googlecode.android_scripting.rpc.RpcParameter; 39 40import java.util.Collection; 41import java.util.HashMap; 42import java.util.Map; 43import java.util.Set; 44import java.util.concurrent.Callable; 45import java.util.concurrent.ConcurrentHashMap; 46 47/** 48 * Basic Bluetooth functions. 49 */ 50public 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 = "Get local Bluetooth device name") 231 public String bluetoothGetLocalName() { 232 return mBluetoothAdapter.getName(); 233 } 234 235 @Rpc(description = "Sets the Bluetooth visible device name", returns = "true on success") 236 public boolean bluetoothSetLocalName( 237 @RpcParameter(name = "name", description = "New local name") 238 String name) { 239 return mBluetoothAdapter.setName(name); 240 } 241 242 @Rpc(description = "Returns the hardware address of the local Bluetooth adapter. ") 243 public String bluetoothGetLocalAddress() { 244 return mBluetoothAdapter.getAddress(); 245 } 246 247 @Rpc(description = "Returns the UUIDs supported by local Bluetooth adapter.") 248 public ParcelUuid[] bluetoothGetLocalUuids() { 249 return mBluetoothAdapter.getUuids(); 250 } 251 252 @Rpc(description = "Gets the scan mode for the local dongle.\r\n" + "Return values:\r\n" 253 + "\t-1 when Bluetooth is disabled.\r\n" 254 + "\t0 if non discoverable and non connectable.\r\n" 255 + "\r1 connectable non discoverable." + "\r3 connectable and discoverable.") 256 public int bluetoothGetScanMode() { 257 if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF 258 || mBluetoothAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF) { 259 return -1; 260 } 261 switch (mBluetoothAdapter.getScanMode()) { 262 case BluetoothAdapter.SCAN_MODE_NONE: 263 return 0; 264 case BluetoothAdapter.SCAN_MODE_CONNECTABLE: 265 return 1; 266 case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: 267 return 3; 268 default: 269 return mBluetoothAdapter.getScanMode() - 20; 270 } 271 } 272 273 @Rpc(description = "Return the set of BluetoothDevice that are paired to the local adapter.") 274 public Set<BluetoothDevice> bluetoothGetBondedDevices() { 275 return mBluetoothAdapter.getBondedDevices(); 276 } 277 278 @Rpc(description = "Checks Bluetooth state.", returns = "True if Bluetooth is enabled.") 279 public Boolean bluetoothCheckState() { 280 return mBluetoothAdapter.isEnabled(); 281 } 282 283 @Rpc(description = "Factory reset bluetooth settings.", returns = "True if successful.") 284 public boolean bluetoothFactoryReset() { 285 return mBluetoothAdapter.factoryReset(); 286 } 287 288 @Rpc(description = "Toggle Bluetooth on and off.", returns = "True if Bluetooth is enabled.") 289 public Boolean bluetoothToggleState(@RpcParameter(name = "enabled") 290 @RpcOptional 291 Boolean enabled, 292 @RpcParameter(name = "prompt", 293 description = "Prompt the user to confirm changing the Bluetooth state.") 294 @RpcDefault("false") 295 Boolean prompt) { 296 mService.registerReceiver(mStateReceiver, 297 new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); 298 if (enabled == null) { 299 enabled = !bluetoothCheckState(); 300 } 301 if (enabled) { 302 return mBluetoothAdapter.enable(); 303 } else { 304 shutdown(); 305 return mBluetoothAdapter.disable(); 306 } 307 } 308 309 310 @Rpc(description = "Start the remote device discovery process. ", 311 returns = "true on success, false on error") 312 public Boolean bluetoothStartDiscovery() { 313 DiscoveredDevices.clear(); 314 mService.registerReceiver(mDiscoveryReceiver, discoveryFilter); 315 return mBluetoothAdapter.startDiscovery(); 316 } 317 318 @Rpc(description = "Cancel the current device discovery process.", 319 returns = "true on success, false on error") 320 public Boolean bluetoothCancelDiscovery() { 321 try { 322 mService.unregisterReceiver(mDiscoveryReceiver); 323 } catch (IllegalArgumentException e) { 324 Log.d("IllegalArgumentExeption found when trying to unregister reciever"); 325 } 326 return mBluetoothAdapter.cancelDiscovery(); 327 } 328 329 @Rpc(description = "If the local Bluetooth adapter is currently" 330 + "in the device discovery process.") 331 public Boolean bluetoothIsDiscovering() { 332 return mBluetoothAdapter.isDiscovering(); 333 } 334 335 @Rpc(description = "Get all the discovered bluetooth devices.") 336 public Collection<BluetoothDevice> bluetoothGetDiscoveredDevices() { 337 while (bluetoothIsDiscovering()) 338 ; 339 return DiscoveredDevices.values(); 340 } 341 342 @Rpc(description = "Enable or disable the Bluetooth HCI snoop log") 343 public boolean bluetoothConfigHciSnoopLog( 344 @RpcParameter(name = "value", description = "enable or disable log") 345 Boolean value 346 ) { 347 return mBluetoothAdapter.configHciSnoopLog(value); 348 } 349 350 @Rpc(description = "Get Bluetooth controller activity energy info.") 351 public String bluetoothGetControllerActivityEnergyInfo( 352 @RpcParameter(name = "value") 353 Integer value 354 ) { 355 BluetoothActivityEnergyInfo energyInfo = mBluetoothAdapter 356 .getControllerActivityEnergyInfo(value); 357 while (energyInfo == null) { 358 energyInfo = mBluetoothAdapter.getControllerActivityEnergyInfo(value); 359 } 360 return energyInfo.toString(); 361 } 362 363 @Rpc(description = "Return true if hardware has entries" + 364 "available for matching beacons.") 365 public boolean bluetoothIsHardwareTrackingFiltersAvailable() { 366 return mBluetoothAdapter.isHardwareTrackingFiltersAvailable(); 367 } 368 369 @Rpc(description = "Gets the current state of LE.") 370 public int bluetoothGetLeState() { 371 return mBluetoothAdapter.getLeState(); 372 } 373 374 @Rpc(description = "Enables BLE functionalities.") 375 public boolean bluetoothEnableBLE() { 376 mService.registerReceiver(mBleStateReceiver, 377 new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)); 378 return mBluetoothAdapter.enableBLE(); 379 } 380 381 @Rpc(description = "Disables BLE functionalities.") 382 public boolean bluetoothDisableBLE() { 383 mService.registerReceiver(mBleStateReceiver, 384 new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)); 385 return mBluetoothAdapter.disableBLE(); 386 } 387 388 @Rpc(description = "Listen for a Bluetooth LE State Change.") 389 public boolean bluetoothListenForBleStateChange() { 390 mService.registerReceiver(mBleStateReceiver, 391 new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)); 392 return true; 393 } 394 395 @Rpc(description = "Stop Listening for a Bluetooth LE State Change.") 396 public boolean bluetoothStopListeningForBleStateChange() { 397 mService.unregisterReceiver(mBleStateReceiver); 398 return true; 399 } 400 401 @Rpc(description = "Listen for Bluetooth State Changes.") 402 public boolean bluetoothStartListeningForAdapterStateChange() { 403 synchronized (mReceiverLock) { 404 if (mMultiStateReceiver != null) { 405 Log.e("Persistent Bluetooth Receiver State Change Listener Already Active"); 406 return false; 407 } 408 mMultiStateReceiver = new BluetoothStateReceiver(true); 409 mService.registerReceiver(mMultiStateReceiver, 410 new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); 411 } 412 return true; 413 } 414 415 @Rpc(description = "Stop Listening for Bluetooth State Changes.") 416 public boolean bluetoothStopListeningForAdapterStateChange() { 417 synchronized (mReceiverLock) { 418 if (mMultiStateReceiver == null) { 419 Log.d("No Persistent Bluetooth Receiever State Change Listener Found to Stop"); 420 return false; 421 } 422 mService.unregisterReceiver(mMultiStateReceiver); 423 mMultiStateReceiver = null; 424 } 425 return true; 426 } 427 428 @Override 429 public void shutdown() { 430 for (Map.Entry<String, BluetoothConnection> entry : connections.entrySet()) { 431 entry.getValue().stop(); 432 } 433 if (mMultiStateReceiver != null ) bluetoothStopListeningForAdapterStateChange(); 434 connections.clear(); 435 } 436} 437