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 17package com.googlecode.android_scripting.facade.bluetooth; 18 19import java.util.HashMap; 20import java.util.List; 21import java.util.concurrent.Callable; 22 23import android.app.Service; 24import android.bluetooth.BluetoothAdapter; 25import android.bluetooth.le.AdvertiseCallback; 26import android.bluetooth.le.AdvertiseData; 27import android.bluetooth.le.AdvertiseData.Builder; 28import android.bluetooth.le.AdvertiseSettings; 29import android.bluetooth.le.BluetoothLeAdvertiser; 30import android.os.Bundle; 31import android.os.ParcelUuid; 32 33import com.googlecode.android_scripting.Log; 34import com.googlecode.android_scripting.MainThread; 35import com.googlecode.android_scripting.facade.EventFacade; 36import com.googlecode.android_scripting.facade.FacadeManager; 37import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 38import com.googlecode.android_scripting.rpc.Rpc; 39import com.googlecode.android_scripting.rpc.RpcParameter; 40 41/** 42 * BluetoothLe Advertise functions. 43 */ 44 45public class BluetoothLeAdvertiseFacade extends RpcReceiver { 46 47 private final EventFacade mEventFacade; 48 private BluetoothAdapter mBluetoothAdapter; 49 private static int BleAdvertiseCallbackCount; 50 private static int BleAdvertiseSettingsCount; 51 private static int BleAdvertiseDataCount; 52 private final HashMap<Integer, myAdvertiseCallback> mAdvertiseCallbackList; 53 private final BluetoothLeAdvertiser mAdvertise; 54 private final Service mService; 55 private Builder mAdvertiseDataBuilder; 56 private android.bluetooth.le.AdvertiseSettings.Builder mAdvertiseSettingsBuilder; 57 private final HashMap<Integer, AdvertiseData> mAdvertiseDataList; 58 private final HashMap<Integer, AdvertiseSettings> mAdvertiseSettingsList; 59 60 public BluetoothLeAdvertiseFacade(FacadeManager manager) { 61 super(manager); 62 mService = manager.getService(); 63 mBluetoothAdapter = MainThread.run(mService, 64 new Callable<BluetoothAdapter>() { 65 @Override 66 public BluetoothAdapter call() throws Exception { 67 return BluetoothAdapter.getDefaultAdapter(); 68 } 69 }); 70 mEventFacade = manager.getReceiver(EventFacade.class); 71 mAdvertiseCallbackList = new HashMap<Integer, myAdvertiseCallback>(); 72 mAdvertise = mBluetoothAdapter.getBluetoothLeAdvertiser(); 73 mAdvertiseDataList = new HashMap<Integer, AdvertiseData>(); 74 mAdvertiseSettingsList = new HashMap<Integer, AdvertiseSettings>(); 75 mAdvertiseDataBuilder = new Builder(); 76 mAdvertiseSettingsBuilder = new android.bluetooth.le.AdvertiseSettings.Builder(); 77 } 78 79 /** 80 * Constructs a myAdvertiseCallback obj and returns its index 81 * 82 * @return myAdvertiseCallback.index 83 */ 84 @Rpc(description = "Generate a new myAdvertisement Object") 85 public Integer bleGenBleAdvertiseCallback() { 86 BleAdvertiseCallbackCount += 1; 87 int index = BleAdvertiseCallbackCount; 88 myAdvertiseCallback mCallback = new myAdvertiseCallback(index); 89 mAdvertiseCallbackList.put(mCallback.index, 90 mCallback); 91 return mCallback.index; 92 } 93 94 /** 95 * Constructs a AdvertiseData obj and returns its index 96 * 97 * @return index 98 */ 99 @Rpc(description = "Constructs a new Builder obj for AdvertiseData and returns its index") 100 public Integer bleBuildAdvertiseData() { 101 BleAdvertiseDataCount += 1; 102 int index = BleAdvertiseDataCount; 103 mAdvertiseDataList.put(index, 104 mAdvertiseDataBuilder.build()); 105 mAdvertiseDataBuilder = new Builder(); 106 return index; 107 } 108 109 /** 110 * Constructs a Advertise Settings obj and returns its index 111 * 112 * @return index 113 */ 114 @Rpc(description = "Constructs a new Builder obj for AdvertiseData and returns its index") 115 public Integer bleBuildAdvertiseSettings() { 116 BleAdvertiseSettingsCount += 1; 117 int index = BleAdvertiseSettingsCount; 118 mAdvertiseSettingsList.put(index, 119 mAdvertiseSettingsBuilder.build()); 120 mAdvertiseSettingsBuilder = new android.bluetooth.le.AdvertiseSettings.Builder(); 121 return index; 122 } 123 124 /** 125 * Stops a ble advertisement 126 * 127 * @param index the id of the advertisement to stop advertising on 128 * @throws Exception 129 */ 130 @Rpc(description = "Stops an ongoing ble advertisement") 131 public void bleStopBleAdvertising( 132 @RpcParameter(name = "index") 133 Integer index) throws Exception { 134 if (mAdvertiseCallbackList.get(index) != null) { 135 Log.d("bluetooth_le mAdvertise " + index); 136 mAdvertise.stopAdvertising(mAdvertiseCallbackList 137 .get(index)); 138 } else { 139 throw new Exception("Invalid index input:" + Integer.toString(index)); 140 } 141 } 142 143 /** 144 * Starts ble advertising 145 * 146 * @param callbackIndex The advertisementCallback index 147 * @param dataIndex the AdvertiseData index 148 * @param settingsIndex the advertisementsettings index 149 * @throws Exception 150 */ 151 @Rpc(description = "Starts ble advertisement") 152 public void bleStartBleAdvertising( 153 @RpcParameter(name = "callbackIndex") 154 Integer callbackIndex, 155 @RpcParameter(name = "dataIndex") 156 Integer dataIndex, 157 @RpcParameter(name = "settingsIndex") 158 Integer settingsIndex 159 ) throws Exception { 160 AdvertiseData mData = new AdvertiseData.Builder().build(); 161 AdvertiseSettings mSettings = new AdvertiseSettings.Builder().build(); 162 if (mAdvertiseDataList.get(dataIndex) != null) { 163 mData = mAdvertiseDataList.get(dataIndex); 164 } else { 165 throw new Exception("Invalid dataIndex input:" + Integer.toString(dataIndex)); 166 } 167 if (mAdvertiseSettingsList.get(settingsIndex) != null) { 168 mSettings = mAdvertiseSettingsList.get(settingsIndex); 169 } else { 170 throw new Exception("Invalid settingsIndex input:" + Integer.toString(settingsIndex)); 171 } 172 if (mAdvertiseCallbackList.get(callbackIndex) != null) { 173 Log.d("bluetooth_le starting a background advertisement on callback index: " 174 + Integer.toString(callbackIndex)); 175 mAdvertise 176 .startAdvertising(mSettings, mData, mAdvertiseCallbackList.get(callbackIndex)); 177 } else { 178 throw new Exception("Invalid callbackIndex input" + Integer.toString(callbackIndex)); 179 } 180 } 181 182 /** 183 * Starts ble advertising with a scanResponse. ScanResponses are created in the same way 184 * AdvertiseData is created since they share the same object type. 185 * 186 * @param callbackIndex The advertisementCallback index 187 * @param dataIndex the AdvertiseData index 188 * @param settingsIndex the advertisementsettings index 189 * @param scanResponseIndex the scanResponse index 190 * @throws Exception 191 */ 192 @Rpc(description = "Starts ble advertisement") 193 public void bleStartBleAdvertisingWithScanResponse( 194 @RpcParameter(name = "callbackIndex") 195 Integer callbackIndex, 196 @RpcParameter(name = "dataIndex") 197 Integer dataIndex, 198 @RpcParameter(name = "settingsIndex") 199 Integer settingsIndex, 200 @RpcParameter(name = "scanResponseIndex") 201 Integer scanResponseIndex 202 ) throws Exception { 203 AdvertiseData mData = new AdvertiseData.Builder().build(); 204 AdvertiseSettings mSettings = new AdvertiseSettings.Builder().build(); 205 AdvertiseData mScanResponse = new AdvertiseData.Builder().build(); 206 207 if (mAdvertiseDataList.get(dataIndex) != null) { 208 mData = mAdvertiseDataList.get(dataIndex); 209 } else { 210 throw new Exception("Invalid dataIndex input:" + Integer.toString(dataIndex)); 211 } 212 if (mAdvertiseSettingsList.get(settingsIndex) != null) { 213 mSettings = mAdvertiseSettingsList.get(settingsIndex); 214 } else { 215 throw new Exception("Invalid settingsIndex input:" + Integer.toString(settingsIndex)); 216 } 217 if (mAdvertiseDataList.get(scanResponseIndex) != null) { 218 mScanResponse = mAdvertiseDataList.get(scanResponseIndex); 219 } else { 220 throw new Exception("Invalid scanResponseIndex input:" 221 + Integer.toString(settingsIndex)); 222 } 223 if (mAdvertiseCallbackList.get(callbackIndex) != null) { 224 Log.d("bluetooth_le starting a background advertise on callback index: " 225 + Integer.toString(callbackIndex)); 226 mAdvertise 227 .startAdvertising(mSettings, mData, mScanResponse, 228 mAdvertiseCallbackList.get(callbackIndex)); 229 } else { 230 throw new Exception("Invalid callbackIndex input" + Integer.toString(callbackIndex)); 231 } 232 } 233 234 /** 235 * Get ble advertisement settings mode 236 * 237 * @param index the advertise settings object to use 238 * @return the mode of the advertise settings object 239 * @throws Exception 240 */ 241 @Rpc(description = "Get ble advertisement settings mode") 242 public int bleGetAdvertiseSettingsMode( 243 @RpcParameter(name = "index") 244 Integer index) throws Exception { 245 if (mAdvertiseSettingsList.get(index) != null) { 246 AdvertiseSettings mSettings = mAdvertiseSettingsList.get(index); 247 return mSettings.getMode(); 248 } else { 249 throw new Exception("Invalid index input:" + Integer.toString(index)); 250 } 251 } 252 253 /** 254 * Get ble advertisement settings tx power level 255 * 256 * @param index the advertise settings object to use 257 * @return the tx power level of the advertise settings object 258 * @throws Exception 259 */ 260 @Rpc(description = "Get ble advertisement settings tx power level") 261 public int bleGetAdvertiseSettingsTxPowerLevel( 262 @RpcParameter(name = "index") 263 Integer index) throws Exception { 264 if (mAdvertiseSettingsList.get(index) != null) { 265 AdvertiseSettings mSettings = mAdvertiseSettingsList.get(index); 266 return mSettings.getTxPowerLevel(); 267 } else { 268 throw new Exception("Invalid index input:" + Integer.toString(index)); 269 } 270 } 271 272 /** 273 * Get ble advertisement settings isConnectable value 274 * 275 * @param index the advertise settings object to use 276 * @return the boolean value whether the advertisement will indicate 277 * connectable. 278 * @throws Exception 279 */ 280 @Rpc(description = "Get ble advertisement settings isConnectable value") 281 public boolean bleGetAdvertiseSettingsIsConnectable( 282 @RpcParameter(name = "index") 283 Integer index) throws Exception { 284 if (mAdvertiseSettingsList.get(index) != null) { 285 AdvertiseSettings mSettings = mAdvertiseSettingsList.get(index); 286 return mSettings.isConnectable(); 287 } else { 288 throw new Exception("Invalid index input:" + Integer.toString(index)); 289 } 290 } 291 292 /** 293 * Get ble advertisement data include tx power level 294 * 295 * @param index the advertise data object to use 296 * @return True if include tx power level, false otherwise 297 * @throws Exception 298 */ 299 @Rpc(description = "Get ble advertisement data include tx power level") 300 public Boolean bleGetAdvertiseDataIncludeTxPowerLevel( 301 @RpcParameter(name = "index") 302 Integer index) throws Exception { 303 if (mAdvertiseDataList.get(index) != null) { 304 AdvertiseData mData = mAdvertiseDataList.get(index); 305 return mData.getIncludeTxPowerLevel(); 306 } else { 307 throw new Exception("Invalid index input:" + Integer.toString(index)); 308 } 309 } 310 311 /** 312 * Get ble advertisement data manufacturer specific data 313 * 314 * @param index the advertise data object to use 315 * @param manufacturerId the id that corresponds to the manufacturer specific data. 316 * @return the corresponding manufacturer specific data to the manufacturer id. 317 * @throws Exception 318 */ 319 @Rpc(description = "Get ble advertisement data manufacturer specific data") 320 public byte[] bleGetAdvertiseDataManufacturerSpecificData( 321 @RpcParameter(name = "index") 322 Integer index, 323 @RpcParameter(name = "manufacturerId") 324 Integer manufacturerId) throws Exception { 325 if (mAdvertiseDataList.get(index) != null) { 326 AdvertiseData mData = mAdvertiseDataList.get(index); 327 if (mData.getManufacturerSpecificData() != null) { 328 return mData.getManufacturerSpecificData().get(manufacturerId); 329 } else { 330 throw new Exception("Invalid manufacturerId input:" + Integer.toString(manufacturerId)); 331 } 332 } else { 333 throw new Exception("Invalid index input:" + Integer.toString(index)); 334 335 } 336 } 337 338 /** 339 * Get ble advertisement data include device name 340 * 341 * @param index the advertise data object to use 342 * @return the advertisement data's include device name 343 * @throws Exception 344 */ 345 @Rpc(description = "Get ble advertisement include device name") 346 public Boolean bleGetAdvertiseDataIncludeDeviceName( 347 @RpcParameter(name = "index") 348 Integer index) throws Exception { 349 if (mAdvertiseDataList.get(index) != null) { 350 AdvertiseData mData = mAdvertiseDataList.get(index); 351 return mData.getIncludeDeviceName(); 352 } else { 353 throw new Exception("Invalid index input:" + Integer.toString(index)); 354 } 355 } 356 357 /** 358 * Get ble advertisement Service Data 359 * 360 * @param index the advertise data object to use 361 * @param serviceUuid the uuid corresponding to the service data. 362 * @return the advertisement data's service data 363 * @throws Exception 364 */ 365 @Rpc(description = "Get ble advertisement Service Data") 366 public byte[] bleGetAdvertiseDataServiceData( 367 @RpcParameter(name = "index") 368 Integer index, 369 @RpcParameter(name = "serviceUuid") 370 String serviceUuid) throws Exception { 371 ParcelUuid uuidKey = ParcelUuid.fromString(serviceUuid); 372 if (mAdvertiseDataList.get(index) != null) { 373 AdvertiseData mData = mAdvertiseDataList.get(index); 374 if (mData.getServiceData().containsKey(uuidKey)) { 375 return mData.getServiceData().get(uuidKey); 376 } else { 377 throw new Exception("Invalid serviceUuid input:" + serviceUuid); 378 } 379 } else { 380 throw new Exception("Invalid index input:" + Integer.toString(index)); 381 } 382 } 383 384 /** 385 * Get ble advertisement Service Uuids 386 * 387 * @param index the advertise data object to use 388 * @return the advertisement data's Service Uuids 389 * @throws Exception 390 */ 391 @Rpc(description = "Get ble advertisement Service Uuids") 392 public List<ParcelUuid> bleGetAdvertiseDataServiceUuids( 393 @RpcParameter(name = "index") 394 Integer index) throws Exception { 395 if (mAdvertiseDataList.get(index) != null) { 396 AdvertiseData mData = mAdvertiseDataList.get(index); 397 return mData.getServiceUuids(); 398 } else { 399 throw new Exception("Invalid index input:" + Integer.toString(index)); 400 } 401 } 402 403 /** 404 * Set ble advertisement data service uuids 405 * 406 * @param uuidList 407 * @throws Exception 408 */ 409 @Rpc(description = "Set ble advertisement data service uuids") 410 public void bleSetAdvertiseDataSetServiceUuids( 411 @RpcParameter(name = "uuidList") 412 String[] uuidList 413 ) { 414 for (String uuid : uuidList) { 415 mAdvertiseDataBuilder.addServiceUuid(ParcelUuid.fromString(uuid)); 416 } 417 } 418 419 /** 420 * Set ble advertise data service uuids 421 * 422 * @param serviceDataUuid 423 * @param serviceData 424 * @throws Exception 425 */ 426 @Rpc(description = "Set ble advertise data service uuids") 427 public void bleAddAdvertiseDataServiceData( 428 @RpcParameter(name = "serviceDataUuid") 429 String serviceDataUuid, 430 @RpcParameter(name = "serviceData") 431 byte[] serviceData 432 ) { 433 mAdvertiseDataBuilder.addServiceData( 434 ParcelUuid.fromString(serviceDataUuid), 435 serviceData); 436 } 437 438 /** 439 * Set ble advertise data manufacturer id 440 * 441 * @param manufacturerId the manufacturer id to set 442 * @param manufacturerSpecificData the manufacturer specific data to set 443 * @throws Exception 444 */ 445 @Rpc(description = "Set ble advertise data manufacturerId") 446 public void bleAddAdvertiseDataManufacturerId( 447 @RpcParameter(name = "manufacturerId") 448 Integer manufacturerId, 449 @RpcParameter(name = "manufacturerSpecificData") 450 byte[] manufacturerSpecificData 451 ) { 452 mAdvertiseDataBuilder.addManufacturerData(manufacturerId, 453 manufacturerSpecificData); 454 } 455 456 /** 457 * Set ble advertise settings advertise mode 458 * 459 * @param advertiseMode 460 * @throws Exception 461 */ 462 @Rpc(description = "Set ble advertise settings advertise mode") 463 public void bleSetAdvertiseSettingsAdvertiseMode( 464 @RpcParameter(name = "advertiseMode") 465 Integer advertiseMode 466 ) { 467 mAdvertiseSettingsBuilder.setAdvertiseMode(advertiseMode); 468 } 469 470 /** 471 * Set ble advertise settings tx power level 472 * 473 * @param txPowerLevel the tx power level to set 474 * @throws Exception 475 */ 476 @Rpc(description = "Set ble advertise settings tx power level") 477 public void bleSetAdvertiseSettingsTxPowerLevel( 478 @RpcParameter(name = "txPowerLevel") 479 Integer txPowerLevel 480 ) { 481 mAdvertiseSettingsBuilder.setTxPowerLevel(txPowerLevel); 482 } 483 484 /** 485 * Set ble advertise settings the isConnectable value 486 * 487 * @param type the isConnectable value 488 * @throws Exception 489 */ 490 @Rpc(description = "Set ble advertise settings isConnectable value") 491 public void bleSetAdvertiseSettingsIsConnectable( 492 @RpcParameter(name = "value") 493 Boolean value 494 ) { 495 mAdvertiseSettingsBuilder.setConnectable(value); 496 } 497 498 /** 499 * Set ble advertisement data include tx power level 500 * 501 * @param includeTxPowerLevel boolean whether to include the tx power level or not in the 502 * advertisement 503 */ 504 @Rpc(description = "Set ble advertisement data include tx power level") 505 public void bleSetAdvertiseDataIncludeTxPowerLevel( 506 @RpcParameter(name = "includeTxPowerLevel") 507 Boolean includeTxPowerLevel 508 ) { 509 mAdvertiseDataBuilder.setIncludeTxPowerLevel(includeTxPowerLevel); 510 } 511 512 /** 513 * Set ble advertisement settings set timeout 514 * 515 * @param timeoutSeconds Limit advertising to a given amount of time. 516 */ 517 @Rpc(description = "Set ble advertisement data include tx power level") 518 public void bleSetAdvertiseSettingsTimeout( 519 @RpcParameter(name = "timeoutSeconds") 520 Integer timeoutSeconds 521 ) { 522 mAdvertiseSettingsBuilder.setTimeout(timeoutSeconds); 523 } 524 525 /** 526 * Set ble advertisement data include device name 527 * 528 * @param includeDeviceName boolean whether to include device name or not in the 529 * advertisement 530 */ 531 @Rpc(description = "Set ble advertisement data include device name") 532 public void bleSetAdvertiseDataIncludeDeviceName( 533 @RpcParameter(name = "includeDeviceName") 534 Boolean includeDeviceName 535 ) { 536 mAdvertiseDataBuilder.setIncludeDeviceName(includeDeviceName); 537 } 538 539 private class myAdvertiseCallback extends AdvertiseCallback { 540 public Integer index; 541 private final Bundle mResults; 542 String mEventType; 543 544 public myAdvertiseCallback(int idx) { 545 index = idx; 546 mEventType = "BleAdvertise"; 547 mResults = new Bundle(); 548 } 549 550 @Override 551 public void onStartSuccess(AdvertiseSettings settingsInEffect) { 552 Log.d("bluetooth_le_advertisement onSuccess " + mEventType + " " 553 + index); 554 mResults.putString("Type", "onSuccess"); 555 mResults.putParcelable("SettingsInEffect", settingsInEffect); 556 mEventFacade.postEvent(mEventType + index + "onSuccess", mResults.clone()); 557 mResults.clear(); 558 } 559 560 @Override 561 public void onStartFailure(int errorCode) { 562 String errorString = "UNKNOWN_ERROR_CODE"; 563 if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED) { 564 errorString = "ADVERTISE_FAILED_ALREADY_STARTED"; 565 } else if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE) { 566 errorString = "ADVERTISE_FAILED_DATA_TOO_LARGE"; 567 } else if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED) { 568 errorString = "ADVERTISE_FAILED_FEATURE_UNSUPPORTED"; 569 } else if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR) { 570 errorString = "ADVERTISE_FAILED_INTERNAL_ERROR"; 571 } else if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS) { 572 errorString = "ADVERTISE_FAILED_TOO_MANY_ADVERTISERS"; 573 } 574 Log.d("bluetooth_le_advertisement onFailure " + mEventType + " " 575 + index + " error " + errorString); 576 mResults.putString("Type", "onFailure"); 577 mResults.putInt("ErrorCode", errorCode); 578 mResults.putString("Error", errorString); 579 mEventFacade.postEvent(mEventType + index + "onFailure", 580 mResults.clone()); 581 mResults.clear(); 582 } 583 } 584 585 @Override 586 public void shutdown() { 587 if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { 588 for (myAdvertiseCallback mAdvertise : mAdvertiseCallbackList 589 .values()) { 590 if (mAdvertise != null) { 591 try{ 592 mBluetoothAdapter.getBluetoothLeAdvertiser() 593 .stopAdvertising(mAdvertise); 594 } catch (NullPointerException e) { 595 Log.e("Failed to stop ble advertising.", e); 596 } 597 } 598 } 599 } 600 mAdvertiseCallbackList.clear(); 601 mAdvertiseSettingsList.clear(); 602 mAdvertiseDataList.clear(); 603 } 604} 605