UNPKG

nativescript-bluetooth

Version:
1,052 lines (1,051 loc) 126 kB
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