
com.gluonhq.charm.down.android.ble.BleServiceImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of charm-down-android Show documentation
Show all versions of charm-down-android Show documentation
API to access features for the android platform
The newest version!
/*
* Copyright (c) 2015, Gluon
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Gluon, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.gluonhq.charm.down.android.ble;
import static android.app.Activity.RESULT_OK;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.content.Intent;
import com.gluonhq.charm.down.common.BleService;
import com.gluonhq.charm.down.common.ble.Configuration;
import com.gluonhq.charm.down.common.ble.ScanDetection;
import com.gluonhq.charm.down.common.ble.ScanDetection.PROXIMITY;
import java.util.Arrays;
import java.util.Formatter;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import javafxports.android.FXActivity;
/**
* Android implementation of BleService
*
* Android apps using BleService require the permissions BLUETOOTH and BLUETOOTH_ADMIN in the AndroidManifest file
*/
public class BleServiceImpl implements BleService {
private BluetoothLeScanner scanner;
private ScanCallback scanCallback;
private List uuids = new LinkedList<>();
private final static int REQUEST_ENABLE_BT = 1001;
/**
* Check if Bluetooth is enabled and triggers a notification in case it is not, returning
* to the application after the user enables it
*/
public BleServiceImpl() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
System.out.println("Device doesn't support Bluetooth");
} else if (!adapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
FXActivity activity=FXActivity.getInstance();
activity.setOnActivityResultHandler((requestCode, resultCode, data) -> {
if (requestCode == REQUEST_ENABLE_BT && resultCode == RESULT_OK) {
scanner = adapter.getBluetoothLeScanner();
}
});
// A dialog will appear requesting user permission to enable Bluetooth
activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
} else {
scanner = adapter.getBluetoothLeScanner();
}
}
/**
* startScanning is called with a given uuid and a callback for the beacon found.
* Android allows scanning for any beacon without knowing the uuid, so an empty
* uuid can be set (this is not allowed in iOS implementation)
*
* @param region Containing the beacon uuid
* @param callback Callback added to the beacon
*/
@Override
public void startScanning(Configuration region, Consumer callback) {
List requested = region.getUuids();
for (String candidate: requested) {
uuids.add(candidate.toLowerCase());
}
if (scanner != null) {
this.scanCallback = createCallback(callback);
scanner.startScan(scanCallback);
}
}
/**
* stopScanning, if the scanner is initialized
*/
@Override
public void stopScanning() {
if (scanner != null && scanCallback != null) {
scanner.stopScan(scanCallback);
}
}
private ScanCallback createCallback(Consumer callback) {
ScanCallback answer = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
ScanRecord mScanRecord = result.getScanRecord();
byte[] scanRecord = mScanRecord.getBytes();
// https://www.pubnub.com/blog/2015-04-14-building-android-beacon-android-ibeacon-tutorial-overview/
int index = 0;
while (index < scanRecord.length) {
int length = scanRecord[index++];
if (length == 0) {
break;
}
int type = (scanRecord[index] & 0xff);
// process data
if (type == 0xff) {
ScanDetection detection = processAD(scanRecord, index + 1, result.getRssi());
if (detection != null) {
callback.accept(detection);
}
}
index += length;
}
}
};
return answer;
}
private ScanDetection processAD(byte[] scanRecord, int init, int mRssi) {
// AD: (Length FF) mID0 mID1 bID1 bID0 uuid15 ... uuid0 M1 M0 m1 m0 tx
int startByte = init;
// Manufacturer ID (little endian)
// https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers
int mID = ((scanRecord[startByte+1] & 0xff) << 8) | (scanRecord[startByte] & 0xff);
startByte += 2;
// Beacon ID (big endian)
int beaconID = ((scanRecord[startByte] & 0xff) << 8) | (scanRecord[startByte+1] & 0xff);
startByte += 2;
// UUID (big endian)
byte[] uuidBytes = Arrays.copyOfRange(scanRecord, startByte, startByte+16);
String scannedUuid = ByteArrayToUUIDString(uuidBytes);
if (uuids.isEmpty() || uuids.contains(scannedUuid.toLowerCase())) {
startByte += 16;
// major (big endian)
int major = ((scanRecord[startByte] & 0xff) << 8) | (scanRecord[startByte+1] & 0xff);
startByte += 2;
// minor (big endian)
int minor = ((scanRecord[startByte] & 0xff) << 8) | (scanRecord[startByte+1] & 0xff);
startByte += 2;
// power in dB
int power = (scanRecord[startByte] & 0xff);
power -= 256;
PROXIMITY distance = calculateProximity(power, mRssi);
// System.out.println("Scan: mID: "+mID+", beaconID: "+beaconID+", uuid: "+scannedUuid+
// ", major: "+major+", minor: "+minor+", power: "+power+", distance: "+distance.name());
ScanDetection detection = new ScanDetection();
detection.setUuid(scannedUuid);
detection.setMajor(major);
detection.setMinor(minor);
detection.setRssi(mRssi);
detection.setProximity(distance.getProximity());
return detection;
}
return null;
}
private static String ByteArrayToUUIDString(byte[] ba) {
StringBuilder hex = new StringBuilder();
for (byte b : ba) {
hex.append(new Formatter().format("%02x", b));
}
return hex.toString().replaceFirst("(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)",
"$1-$2-$3-$4-$5" );
}
// https://github.com/RadiusNetworks/android-ibeacon-service/blob/tip/src/main/java/com/radiusnetworks/ibeacon/IBeacon.java
private PROXIMITY calculateProximity(int txPower, double rssi) {
double accuracy = calculateAccuracy(txPower, rssi);
if (accuracy < 0) {
return PROXIMITY.UNKNOWN;
}
if (accuracy < 0.5) {
return PROXIMITY.IMMEDIATE;
}
if (accuracy <= 4.0) {
return PROXIMITY.NEAR;
}
return PROXIMITY.FAR;
}
private static double calculateAccuracy(int txPower, double rssi) {
if (rssi == 0 || txPower == 0) {
return -1.0; // if we cannot determine accuracy, return -1.
}
double ratio = rssi * 1.0 / txPower;
if (ratio < 1.0) {
return Math.pow(ratio, 10);
} else {
double accuracy = 0.89976 * Math.pow(ratio, 7.7095) + 0.111;
return accuracy;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy