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 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 = "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 mBluetoothAdapter.enable(); 303 } else { 304 shutdown(); 305 mBluetoothAdapter.disable(); 306 } 307 return enabled; 308 } 309 310 311 @Rpc(description = "Start the remote device discovery process. ", 312 returns = "true on success, false on error") 313 public Boolean bluetoothStartDiscovery() { 314 DiscoveredDevices.clear(); 315 mService.registerReceiver(mDiscoveryReceiver, discoveryFilter); 316 return mBluetoothAdapter.startDiscovery(); 317 } 318 319 @Rpc(description = "Cancel the current device discovery process.", 320 returns = "true on success, false on error") 321 public Boolean bluetoothCancelDiscovery() { 322 try { 323 mService.unregisterReceiver(mDiscoveryReceiver); 324 } catch (IllegalArgumentException e) { 325 Log.d("IllegalArgumentExeption found when trying to unregister reciever"); 326 } 327 return mBluetoothAdapter.cancelDiscovery(); 328 } 329 330 @Rpc(description = "If the local Bluetooth adapter is currently" 331 + "in the device discovery process.") 332 public Boolean bluetoothIsDiscovering() { 333 return mBluetoothAdapter.isDiscovering(); 334 } 335 336 @Rpc(description = "Get all the discovered bluetooth devices.") 337 public Collection<BluetoothDevice> bluetoothGetDiscoveredDevices() { 338 while (bluetoothIsDiscovering()) 339 ; 340 return DiscoveredDevices.values(); 341 } 342 343 @Rpc(description = "Enable or disable the Bluetooth HCI snoop log") 344 public boolean bluetoothConfigHciSnoopLog( 345 @RpcParameter(name = "value", description = "enable or disable log") 346 Boolean value 347 ) { 348 return mBluetoothAdapter.configHciSnoopLog(value); 349 } 350 351 @Rpc(description = "Get Bluetooth controller activity energy info.") 352 public String bluetoothGetControllerActivityEnergyInfo( 353 @RpcParameter(name = "value") 354 Integer value 355 ) { 356 BluetoothActivityEnergyInfo energyInfo = mBluetoothAdapter 357 .getControllerActivityEnergyInfo(value); 358 while (energyInfo == null) { 359 energyInfo = mBluetoothAdapter.getControllerActivityEnergyInfo(value); 360 } 361 return energyInfo.toString(); 362 } 363 364 @Rpc(description = "Return true if hardware has entries" + 365 "available for matching beacons.") 366 public boolean bluetoothIsHardwareTrackingFiltersAvailable() { 367 return mBluetoothAdapter.isHardwareTrackingFiltersAvailable(); 368 } 369 370 @Rpc(description = "Gets the current state of LE.") 371 public int bluetoothGetLeState() { 372 return mBluetoothAdapter.getLeState(); 373 } 374 375 @Rpc(description = "Enables BLE functionalities.") 376 public boolean bluetoothEnableBLE() { 377 mService.registerReceiver(mBleStateReceiver, 378 new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)); 379 return mBluetoothAdapter.enableBLE(); 380 } 381 382 @Rpc(description = "Disables BLE functionalities.") 383 public boolean bluetoothDisableBLE() { 384 mService.registerReceiver(mBleStateReceiver, 385 new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)); 386 return mBluetoothAdapter.disableBLE(); 387 } 388 389 @Rpc(description = "Listen for Bluetooth State Changes.") 390 public boolean bluetoothStartListeningForAdapterStateChange() { 391 synchronized (mReceiverLock) { 392 if (mMultiStateReceiver != null) { 393 Log.e("Persistent Bluetooth Receiver State Change Listener Already Active"); 394 return false; 395 } 396 mMultiStateReceiver = new BluetoothStateReceiver(true); 397 mService.registerReceiver(mMultiStateReceiver, 398 new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); 399 } 400 return true; 401 } 402 403 @Rpc(description = "Stop Listening for Bluetooth State Changes.") 404 public boolean bluetoothStopListeningForAdapterStateChange() { 405 synchronized (mReceiverLock) { 406 if (mMultiStateReceiver == null) { 407 Log.d("No Persistent Bluetooth Receiever State Change Listener Found to Stop"); 408 return false; 409 } 410 mService.unregisterReceiver(mMultiStateReceiver); 411 mMultiStateReceiver = null; 412 } 413 return true; 414 } 415 416 @Override 417 public void shutdown() { 418 for (Map.Entry<String, BluetoothConnection> entry : connections.entrySet()) { 419 entry.getValue().stop(); 420 } 421 if (mMultiStateReceiver != null ) bluetoothStopListeningForAdapterStateChange(); 422 connections.clear(); 423 } 424 } 425