nativescript-bluetooth
Version:
Connect to and interact with Bluetooth LE peripherals
1,052 lines (1,051 loc) • 126 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable no-caller */
var utils_1 = require("@nativescript/core/utils/utils");
var application_1 = require("@nativescript/core/application");
var bluetooth_common_1 = require("./bluetooth.common");
var p_queue_1 = require("p-queue");
var _bluetoothInstance;
function getBluetoothInstance() {
if (!_bluetoothInstance) {
_bluetoothInstance = new Bluetooth();
}
return _bluetoothInstance;
}
exports.getBluetoothInstance = getBluetoothInstance;
var ACCESS_LOCATION_PERMISSION_REQUEST_CODE = 222;
var ACTION_REQUEST_ENABLE_BLUETOOTH_REQUEST_CODE = 223;
var GATT_SUCCESS = 0;
var ANDROID_SDK = -1;
function getAndroidSDK() {
if (ANDROID_SDK === -1) {
ANDROID_SDK = android.os.Build.VERSION.SDK_INT;
}
return ANDROID_SDK;
}
var JELLY_BEAN = 18;
var LOLLIPOP = 21;
var MARSHMALLOW = 23;
var ScanMode;
(function (ScanMode) {
ScanMode[ScanMode["LOW_LATENCY"] = 0] = "LOW_LATENCY";
ScanMode[ScanMode["BALANCED"] = 1] = "BALANCED";
ScanMode[ScanMode["LOW_POWER"] = 2] = "LOW_POWER";
ScanMode[ScanMode["OPPORTUNISTIC"] = 3] = "OPPORTUNISTIC";
})(ScanMode = exports.ScanMode || (exports.ScanMode = {}));
function androidScanMode(mode) {
switch (mode) {
case ScanMode.BALANCED:
return android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED;
case ScanMode.LOW_POWER:
return android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
case ScanMode.OPPORTUNISTIC:
return android.bluetooth.le.ScanSettings.SCAN_MODE_OPPORTUNISTIC;
case ScanMode.LOW_LATENCY:
default:
return android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_LATENCY;
}
}
var MatchMode;
(function (MatchMode) {
MatchMode[MatchMode["AGGRESSIVE"] = 0] = "AGGRESSIVE";
MatchMode[MatchMode["STICKY"] = 1] = "STICKY";
})(MatchMode = exports.MatchMode || (exports.MatchMode = {}));
function androidMatchMode(mode) {
switch (mode) {
case MatchMode.STICKY:
return android.bluetooth.le.ScanSettings.MATCH_MODE_STICKY;
default:
return android.bluetooth.le.ScanSettings.MATCH_MODE_AGGRESSIVE;
}
}
var MatchNum;
(function (MatchNum) {
MatchNum[MatchNum["MAX_ADVERTISEMENT"] = 0] = "MAX_ADVERTISEMENT";
MatchNum[MatchNum["FEW_ADVERTISEMENT"] = 1] = "FEW_ADVERTISEMENT";
MatchNum[MatchNum["ONE_ADVERTISEMENT"] = 2] = "ONE_ADVERTISEMENT";
})(MatchNum = exports.MatchNum || (exports.MatchNum = {}));
function androidMatchNum(mode) {
switch (mode) {
case MatchNum.ONE_ADVERTISEMENT:
return android.bluetooth.le.ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT;
case MatchNum.FEW_ADVERTISEMENT:
return android.bluetooth.le.ScanSettings.MATCH_NUM_FEW_ADVERTISEMENT;
default:
return android.bluetooth.le.ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT;
}
}
var CallbackType;
(function (CallbackType) {
CallbackType[CallbackType["ALL_MATCHES"] = 0] = "ALL_MATCHES";
CallbackType[CallbackType["FIRST_MATCH"] = 1] = "FIRST_MATCH";
CallbackType[CallbackType["MATCH_LOST"] = 2] = "MATCH_LOST";
})(CallbackType = exports.CallbackType || (exports.CallbackType = {}));
function androidCallbackType(mode) {
switch (mode) {
case CallbackType.MATCH_LOST:
return android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST;
case CallbackType.FIRST_MATCH:
return android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH;
default:
return android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
}
}
var Phy;
(function (Phy) {
Phy[Phy["LE_1M"] = 0] = "LE_1M";
Phy[Phy["LE_CODED"] = 1] = "LE_CODED";
Phy[Phy["LE_ALL_SUPPORTED"] = 2] = "LE_ALL_SUPPORTED";
})(Phy = exports.Phy || (exports.Phy = {}));
function androidPhy(mode) {
switch (mode) {
case Phy.LE_1M:
return android.bluetooth.BluetoothDevice.PHY_LE_1M;
case Phy.LE_CODED:
return android.bluetooth.BluetoothDevice.PHY_LE_CODED;
default:
// PHY_LE_ALL_SUPPORTED
return android.bluetooth.le.ScanSettings.PHY_LE_ALL_SUPPORTED;
}
}
function uuidToString(uuid) {
// uuid is returned lowercase
var uuidStr = uuid.toString();
var pattern = java.util.regex.Pattern.compile('0000(.{4})-0000-1000-8000-00805f9b34fb', 2);
var matcher = pattern.matcher(uuidStr);
return matcher.matches() ? matcher.group(1) : uuidStr;
}
exports.uuidToString = uuidToString;
// val must be a Uint8Array or Uint16Array or a string like '0x01' or '0x007F' or '0x01,0x02', or '0x007F,'0x006F''
function arrayToNativeByteArray(val) {
var length = val.length;
var result = Array.create('byte', length);
for (var i = 0; i < length; i++) {
result[i] = val[i];
}
return result;
}
exports.arrayToNativeByteArray = arrayToNativeByteArray;
function nativeEncoding(encoding) {
var result = java.nio.charset.Charset.forName(encoding);
return result;
}
function stringToUint8Array(value, encoding) {
if (encoding === void 0) { encoding = 'iso-8859-1'; }
var nativeArray = new java.lang.String(value).getBytes(nativeEncoding(encoding));
var length = nativeArray.length;
var ret = new Uint8Array(length);
for (var i = 0; i < length; i++) {
ret[i] = value[i];
}
return ret;
}
exports.stringToUint8Array = stringToUint8Array;
function valueToByteArray(value, encoding) {
if (encoding === void 0) { encoding = 'iso-8859-1'; }
if (value instanceof ArrayBuffer) {
return arrayToNativeByteArray(new Uint8Array(value));
}
else if (value.buffer) {
return arrayToNativeByteArray(value);
}
else if (Array.isArray(value)) {
return arrayToNativeByteArray(value);
}
var type = typeof value;
if (type === 'string') {
return new java.lang.String(value).getBytes(nativeEncoding(encoding));
}
else if (type === 'number') {
return arrayToNativeByteArray([value]);
}
return null;
}
exports.valueToByteArray = valueToByteArray;
function byteArrayToBuffer(value) {
if (!value) {
return null;
}
var length = value.length;
var ret = new Uint8Array(length);
var isString = typeof value === 'string';
for (var i = 0; i < length; i++) {
ret[i] = isString ? value.charCodeAt(i) : value[i];
}
return ret.buffer;
}
exports.byteArrayToBuffer = byteArrayToBuffer;
function printValueToString(value) {
if (value instanceof java.lang.Object) {
var array = [];
var bytes = value;
for (var i = 0; i < bytes.length; i++) {
// array.push(new Number(bytes[i]).valueOf());
array.push(bytes[i]);
}
return array;
}
return value;
}
exports.printValueToString = printValueToString;
// JS UUID -> Java
function stringToUuid(uuidStr) {
if (uuidStr.length === 4) {
uuidStr = '0000' + uuidStr + '-0000-1000-8000-00805f9b34fb';
}
return java.util.UUID.fromString(uuidStr);
}
exports.stringToUuid = stringToUuid;
var LeScanCallbackVar;
function initLeScanCallback() {
if (LeScanCallbackVar) {
return;
}
var ScanRecord = /** @class */ (function () {
function ScanRecord(serviceUuids, manufacturerData, serviceData, advertiseFlags, txPowerLevel, localName, bytes) {
this.serviceUuids = serviceUuids;
this.manufacturerData = manufacturerData;
this.serviceData = serviceData;
this.advertiseFlags = advertiseFlags;
this.txPowerLevel = txPowerLevel;
this.localName = localName;
this.bytes = bytes;
}
ScanRecord.prototype.getManufacturerSpecificData = function () {
return this.manufacturerData;
};
ScanRecord.prototype.getBytes = function () {
return this.bytes;
};
ScanRecord.prototype.getAdvertiseFlags = function () {
return this.advertiseFlags;
};
ScanRecord.prototype.getServiceUuids = function () {
return this.serviceUuids;
};
ScanRecord.prototype.getServiceData = function () {
return this.serviceData;
};
ScanRecord.prototype.getDeviceName = function () {
return this.localName;
};
ScanRecord.prototype.getTxPowerLevel = function () {
return this.txPowerLevel;
};
return ScanRecord;
}());
var ScanAdvertisment = /** @class */ (function () {
function ScanAdvertisment(scanRecord) {
this.scanRecord = scanRecord;
}
Object.defineProperty(ScanAdvertisment.prototype, "manufacturerData", {
get: function () {
var data = this.scanRecord.getManufacturerSpecificData();
var size = data.size();
if (size > 0) {
var mKey = data.keyAt(0);
return byteArrayToBuffer(data.get(mKey));
}
return undefined;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "data", {
get: function () {
return byteArrayToBuffer(this.scanRecord.getBytes());
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "manufacturerId", {
get: function () {
var data = this.scanRecord.getManufacturerSpecificData();
var size = data.size();
if (size > 0) {
return data.keyAt(0);
}
return -1;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "txPowerLevel", {
get: function () {
return this.scanRecord.getTxPowerLevel();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "localName", {
get: function () {
return this.scanRecord.getDeviceName();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "flags", {
get: function () {
return this.scanRecord.getAdvertiseFlags();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "serviceUUIDs", {
get: function () {
var result = [];
var serviceUuids = this.scanRecord.getServiceUuids();
for (var i = 0; i < serviceUuids.length; i++) {
result.push(uuidToString(serviceUuids[i]));
}
return result;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "serviceData", {
get: function () {
var result = {};
var serviceData = this.scanRecord.getServiceData();
var keys = Object.keys(serviceData);
var currentKey;
for (var i = 0; i < keys.length; i++) {
currentKey = keys[i];
result[uuidToString(currentKey)] = byteArrayToBuffer(serviceData[currentKey]);
}
return result;
},
enumerable: true,
configurable: true
});
return ScanAdvertisment;
}());
// Helper method to extract bytes from byte array.
function extractBytes(scanRecord, start, length) {
// const bytes = new byte[length];
// System.arraycopy(scanRecord, start, bytes, 0, length);
return java.util.Arrays.copyOfRange(scanRecord, start, start + length);
}
var BASE_UUID;
function getBASE_UUID() {
if (!BASE_UUID) {
BASE_UUID = android.os.ParcelUuid.fromString('00000000-0000-1000-8000-00805F9B34FB');
}
return BASE_UUID;
}
/** Length of bytes for 16 bit UUID */
var UUID_BYTES_16_BIT = 2;
/** Length of bytes for 32 bit UUID */
var UUID_BYTES_32_BIT = 4;
/** Length of bytes for 128 bit UUID */
var UUID_BYTES_128_BIT = 16;
function parseUuidFrom(uuidBytes) {
if (uuidBytes == null) {
throw new Error('uuidBytes cannot be null');
}
var length = uuidBytes.length;
if (length !== UUID_BYTES_16_BIT && length !== UUID_BYTES_32_BIT && length !== UUID_BYTES_128_BIT) {
throw new Error('uuidBytes length invalid - ' + length);
}
// Construct a 128 bit UUID.
if (length === UUID_BYTES_128_BIT) {
var buf = java.nio.ByteBuffer.wrap(uuidBytes).order(java.nio.ByteOrder.LITTLE_ENDIAN);
var msb_1 = buf.getLong(8);
var lsb_1 = buf.getLong(0);
return new java.util.UUID(msb_1, lsb_1).toString();
}
// For 16 bit and 32 bit UUID we need to convert them to 128 bit value.
// 128_bit_value = uuid * 2^96 + BASE_UUID
var 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;
}
var msb = getBASE_UUID().getUuid().getMostSignificantBits() + (shortUuid << 32);
var lsb = getBASE_UUID().getUuid().getLeastSignificantBits();
return new java.util.UUID(msb, lsb).toString();
}
function parseServiceUuid(scanRecord, currentPos, dataLength, uuidLength, serviceUuids) {
while (dataLength > 0) {
var uuidBytes = extractBytes(scanRecord, currentPos, uuidLength);
serviceUuids.push(parseUuidFrom(uuidBytes));
dataLength -= uuidLength;
currentPos += uuidLength;
}
return currentPos;
}
var DATA_TYPE_FLAGS = 0x01;
var DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
var DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
var DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
var DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
var DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
var DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
var DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
var DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
var DATA_TYPE_TX_POWER_LEVEL = 0x0a;
var DATA_TYPE_SERVICE_DATA_16_BIT = 0x16;
var DATA_TYPE_SERVICE_DATA_32_BIT = 0x20;
var DATA_TYPE_SERVICE_DATA_128_BIT = 0x21;
var DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xff;
function parseFromBytes(scanRecord) {
if (scanRecord == null) {
return null;
}
var currentPos = 0;
var advertiseFlag = -1;
var serviceUuids = [];
var localName = null;
var txPowerLevel = Number.MIN_VALUE;
var manufacturerData = new android.util.SparseArray();
// const manufacturerData = null;
var serviceData = {};
try {
while (currentPos < scanRecord.length) {
// length is unsigned int.
var length_1 = scanRecord[currentPos++] & 0xff;
if (length_1 === 0) {
break;
}
// Note the length includes the length of the field type itself.
var dataLength = length_1 - 1;
// fieldType is unsigned int.
var fieldType = scanRecord[currentPos++] & 0xff;
switch (fieldType) {
case DATA_TYPE_FLAGS:
advertiseFlag = scanRecord[currentPos] & 0xff;
break;
case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos, dataLength, UUID_BYTES_16_BIT, serviceUuids);
break;
case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos, dataLength, UUID_BYTES_32_BIT, serviceUuids);
break;
case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos, dataLength, UUID_BYTES_128_BIT, serviceUuids);
break;
case DATA_TYPE_LOCAL_NAME_SHORT:
case DATA_TYPE_LOCAL_NAME_COMPLETE:
localName = String.fromCharCode.apply(String, extractBytes(scanRecord, currentPos, dataLength));
break;
case DATA_TYPE_TX_POWER_LEVEL:
txPowerLevel = scanRecord[currentPos];
break;
case DATA_TYPE_SERVICE_DATA_16_BIT:
case DATA_TYPE_SERVICE_DATA_32_BIT:
case DATA_TYPE_SERVICE_DATA_128_BIT:
var serviceUuidLength = UUID_BYTES_16_BIT;
if (fieldType === DATA_TYPE_SERVICE_DATA_32_BIT) {
serviceUuidLength = UUID_BYTES_32_BIT;
}
else if (fieldType === DATA_TYPE_SERVICE_DATA_128_BIT) {
serviceUuidLength = UUID_BYTES_128_BIT;
}
var serviceDataUuidBytes = extractBytes(scanRecord, currentPos, serviceUuidLength);
var serviceDataUuid = parseUuidFrom(serviceDataUuidBytes);
var serviceDataArray = extractBytes(scanRecord, currentPos + serviceUuidLength, dataLength - serviceUuidLength);
serviceData[serviceDataUuid] = serviceDataArray;
break;
case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
// The first two bytes of the manufacturer specific data are
// manufacturer ids in little endian.
var manufacturerId = ((scanRecord[currentPos + 1] & 0xff) << 8) + (scanRecord[currentPos] & 0xff);
var manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2, dataLength - 2);
manufacturerData.put(manufacturerId, manufacturerDataBytes);
break;
default:
// Just ignore, we don't handle such data type.
break;
}
currentPos += dataLength;
}
if (serviceUuids.length === 0) {
serviceUuids = null;
}
return new ScanRecord(serviceUuids, manufacturerData, serviceData, advertiseFlag, txPowerLevel, localName, scanRecord);
}
catch (e) {
// Log.e(TAG, 'unable to parse scan record: ' + Arrays.toString(scanRecord));
// As the record is invalid, ignore all the parsed results for this packet
// and return an empty record with raw scanRecord bytes in results
return new ScanRecord(null, null, null, -1, Number.MIN_VALUE, null, scanRecord);
}
}
var LeScanCallbackImpl = /** @class */ (function (_super) {
__extends(LeScanCallbackImpl, _super);
function LeScanCallbackImpl(owner) {
var _this = _super.call(this) || this;
_this.owner = owner;
/**
* Callback reporting an LE device found during a device scan initiated by the startLeScan(BluetoothAdapter.LeScanCallback) function.
* @param device [android.bluetooth.BluetoothDevice] - Identifies the remote device
* @param rssi [number] - The RSSI value for the remote device as reported by the Bluetooth hardware. 0 if no RSSI value is available.
* @param scanRecord [byte[]] - The content of the advertisement record offered by the remote device.
*/
return global.__native(_this);
}
LeScanCallbackImpl.prototype.onLeScan = function (device, rssi, data) {
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_LeScanCallback.onLeScan ---- device: " + device + ", rssi: " + rssi + ", scanRecord: " + data);
var stateObject = this.owner.get().connections[device.getAddress()];
if (!stateObject) {
stateObject = this.owner.get().connections[device.getAddress()] = {
state: 'disconnected',
};
var scanRecord = parseFromBytes(data);
var advertismentData = new ScanAdvertisment(scanRecord);
stateObject.advertismentData = advertismentData;
var payload = {
type: 'scanResult',
UUID: device.getAddress(),
name: device.getName(),
localName: advertismentData.localName,
RSSI: rssi,
state: 'disconnected',
advertismentData: advertismentData,
manufacturerId: advertismentData.manufacturerId,
};
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_LeScanCallback.onLeScan ---- payload: " + JSON.stringify(payload));
this.onPeripheralDiscovered && this.onPeripheralDiscovered(payload);
this.owner.get().sendEvent(Bluetooth.device_discovered_event, payload);
}
};
return LeScanCallbackImpl;
}(android.bluetooth.BluetoothAdapter.LeScanCallback));
LeScanCallbackVar = LeScanCallbackImpl;
}
var ScanCallbackVar;
function initScanCallback() {
if (ScanCallbackVar) {
return;
}
var ScanCallBackImpl = /** @class */ (function (_super) {
__extends(ScanCallBackImpl, _super);
function ScanCallBackImpl(owner) {
var _this = _super.call(this) || this;
_this.owner = owner;
return global.__native(_this);
}
/**
* Callback when batch results are delivered.
* @param results [List<android.bluetooth.le.ScanResult>] - List of scan results that are previously scanned.
*/
ScanCallBackImpl.prototype.onBatchScanResults = function (results) {
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_ScanCallback.onBatchScanResults ---- results: " + results);
};
/**
* Callback when scan could not be started.
* @param errorCode [number] - Error code (one of SCAN_FAILED_*) for scan failure.
*/
ScanCallBackImpl.prototype.onScanFailed = function (errorCode) {
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_ScanCallback.onScanFailed ---- errorCode: " + errorCode);
var errorMessage;
if (errorCode === android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED) {
errorMessage = 'Scan already started';
}
else if (errorCode === android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED) {
errorMessage = 'Application registration failed';
}
else if (errorCode === android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED) {
errorMessage = 'Feature unsupported';
}
else if (errorCode === android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR) {
errorMessage = 'Internal error';
}
else {
errorMessage = 'Scan failed to start';
}
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_ScanCallback.onScanFailed errorMessage: " + errorMessage);
};
/**
* Callback when a BLE advertisement has been found.
* @param callbackType [number] - Determines how this callback was triggered. Could be one of CALLBACK_TYPE_ALL_MATCHES, CALLBACK_TYPE_FIRST_MATCH or CALLBACK_TYPE_MATCH_LOST
* @param result [android.bluetooth.le.ScanResult] - A Bluetooth LE scan result.
*/
ScanCallBackImpl.prototype.onScanResult = function (callbackType, result) {
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_ScanCallback.onScanResult ---- callbackType: " + callbackType + ", result: " + result);
var stateObject = this.owner.get().connections[result.getDevice().getAddress()];
if (!stateObject) {
stateObject = this.owner.get().connections[result.getDevice().getAddress()] = {
state: 'disconnected',
};
}
var advertismentData = new ScanAdvertisment(result.getScanRecord());
stateObject.advertismentData = advertismentData;
var payload = {
type: 'scanResult',
UUID: result.getDevice().getAddress(),
name: result.getDevice().getName(),
RSSI: result.getRssi(),
localName: advertismentData.localName,
state: 'disconnected',
manufacturerId: advertismentData.manufacturerId,
advertismentData: advertismentData,
};
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_ScanCallback.onScanResult ---- payload: " + JSON.stringify(payload));
this.onPeripheralDiscovered && this.onPeripheralDiscovered(payload);
this.owner.get().sendEvent(Bluetooth.device_discovered_event, payload);
};
return ScanCallBackImpl;
}(android.bluetooth.le.ScanCallback));
var ScanAdvertisment = /** @class */ (function () {
function ScanAdvertisment(scanRecord) {
this.scanRecord = scanRecord;
}
Object.defineProperty(ScanAdvertisment.prototype, "manufacturerData", {
get: function () {
var data = this.scanRecord.getManufacturerSpecificData();
var size = data ? data.size() : 0;
if (size > 0) {
var mKey = data.keyAt(0);
return byteArrayToBuffer(data.get(mKey));
}
return undefined;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "data", {
get: function () {
return byteArrayToBuffer(this.scanRecord.getBytes());
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "manufacturerId", {
get: function () {
var data = this.scanRecord.getManufacturerSpecificData();
var size = data ? data.size() : 0;
if (size > 0) {
return data.keyAt(0);
}
return -1;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "txPowerLevel", {
get: function () {
return this.scanRecord.getTxPowerLevel();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "localName", {
get: function () {
var deviceName = this.scanRecord.getDeviceName();
if (deviceName) {
deviceName = deviceName.replace('\0', '').replace('�', '');
}
return deviceName;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "flags", {
get: function () {
return this.scanRecord.getAdvertiseFlags();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "serviceUUIDs", {
get: function () {
var result = [];
var serviceUuids = this.scanRecord.getServiceUuids();
if (serviceUuids) {
for (var i = 0; i < serviceUuids.size(); i++) {
result.push(uuidToString(serviceUuids[i]));
}
}
return result;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScanAdvertisment.prototype, "serviceData", {
get: function () {
var result = {};
var serviceData = this.scanRecord.getServiceData();
if (serviceData && serviceData.size() > 0) {
var entries = serviceData.entrySet().iterator();
while (entries.hasNext()) {
var entry = entries.next();
result[uuidToString(entry.getKey())] = byteArrayToBuffer(entry.getValue());
}
}
return result;
},
enumerable: true,
configurable: true
});
return ScanAdvertisment;
}());
ScanCallbackVar = ScanCallBackImpl;
}
var BluetoothGattCallback;
function initBluetoothGattCallback() {
if (BluetoothGattCallback) {
return;
}
var BluetoothGattCallbackImpl = /** @class */ (function (_super) {
__extends(BluetoothGattCallbackImpl, _super);
// private owner: WeakRef<Bluetooth>;
function BluetoothGattCallbackImpl(owner) {
var _this = _super.call(this) || this;
_this.owner = owner;
_this.subDelegates = [];
return global.__native(_this);
}
BluetoothGattCallbackImpl.prototype.addSubDelegate = function (delegate) {
var index = this.subDelegates.indexOf(delegate);
// CLog(CLogTypes.info, `TNS_BluetoothGattCallback.addSubDelegate ---- index: ${index}, subdelegates:${this.subDelegates.length}`);
if (index === -1) {
this.subDelegates.push(delegate);
}
};
BluetoothGattCallbackImpl.prototype.removeSubDelegate = function (delegate) {
var index = this.subDelegates.indexOf(delegate);
// CLog(CLogTypes.info, `TNS_BluetoothGattCallback.removeSubDelegate ---- index: ${index}, subdelegates:${this.subDelegates.length}`);
if (index !== -1) {
this.subDelegates.splice(index, 1);
}
};
/**
* Callback indicating when GATT client has connected/disconnected to/from a remote GATT server.
* @param bluetoothGatt [android.bluetooth.BluetoothGatt] - GATT client
* @param status [number] - Status of the connect or disconnect operation. GATT_SUCCESS if the operation succeeds.
* @param newState [number] - Returns the new connection state. Can be one of STATE_DISCONNECTED or STATE_CONNECTED
*/
BluetoothGattCallbackImpl.prototype.onConnectionStateChange = function (gatt, status, newState) {
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_BluetoothGattCallback.onConnectionStateChange ---- gatt: " + gatt + ", device:" + (gatt.getDevice() && gatt.getDevice().getAddress()) + " status: " + status + ", newState: " + newState + ", subdelegates:" + this.subDelegates.length);
this.subDelegates.forEach(function (d) {
if (d.onConnectionStateChange) {
d.onConnectionStateChange(gatt, status, newState);
}
});
if (newState === android.bluetooth.BluetoothProfile.STATE_CONNECTED && status === GATT_SUCCESS) {
var device = gatt.getDevice();
var address = null;
if (device == null) {
// happens some time, why ... ?
}
else {
address = device.getAddress();
}
var stateObject = this.owner.get().connections[address];
if (!stateObject) {
this.owner.get().gattDisconnect(gatt);
}
}
else {
// perhaps the device was manually disconnected, or in use by another device
this.owner.get().gattDisconnect(gatt);
}
};
/**
* Callback invoked when the list of remote services, characteristics and descriptors for the remote device have been updated, ie new services have been discovered.
* @param gatt [android.bluetooth.BluetoothGatt] - GATT client invoked discoverServices()
* @param status [number] - GATT_SUCCESS if the remote device has been explored successfully.
*/
BluetoothGattCallbackImpl.prototype.onServicesDiscovered = function (gatt, status) {
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_BluetoothGattCallback.onServicesDiscovered ---- gatt: " + gatt + ", status (0=success): " + status + " " + this.subDelegates);
this.subDelegates.forEach(function (d) {
if (d.onServicesDiscovered) {
d.onServicesDiscovered(gatt, status);
}
});
};
/**
* Callback reporting the result of a characteristic read operation.
* @param gatt [android.bluetooth.BluetoothGatt] - GATT client invoked readCharacteristic(BluetoothGattCharacteristic)
* @param characteristic - Characteristic that was read from the associated remote device.
* @param status [number] - GATT_SUCCESS if the read operation was completed successfully.
*/
BluetoothGattCallbackImpl.prototype.onCharacteristicRead = function (gatt, characteristic, status) {
this.subDelegates.forEach(function (d) {
if (d.onCharacteristicRead) {
d.onCharacteristicRead(gatt, characteristic, status);
}
});
};
/**
* Callback triggered as a result of a remote characteristic notification.
* @param gatt [android.bluetooth.BluetoothGatt] - GATT client the characteristic is associated with.
* @param characteristic [android.bluetooth.BluetoothGattCharacteristic] - Characteristic that has been updated as a result of a remote notification event.
*/
BluetoothGattCallbackImpl.prototype.onCharacteristicChanged = function (gatt, characteristic) {
var device = gatt.getDevice();
var address = null;
if (device == null) {
// happens some time, why ... ?
}
else {
address = device.getAddress();
}
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_BluetoothGattCallback.onCharacteristicChanged ---- gatt: " + gatt + ", characteristic: " + characteristic + ", device: " + address);
this.subDelegates.forEach(function (d) {
if (d.onCharacteristicChanged) {
d.onCharacteristicChanged(gatt, characteristic);
}
});
var stateObject = this.owner.get().connections[address];
if (stateObject) {
var cUUID = uuidToString(characteristic.getUuid());
var sUUID = uuidToString(characteristic.getService().getUuid());
var key = sUUID + '/' + cUUID;
if (stateObject.onNotifyCallbacks && stateObject.onNotifyCallbacks[key]) {
var value = characteristic.getValue();
stateObject.onNotifyCallbacks[key]({
android: value,
value: byteArrayToBuffer(value),
serviceUUID: sUUID,
characteristicUUID: cUUID,
});
}
}
};
/**
* Callback indicating the result of a characteristic write operation.
* If this callback is invoked while a reliable write transaction is in progress, the value of the characteristic represents the value reported by the remote device.
* An application should compare this value to the desired value to be written.
* If the values don't match, the application must abort the reliable write transaction.
* @param gatt - GATT client invoked writeCharacteristic(BluetoothGattCharacteristic)
* @param characteristic - Characteristic that was written to the associated remote device.
* @param status - The result of the write operation GATT_SUCCESS if the operation succeeds.
*/
BluetoothGattCallbackImpl.prototype.onCharacteristicWrite = function (gatt, characteristic, status) {
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_BluetoothGattCallback.onCharacteristicWrite ---- gatt: " + gatt + ", characteristic: " + characteristic);
this.subDelegates.forEach(function (d) {
if (d.onCharacteristicWrite) {
d.onCharacteristicWrite(gatt, characteristic, status);
}
});
};
/**
* Callback reporting the result of a descriptor read operation.
* @param gatt - GATT client invoked readDescriptor(BluetoothGattDescriptor)
* @param descriptor - Descriptor that was read from the associated remote device.
* @param status - GATT_SUCCESS if the read operation was completed successfully
*/
BluetoothGattCallbackImpl.prototype.onDescriptorRead = function (gatt, descriptor, status) {
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_BluetoothGattCallback.onDescriptorRead ---- gatt: " + gatt + ", descriptor: " + descriptor + ", status: " + status);
this.subDelegates.forEach(function (d) {
if (d.onDescriptorRead) {
d.onDescriptorRead(gatt, descriptor, status);
}
});
};
/**
* Callback indicating the result of a descriptor write operation.
* @param gatt - GATT client invoked writeDescriptor(BluetoothGattDescriptor).
* @param descriptor - Descriptor that was written to the associated remote device.
* @param status - The result of the write operation GATT_SUCCESS if the operation succeeds.
*/
BluetoothGattCallbackImpl.prototype.onDescriptorWrite = function (gatt, descriptor, status) {
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_BluetoothGattCallback.onDescriptorWrite ---- gatt: " + gatt + ", descriptor: " + descriptor + ", status: " + status);
this.subDelegates.forEach(function (d) {
if (d.onDescriptorWrite) {
d.onDescriptorWrite(gatt, descriptor, status);
}
});
};
/**
* Callback reporting the RSSI for a remote device connection. This callback is triggered in response to the readRemoteRssi() function.
* @param gatt - GATT client invoked readRemoteRssi().
* @param rssi - The RSSI value for the remote device.
* @param status - GATT_SUCCESS if the RSSI was read successfully.
*/
BluetoothGattCallbackImpl.prototype.onReadRemoteRssi = function (gatt, rssi, status) {
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_BluetoothGattCallback.onReadRemoteRssi ---- gatt: " + gatt + " rssi: " + rssi + ", status: " + status);
};
/**
* Callback indicating the MTU for a given device connection has changed. This callback is triggered in response to the requestMtu(int) function, or in response to a connection event.
* @param gatt - GATT client invoked requestMtu(int).
* @param mtu - The new MTU size.
* @param status - GATT_SUCCESS if the MTU has been changed successfully.
*/
BluetoothGattCallbackImpl.prototype.onMtuChanged = function (gatt, mtu, status) {
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_BluetoothGattCallback.onMtuChanged ---- gatt: " + gatt + " mtu: " + mtu + ", status: " + status);
var owner = this.owner.get();
if (owner) {
owner.notify({
eventName: 'mtu',
object: owner,
data: mtu,
});
}
this.subDelegates.forEach(function (d) {
if (d.onMtuChanged) {
d.onMtuChanged(gatt, mtu, status);
}
});
};
return BluetoothGattCallbackImpl;
}(android.bluetooth.BluetoothGattCallback));
BluetoothGattCallback = BluetoothGattCallbackImpl;
}
function getGattDeviceServiceInfo(gatt) {
var services = gatt.getServices();
var servicesJs = [];
var BluetoothGattCharacteristic = android.bluetooth.BluetoothGattCharacteristic;
for (var i = 0; i < services.size(); i++) {
var service = services.get(i);
var characteristics = service.getCharacteristics();
var characteristicsJs = [];
for (var j = 0; j < characteristics.size(); j++) {
var characteristic = characteristics.get(j);
var props = characteristic.getProperties();
var descriptors = characteristic.getDescriptors();
var descriptorsJs = [];
for (var k = 0; k < descriptors.size(); k++) {
var descriptor = descriptors.get(k);
var descriptorJs = {
UUID: uuidToString(descriptor.getUuid()),
value: descriptor.getValue(),
permissions: null,
};
var descPerms = descriptor.getPermissions();
if (descPerms > 0) {
descriptorJs.permissions = {
read: (descPerms & BluetoothGattCharacteristic.PERMISSION_READ) !== 0,
readEncrypted: (descPerms & BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED) !== 0,
readEncryptedMitm: (descPerms & BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED_MITM) !== 0,
write: (descPerms & BluetoothGattCharacteristic.PERMISSION_WRITE) !== 0,
writeEncrypted: (descPerms & BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED) !== 0,
writeEncryptedMitm: (descPerms & BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED_MITM) !== 0,
writeSigned: (descPerms & BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED) !== 0,
writeSignedMitm: (descPerms & BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED_MITM) !== 0,
};
}
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_BluetoothGattCallback.onServicesDiscovered ---- pushing descriptor: " + descriptor);
descriptorsJs.push(descriptorJs);
}
var characteristicJs = {
serviceUUID: uuidToString(service.getUuid()),
UUID: uuidToString(characteristic.getUuid()),
name: uuidToString(characteristic.getUuid()),
properties: {
read: (props & BluetoothGattCharacteristic.PROPERTY_READ) !== 0,
write: (props & BluetoothGattCharacteristic.PROPERTY_WRITE) !== 0,
writeWithoutResponse: (props & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) !== 0,
notify: (props & BluetoothGattCharacteristic.PROPERTY_NOTIFY) !== 0,
indicate: (props & BluetoothGattCharacteristic.PROPERTY_INDICATE) !== 0,
broadcast: (props & BluetoothGattCharacteristic.PROPERTY_BROADCAST) !== 0,
authenticatedSignedWrites: (props & BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE) !== 0,
extendedProperties: (props & BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) !== 0,
},
descriptors: descriptorsJs,
permissions: null,
};
// permissions are usually not provided, so let's not return them in that case
var charPerms = characteristic.getPermissions();
if (charPerms > 0) {
characteristicJs.permissions = {
read: (charPerms & BluetoothGattCharacteristic.PERMISSION_READ) !== 0,
readEncrypted: (charPerms & BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED) !== 0,
readEncryptedMitm: (charPerms & BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED_MITM) !== 0,
write: (charPerms & BluetoothGattCharacteristic.PERMISSION_WRITE) !== 0,
writeEncrypted: (charPerms & BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED) !== 0,
writeEncryptedMitm: (charPerms & BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED_MITM) !== 0,
writeSigned: (charPerms & BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED) !== 0,
writeSignedMitm: (charPerms & BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED_MITM) !== 0,
};
}
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, "TNS_BluetoothGattCallback.onServicesDiscovered ---- pushing characteristic: " + JSON.stringify(characteristicJs));
characteristicsJs.push(characteristicJs);
}
servicesJs.push({
UUID: uuidToString(service.getUuid()),
characteristics: characteristicsJs,
});
}
return { services: servicesJs };
}
var Bluetooth = /** @class */ (function (_super) {
__extends(Bluetooth, _super);
function Bluetooth() {
var _this = _super.call(this) || this;
/**
* Connections are stored as key-val pairs of UUID-Connection.
* So something like this:
* [{
* 34343-2434-5454: {
* state: 'connected',
* discoveredState: '',
* operationConnect: someCallbackFunction
* },
* 1323213-21321323: {
* ..
* }
* }, ..]
*/
_this.connections = {};
_this.broadcastRegistered = false;
_this.disconnectListeners = [];
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, '*** Android Bluetooth Constructor ***');
// if >= Android21 (Lollipop)
if (android.os.Build.VERSION.SDK_INT >= LOLLIPOP) {
initScanCallback();
_this.scanCallback = new ScanCallbackVar(new WeakRef(_this));
}
else if (android.os.Build.VERSION.SDK_INT >= JELLY_BEAN) {
initLeScanCallback();
_this.LeScanCallback = new LeScanCallbackVar(new WeakRef(_this));
}
_this.gattQueue = new p_queue_1.default({ concurrency: 1 });
return _this;
}
Object.defineProperty(Bluetooth.prototype, "adapter", {
get: function () {
if (!this._adapter) {
this._adapter = this.bluetoothManager.getAdapter();
}
return this._adapter;
},
enumerable: true,
configurable: true
});
Object.defineProperty(Bluetooth.prototype, "bluetoothManager", {
get: function () {
if (!this._bluetoothManager) {
this._bluetoothManager = utils_1.ad.getApplicationContext().getSystemService(android.content.Context.BLUETOOTH_SERVICE);
}
return this._bluetoothManager;
},
enumerable: true,
configurable: true
});
Object.defineProperty(Bluetooth.prototype, "bluetoothGattCallback", {
get: function () {
if (!this._bluetoothGattCallback) {
initBluetoothGattCallback();
this._bluetoothGattCallback = new BluetoothGattCallback(new WeakRef(this));
}
return this._bluetoothGattCallback;
},
enumerable: true,
configurable: true
});
Bluetooth.prototype.clear = function () {
this.gattQueue.clear();
};
Bluetooth.prototype.registerBroadcast = function () {
var _this = this;
if (this.broadcastRegistered) {
return;
}
this.broadcastRegistered = true;
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, 'Android Bluetooth registering for state change');
application_1.android.registerBroadcastReceiver(android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED, function (context, intent) {
var state = intent.getIntExtra(android.bluetooth.BluetoothAdapter.EXTRA_STATE, android.bluetooth.BluetoothAdapter.ERROR);
bluetooth_common_1.CLog(bluetooth_common_1.CLogTypes.info, 'Android Bluetooth ACTION_STATE_CHANGED', state, android.bluetooth.BluetoothAdapter.STATE_ON, android.bluetooth.BluetoothAdapter.STATE_OFF);
if (state === android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF) {
// ensure all connections are closed correctly on bluetooth closing