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 final BleStateReceiver mBleStateReceiver; 57 private Map<String, BluetoothConnection> connections = 58 new HashMap<String, BluetoothConnection>(); 59 private BluetoothAdapter mBluetoothAdapter; 60 61 public static ConcurrentHashMap<String, BluetoothDevice> DiscoveredDevices; 62 63 public BluetoothFacade(FacadeManager manager) { 64 super(manager); 65 mBluetoothAdapter = MainThread.run(manager.getService(), new Callable<BluetoothAdapter>() { 66 @Override 67 public BluetoothAdapter call() throws Exception { 68 return BluetoothAdapter.getDefaultAdapter(); 69 } 70 }); 71 mEventFacade = manager.getReceiver(EventFacade.class); 72 mService = manager.getService(); 73 74 DiscoveredDevices = new ConcurrentHashMap<String, BluetoothDevice>(); 75 discoveryFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND); 76 discoveryFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 77 mDiscoveryReceiver = new DiscoveryCacheReceiver(); 78 mStateReceiver = new BluetoothStateReceiver(); 79 mBleStateReceiver = new BleStateReceiver(); 80 } 81 82 class DiscoveryCacheReceiver extends BroadcastReceiver { 83 @Override 84 public void onReceive(Context context, Intent intent) { 85 String action = intent.getAction(); 86 if (action.equals(BluetoothDevice.ACTION_FOUND)) { 87 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 88 Log.d("Found device " + device.getAliasName()); 89 if (!DiscoveredDevices.containsKey(device.getAddress())) { 90 String name = device.getAliasName(); 91 if (name != null) { 92 DiscoveredDevices.put(device.getAliasName(), device); 93 } 94 DiscoveredDevices.put(device.getAddress(), device); 95 } 96 } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { 97 mEventFacade.postEvent("BluetoothDiscoveryFinished", new Bundle()); 98 mService.unregisterReceiver(mDiscoveryReceiver); 99 } 100 } 101 } 102 103 class BluetoothStateReceiver extends BroadcastReceiver { 104 105 @Override 106 public void onReceive(Context context, Intent intent) { 107 String action = intent.getAction(); 108 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 109 final int state = mBluetoothAdapter.getState(); 110 Bundle msg = new Bundle(); 111 if (state == BluetoothAdapter.STATE_ON) { 112 msg.putString("State", "ON"); 113 mEventFacade.postEvent("BluetoothStateChangedOn", msg); 114 mService.unregisterReceiver(mStateReceiver); 115 } else if(state == BluetoothAdapter.STATE_OFF) { 116 msg.putString("State", "OFF"); 117 mEventFacade.postEvent("BluetoothStateChangedOff", msg); 118 mService.unregisterReceiver(mStateReceiver); 119 } 120 msg.clear(); 121 } 122 } 123 } 124 125 class BleStateReceiver extends BroadcastReceiver { 126 127 @Override 128 public void onReceive(Context context, Intent intent) { 129 String action = intent.getAction(); 130 if (action.equals(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)) { 131 int state = mBluetoothAdapter.getLeState(); 132 if (state == BluetoothAdapter.STATE_BLE_ON) { 133 mEventFacade.postEvent("BleStateChangedOn", new Bundle()); 134 mService.unregisterReceiver(mBleStateReceiver); 135 } else if (state == BluetoothAdapter.STATE_OFF) { 136 mEventFacade.postEvent("BleStateChangedOff", new Bundle()); 137 mService.unregisterReceiver(mBleStateReceiver); 138 } 139 } 140 } 141 } 142 143 144 public static boolean deviceMatch(BluetoothDevice device, String deviceID) { 145 return deviceID.equals(device.getAliasName()) || deviceID.equals(device.getAddress()); 146 } 147 148 public static <T> BluetoothDevice getDevice(ConcurrentHashMap<String, T> devices, String device) 149 throws Exception { 150 if (devices.containsKey(device)) { 151 return (BluetoothDevice) devices.get(device); 152 } else { 153 throw new Exception("Can't find device " + device); 154 } 155 } 156 157 public static BluetoothDevice getDevice(Collection<BluetoothDevice> devices, String deviceID) 158 throws Exception { 159 Log.d("Looking for " + deviceID); 160 for (BluetoothDevice bd : devices) { 161 Log.d(bd.getAliasName() + " " + bd.getAddress()); 162 if (deviceMatch(bd, deviceID)) { 163 Log.d("Found match " + bd.getAliasName() + " " + bd.getAddress()); 164 return bd; 165 } 166 } 167 throw new Exception("Can't find device " + deviceID); 168 } 169 170 public static boolean deviceExists(Collection<BluetoothDevice> devices, String deviceID) { 171 for (BluetoothDevice bd : devices) { 172 if (deviceMatch(bd, deviceID)) { 173 Log.d("Found match " + bd.getAliasName() + " " + bd.getAddress()); 174 return true; 175 } 176 } 177 return false; 178 } 179 180 @Rpc(description = "Requests that the device be made connectable.") 181 public void bluetoothMakeConnectable() { 182 mBluetoothAdapter 183 .setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); 184 } 185 186 @Rpc(description = "Requests that the device be discoverable for Bluetooth connections.") 187 public void bluetoothMakeDiscoverable( 188 @RpcParameter(name = "duration", 189 description = "period of time, in seconds," 190 + "during which the device should be discoverable") 191 @RpcDefault("300") 192 Integer duration) { 193 Log.d("Making discoverable for " + duration + " seconds.\n"); 194 mBluetoothAdapter 195 .setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, duration); 196 } 197 198 @Rpc(description = "Requests that the device be not discoverable.") 199 public void bluetoothMakeUndiscoverable() { 200 Log.d("Making undiscoverable\n"); 201 mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_NONE); 202 } 203 204 @Rpc(description = "Queries a remote device for it's name or null if it can't be resolved") 205 public String bluetoothGetRemoteDeviceName( 206 @RpcParameter(name = "address", description = "Bluetooth Address For Target Device") 207 String address) { 208 try { 209 BluetoothDevice mDevice; 210 mDevice = mBluetoothAdapter.getRemoteDevice(address); 211 return mDevice.getName(); 212 } catch (Exception e) { 213 return null; 214 } 215 } 216 217 @Rpc(description = "Get local Bluetooth device name") 218 public String bluetoothGetLocalName() { 219 return mBluetoothAdapter.getName(); 220 } 221 222 @Rpc(description = "Sets the Bluetooth visible device name", returns = "true on success") 223 public boolean bluetoothSetLocalName( 224 @RpcParameter(name = "name", description = "New local name") 225 String name) { 226 return mBluetoothAdapter.setName(name); 227 } 228 229 @Rpc(description = "Returns the hardware address of the local Bluetooth adapter. ") 230 public String bluetoothGetLocalAddress() { 231 return mBluetoothAdapter.getAddress(); 232 } 233 234 @Rpc(description = "Returns the UUIDs supported by local Bluetooth adapter.") 235 public ParcelUuid[] bluetoothGetLocalUuids() { 236 return mBluetoothAdapter.getUuids(); 237 } 238 239 @Rpc(description = "Gets the scan mode for the local dongle.\r\n" + "Return values:\r\n" 240 + "\t-1 when Bluetooth is disabled.\r\n" 241 + "\t0 if non discoverable and non connectable.\r\n" 242 + "\r1 connectable non discoverable." + "\r3 connectable and discoverable.") 243 public int bluetoothGetScanMode() { 244 if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF 245 || mBluetoothAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF) { 246 return -1; 247 } 248 switch (mBluetoothAdapter.getScanMode()) { 249 case BluetoothAdapter.SCAN_MODE_NONE: 250 return 0; 251 case BluetoothAdapter.SCAN_MODE_CONNECTABLE: 252 return 1; 253 case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: 254 return 3; 255 default: 256 return mBluetoothAdapter.getScanMode() - 20; 257 } 258 } 259 260 @Rpc(description = "Return the set of BluetoothDevice that are paired to the local adapter.") 261 public Set<BluetoothDevice> bluetoothGetBondedDevices() { 262 return mBluetoothAdapter.getBondedDevices(); 263 } 264 265 @Rpc(description = "Checks Bluetooth state.", returns = "True if Bluetooth is enabled.") 266 public Boolean bluetoothCheckState() { 267 return mBluetoothAdapter.isEnabled(); 268 } 269 270 @Rpc(description = "Toggle Bluetooth on and off.", returns = "True if Bluetooth is enabled.") 271 public Boolean bluetoothToggleState(@RpcParameter(name = "enabled") 272 @RpcOptional 273 Boolean enabled, 274 @RpcParameter(name = "prompt", 275 description = "Prompt the user to confirm changing the Bluetooth state.") 276 @RpcDefault("false") 277 Boolean prompt) { 278 mService.registerReceiver(mStateReceiver, 279 new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); 280 if (enabled == null) { 281 enabled = !bluetoothCheckState(); 282 } 283 if (enabled) { 284 mBluetoothAdapter.enable(); 285 } else { 286 shutdown(); 287 mBluetoothAdapter.disable(); 288 } 289 return enabled; 290 } 291 292 293 @Rpc(description = "Start the remote device discovery process. ", 294 returns = "true on success, false on error") 295 public Boolean bluetoothStartDiscovery() { 296 DiscoveredDevices.clear(); 297 mService.registerReceiver(mDiscoveryReceiver, discoveryFilter); 298 return mBluetoothAdapter.startDiscovery(); 299 } 300 301 @Rpc(description = "Cancel the current device discovery process.", 302 returns = "true on success, false on error") 303 public Boolean bluetoothCancelDiscovery() { 304 try { 305 mService.unregisterReceiver(mDiscoveryReceiver); 306 } catch (IllegalArgumentException e) { 307 Log.d("IllegalArgumentExeption found when trying to unregister reciever"); 308 } 309 return mBluetoothAdapter.cancelDiscovery(); 310 } 311 312 @Rpc(description = "If the local Bluetooth adapter is currently" 313 + "in the device discovery process.") 314 public Boolean bluetoothIsDiscovering() { 315 return mBluetoothAdapter.isDiscovering(); 316 } 317 318 @Rpc(description = "Get all the discovered bluetooth devices.") 319 public Collection<BluetoothDevice> bluetoothGetDiscoveredDevices() { 320 while (bluetoothIsDiscovering()) 321 ; 322 return DiscoveredDevices.values(); 323 } 324 325 @Rpc(description = "Enable or disable the Bluetooth HCI snoop log") 326 public boolean bluetoothConfigHciSnoopLog( 327 @RpcParameter(name = "value", description = "enable or disable log") 328 Boolean value 329 ) { 330 return mBluetoothAdapter.configHciSnoopLog(value); 331 } 332 333 @Rpc(description = "Get Bluetooth controller activity energy info.") 334 public String bluetoothGetControllerActivityEnergyInfo( 335 @RpcParameter(name = "value") 336 Integer value 337 ) { 338 BluetoothActivityEnergyInfo energyInfo = mBluetoothAdapter 339 .getControllerActivityEnergyInfo(value); 340 while (energyInfo == null) { 341 energyInfo = mBluetoothAdapter.getControllerActivityEnergyInfo(value); 342 } 343 return energyInfo.toString(); 344 } 345 346 @Rpc(description = "Return true if hardware has entries" + 347 "available for matching beacons.") 348 public boolean bluetoothIsHardwareTrackingFiltersAvailable() { 349 return mBluetoothAdapter.isHardwareTrackingFiltersAvailable(); 350 } 351 352 @Rpc(description = "Gets the current state of LE.") 353 public int bluetoothGetLeState() { 354 return mBluetoothAdapter.getLeState(); 355 } 356 357 @Rpc(description = "Enables BLE functionalities.") 358 public boolean bluetoothEnableBLE() { 359 mService.registerReceiver(mBleStateReceiver, 360 new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)); 361 return mBluetoothAdapter.enableBLE(); 362 } 363 364 @Rpc(description = "Disables BLE functionalities.") 365 public boolean bluetoothDisableBLE() { 366 mService.registerReceiver(mBleStateReceiver, 367 new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)); 368 return mBluetoothAdapter.disableBLE(); 369 } 370 371 @Override 372 public void shutdown() { 373 for (Map.Entry<String, BluetoothConnection> entry : connections.entrySet()) { 374 entry.getValue().stop(); 375 } 376 connections.clear(); 377 } 378 } 379