From b9720a7bb6dcfb544db2788628aa3f804b32d5f1 Mon Sep 17 00:00:00 2001 From: Tony Date: Sun, 20 May 2018 16:08:48 -0700 Subject: [PATCH] Re-design BLE beacons logic. Add support for Eddystone beacons. Signed-off-by: Chengzhi Hu --- mobile/src/main/AndroidManifest.xml | 6 + .../habdroid/util/BleBeaconConnector.java | 162 ++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 mobile/src/main/java/org/openhab/habdroid/util/BleBeaconConnector.java diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 7eeee0aa79f..8d9c1558ced 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + + { + if (!isEddyStoneBeacon(bytes)){ + return; + } + Log.d(TAG, "Device:" + bluetoothDevice.getName() + " address: " + bluetoothDevice.getAddress()); + }; + + //Enforce singleton by using private constructors + private BleBeaconConnector(){} + + private BleBeaconConnector(AppCompatActivity activity){ + handler = new Handler(); + final BluetoothManager manager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE); + bluetoothAdapter = manager.getAdapter(); + + if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()){ + Intent enableBluetoothIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + activity.startActivityForResult(enableBluetoothIntent, REQUEST_ENABLE_BT); + } + + scanLeServiceCompact(); + } + + @SuppressWarnings("deprecation") + private void scanLeServiceCompact(){ + handler.postDelayed(() -> bluetoothAdapter.stopLeScan(leScanCallback), SCAN_PERIOD); + bluetoothAdapter.startLeScan(leScanCallback); + } + + public static BleBeaconConnector getInstance(AppCompatActivity activity) { + if (INSTANCE == null){ + INSTANCE = new BleBeaconConnector(activity); + } + return INSTANCE; + } + + private boolean isEddyStoneBeacon(byte[] bytes){ + List serviceUuids = parseFromBytes(bytes); + return serviceUuids.contains(EDDYSTONE_URL_SERVICE_UUID); + } + + private List parseFromBytes(byte[] bytes){ + int length; + int fieldType; + int currentPos = 0; + List serviceUuids = new ArrayList<>(); + while (currentPos < bytes.length) { + length = bytes[currentPos++] & 0xFF; + if (length == 0){ + break; + } + fieldType = bytes[currentPos++] & 0xFF; + switch (fieldType) { + case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL: + case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE: + parseServiceUuid(bytes, currentPos, length, UUID_BYTES_16_BIT, serviceUuids); + case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL: + case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE: + parseServiceUuid(bytes, currentPos, length, UUID_BYTES_32_BIT, serviceUuids); + case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL: + case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE: + parseServiceUuid(bytes, currentPos, length, UUID_BYTES_128_BIT, serviceUuids); + } + currentPos += length - 1; + } + return serviceUuids; + } + + private void parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, + int uuidLength, List serviceUuids) { + while (dataLength > 0) { + byte[] uuidBytes = new byte[uuidLength]; + System.arraycopy(scanRecord, currentPos, uuidBytes, 0, uuidLength); + serviceUuids.add(parseUuidFrom(uuidBytes)); + dataLength -= uuidLength; + currentPos += uuidLength; + } + } + + private ParcelUuid parseUuidFrom(byte[] uuidBytes) { + if (uuidBytes == null) { + throw new IllegalArgumentException("uuidBytes cannot be null"); + } + int length = uuidBytes.length; + if (length != UUID_BYTES_16_BIT && length != UUID_BYTES_32_BIT && + length != UUID_BYTES_128_BIT) { + throw new IllegalArgumentException("uuidBytes length invalid - " + length); + } + // Construct a 128 bit UUID. + if (length == UUID_BYTES_128_BIT) { + ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN); + long msb = buf.getLong(8); + long lsb = buf.getLong(0); + return new ParcelUuid(new UUID(msb, lsb)); + } + // For 16 bit and 32 bit UUID we need to convert them to 128 bit value. + // 128_bit_value = uuid * 2^96 + BASE_UUID + long shortUuid; + if (length == UUID_BYTES_16_BIT) { + shortUuid = uuidBytes[0] & 0xFF; + shortUuid += (uuidBytes[1] & 0xFF) << 8; + } else { + shortUuid = uuidBytes[0] & 0xFF; + shortUuid += (uuidBytes[1] & 0xFF) << 8; + shortUuid += (uuidBytes[2] & 0xFF) << 16; + shortUuid += (uuidBytes[3] & 0xFF) << 24; + } + long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32); + long lsb = BASE_UUID.getUuid().getLeastSignificantBits(); + StringBuilder sb = new StringBuilder(); + sb.append("Unconverted: size: ").append(uuidBytes.length).append("\n"); + for (byte uuid : uuidBytes){ + sb.append(uuid).append(" "); + } + sb.append("\n"); + Log.d(TAG, sb.toString() + "Convtered: " + new UUID(msb, lsb)); + return new ParcelUuid(new UUID(msb, lsb)); + } +}