package com.megster.cordova.ble.central; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Build; import android.os.Handler; import android.provider.Settings; import androidx.core.view.inputmethod.EditorInfoCompat; import java.util.ArrayList; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaArgs; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.LOG; import org.apache.cordova.PermissionHelper; import org.apache.cordova.PluginResult; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class BLECentralPlugin extends CordovaPlugin implements BluetoothAdapter.LeScanCallback { private static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; private static final String AUTOCONNECT = "autoConnect"; private static final String BONDED_DEVICES = "bondedDevices"; private static final String CONNECT = "connect"; private static final String DISCONNECT = "disconnect"; private static final String ENABLE = "enable"; private static final String IS_CONNECTED = "isConnected"; private static final String IS_ENABLED = "isEnabled"; private static final String IS_LOCATION_ENABLED = "isLocationEnabled"; private static final String LIST = "list"; private static final String QUEUE_CLEANUP = "queueCleanup"; private static final String READ = "read"; private static final String READ_RSSI = "readRSSI"; private static final String REFRESH_DEVICE_CACHE = "refreshDeviceCache"; private static final int REQUEST_ACCESS_COARSE_LOCATION = 2; private static final String REQUEST_CONNECTION_PRIORITY = "requestConnectionPriority"; private static final int REQUEST_ENABLE_BLUETOOTH = 1; private static final String REQUEST_MTU = "requestMtu"; private static final String SCAN = "scan"; private static final String SETTINGS = "showBluetoothSettings"; private static final String SET_PIN = "setPin"; private static final String START_NOTIFICATION = "startNotification"; private static final String START_SCAN = "startScan"; private static final String START_SCAN_WITH_OPTIONS = "startScanWithOptions"; private static final String START_STATE_NOTIFICATIONS = "startStateNotifications"; private static final String STOP_NOTIFICATION = "stopNotification"; private static final String STOP_SCAN = "stopScan"; private static final String STOP_STATE_NOTIFICATIONS = "stopStateNotifications"; private static final String TAG = "BLEPlugin"; private static final String WRITE = "write"; private static final String WRITE_WITHOUT_RESPONSE = "writeWithoutResponse"; private final String CONNECTION_PRIORITY_BALANCED = "balanced"; private final String CONNECTION_PRIORITY_HIGH = "high"; private final String CONNECTION_PRIORITY_LOW = "low"; BluetoothAdapter bluetoothAdapter; Map<Integer, String> bluetoothStates = new Hashtable<Integer, String>() { /* class com.megster.cordova.ble.central.BLECentralPlugin.AnonymousClass1 */ { put(10, "off"); put(13, "turningOff"); put(12, "on"); put(11, "turningOn"); } }; BroadcastReceiver broadCastReceiver; CallbackContext discoverCallback; private CallbackContext enableBluetoothCallback; Map<String, Peripheral> peripherals = new LinkedHashMap(); private CallbackContext permissionCallback; boolean reportDuplicates = false; private int scanSeconds; private UUID[] serviceUUIDs; CallbackContext stateCallback; BroadcastReceiver stateReceiver; @Override // org.apache.cordova.CordovaPlugin public void onDestroy() { removeStateListener(); } @Override // org.apache.cordova.CordovaPlugin public void onReset() { removeStateListener(); } @Override // org.apache.cordova.CordovaPlugin public boolean execute(String str, CordovaArgs cordovaArgs, CallbackContext callbackContext) throws JSONException { LOG.d(TAG, "action = %s", str); if (this.bluetoothAdapter == null) { Activity activity = this.f0cordova.getActivity(); if (!(activity.getApplicationContext().getPackageManager().hasSystemFeature("android.hardware.bluetooth_le") && Build.VERSION.SDK_INT >= 18)) { LOG.w(TAG, "This hardware does not support Bluetooth Low Energy."); callbackContext.error("This hardware does not support Bluetooth Low Energy."); return false; } this.bluetoothAdapter = ((BluetoothManager) activity.getSystemService("bluetooth")).getAdapter(); } if (str.equals(SCAN)) { UUID[] parseServiceUUIDList = parseServiceUUIDList(cordovaArgs.getJSONArray(0)); int i = cordovaArgs.getInt(1); resetScanOptions(); findLowEnergyDevices(callbackContext, parseServiceUUIDList, i); } else if (str.equals(START_SCAN)) { UUID[] parseServiceUUIDList2 = parseServiceUUIDList(cordovaArgs.getJSONArray(0)); resetScanOptions(); findLowEnergyDevices(callbackContext, parseServiceUUIDList2, -1); } else if (str.equals(STOP_SCAN)) { this.bluetoothAdapter.stopLeScan(this); callbackContext.success(); } else if (str.equals(LIST)) { listKnownDevices(callbackContext); } else if (str.equals(CONNECT)) { connect(callbackContext, cordovaArgs.getString(0)); } else if (str.equals(AUTOCONNECT)) { autoConnect(callbackContext, cordovaArgs.getString(0)); } else if (str.equals(DISCONNECT)) { disconnect(callbackContext, cordovaArgs.getString(0)); } else if (str.equals(QUEUE_CLEANUP)) { queueCleanup(callbackContext, cordovaArgs.getString(0)); } else if (str.equals(SET_PIN)) { setPin(callbackContext, cordovaArgs.getString(0)); } else if (str.equals(REQUEST_MTU)) { requestMtu(callbackContext, cordovaArgs.getString(0), cordovaArgs.getInt(1)); } else if (str.equals(REQUEST_CONNECTION_PRIORITY)) { requestConnectionPriority(callbackContext, cordovaArgs.getString(0), cordovaArgs.getString(1)); } else if (str.equals(REFRESH_DEVICE_CACHE)) { refreshDeviceCache(callbackContext, cordovaArgs.getString(0), cordovaArgs.getLong(1)); } else if (str.equals(READ)) { read(callbackContext, cordovaArgs.getString(0), uuidFromString(cordovaArgs.getString(1)), uuidFromString(cordovaArgs.getString(2))); } else if (str.equals(READ_RSSI)) { readRSSI(callbackContext, cordovaArgs.getString(0)); } else if (str.equals(WRITE)) { write(callbackContext, cordovaArgs.getString(0), uuidFromString(cordovaArgs.getString(1)), uuidFromString(cordovaArgs.getString(2)), cordovaArgs.getArrayBuffer(3), 2); } else if (str.equals(WRITE_WITHOUT_RESPONSE)) { write(callbackContext, cordovaArgs.getString(0), uuidFromString(cordovaArgs.getString(1)), uuidFromString(cordovaArgs.getString(2)), cordovaArgs.getArrayBuffer(3), 1); } else if (str.equals(START_NOTIFICATION)) { registerNotifyCallback(callbackContext, cordovaArgs.getString(0), uuidFromString(cordovaArgs.getString(1)), uuidFromString(cordovaArgs.getString(2))); } else if (str.equals(STOP_NOTIFICATION)) { removeNotifyCallback(callbackContext, cordovaArgs.getString(0), uuidFromString(cordovaArgs.getString(1)), uuidFromString(cordovaArgs.getString(2))); } else if (str.equals(IS_ENABLED)) { if (this.bluetoothAdapter.isEnabled()) { callbackContext.success(); } else { callbackContext.error("Bluetooth is disabled."); } } else if (str.equals(IS_LOCATION_ENABLED)) { if (locationServicesEnabled()) { callbackContext.success(); } else { callbackContext.error("Location services disabled."); } } else if (str.equals(IS_CONNECTED)) { String string = cordovaArgs.getString(0); if (!this.peripherals.containsKey(string) || !this.peripherals.get(string).isConnected()) { callbackContext.error("Not connected."); } else { callbackContext.success(); } } else if (str.equals(SETTINGS)) { this.f0cordova.getActivity().startActivity(new Intent("android.settings.BLUETOOTH_SETTINGS")); callbackContext.success(); } else if (str.equals(ENABLE)) { this.enableBluetoothCallback = callbackContext; this.f0cordova.startActivityForResult(this, new Intent("android.bluetooth.adapter.action.REQUEST_ENABLE"), 1); } else if (str.equals(START_STATE_NOTIFICATIONS)) { if (this.stateCallback != null) { callbackContext.error("State callback already registered."); } else { this.stateCallback = callbackContext; addStateListener(); sendBluetoothStateChange(this.bluetoothAdapter.getState()); } } else if (str.equals(STOP_STATE_NOTIFICATIONS)) { if (this.stateCallback != null) { PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT); pluginResult.setKeepCallback(false); this.stateCallback.sendPluginResult(pluginResult); this.stateCallback = null; } removeStateListener(); callbackContext.success(); } else if (str.equals(START_SCAN_WITH_OPTIONS)) { UUID[] parseServiceUUIDList3 = parseServiceUUIDList(cordovaArgs.getJSONArray(0)); JSONObject jSONObject = cordovaArgs.getJSONObject(1); resetScanOptions(); this.reportDuplicates = jSONObject.optBoolean("reportDuplicates", false); findLowEnergyDevices(callbackContext, parseServiceUUIDList3, -1); } else if (!str.equals(BONDED_DEVICES)) { return false; } else { getBondedDevices(callbackContext); } return true; } private void getBondedDevices(CallbackContext callbackContext) { JSONArray jSONArray = new JSONArray(); for (BluetoothDevice bluetoothDevice : this.bluetoothAdapter.getBondedDevices()) { bluetoothDevice.getBondState(); int type = bluetoothDevice.getType(); if (type == 2 || type == 3) { jSONArray.put(new Peripheral(bluetoothDevice).asJSONObject()); } } callbackContext.success(jSONArray); } private UUID[] parseServiceUUIDList(JSONArray jSONArray) throws JSONException { ArrayList arrayList = new ArrayList(); for (int i = 0; i < jSONArray.length(); i++) { arrayList.add(uuidFromString(jSONArray.getString(i))); } return (UUID[]) arrayList.toArray(new UUID[jSONArray.length()]); } /* access modifiers changed from: private */ /* access modifiers changed from: public */ private void onBluetoothStateChange(Intent intent) { if (intent.getAction().equals("android.bluetooth.adapter.action.STATE_CHANGED")) { sendBluetoothStateChange(intent.getIntExtra("android.bluetooth.adapter.extra.STATE", EditorInfoCompat.IME_FLAG_FORCE_ASCII)); } } private void sendBluetoothStateChange(int i) { if (this.stateCallback != null) { PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, this.bluetoothStates.get(Integer.valueOf(i))); pluginResult.setKeepCallback(true); this.stateCallback.sendPluginResult(pluginResult); } } private void addStateListener() { if (this.stateReceiver == null) { this.stateReceiver = new BroadcastReceiver() { /* class com.megster.cordova.ble.central.BLECentralPlugin.AnonymousClass2 */ public void onReceive(Context context, Intent intent) { BLECentralPlugin.this.onBluetoothStateChange(intent); } }; } try { this.webView.getContext().registerReceiver(this.stateReceiver, new IntentFilter("android.bluetooth.adapter.action.STATE_CHANGED")); } catch (Exception e) { LOG.e(TAG, "Error registering state receiver: " + e.getMessage(), e); } } private void removeStateListener() { if (this.stateReceiver != null) { try { this.webView.getContext().unregisterReceiver(this.stateReceiver); } catch (Exception e) { LOG.e(TAG, "Error unregistering state receiver: " + e.getMessage(), e); } } this.stateCallback = null; this.stateReceiver = null; } private void connect(CallbackContext callbackContext, String str) { if (!this.peripherals.containsKey(str)) { BluetoothAdapter bluetoothAdapter2 = this.bluetoothAdapter; if (BluetoothAdapter.checkBluetoothAddress(str)) { this.peripherals.put(str, new Peripheral(this.bluetoothAdapter.getRemoteDevice(str))); } } Peripheral peripheral = this.peripherals.get(str); if (peripheral != null) { peripheral.connect(callbackContext, this.f0cordova.getActivity(), false); return; } callbackContext.error("Peripheral " + str + " not found."); } private void autoConnect(CallbackContext callbackContext, String str) { Peripheral peripheral = this.peripherals.get(str); if (peripheral == null) { if (BluetoothAdapter.checkBluetoothAddress(str)) { BluetoothDevice remoteDevice = this.bluetoothAdapter.getRemoteDevice(str); peripheral = new Peripheral(remoteDevice); this.peripherals.put(remoteDevice.getAddress(), peripheral); } else { callbackContext.error(str + " is not a valid MAC address."); return; } } peripheral.connect(callbackContext, this.f0cordova.getActivity(), true); } private void disconnect(CallbackContext callbackContext, String str) { Peripheral peripheral = this.peripherals.get(str); if (peripheral != null) { peripheral.disconnect(); callbackContext.success(); return; } String str2 = "Peripheral " + str + " not found."; LOG.w(TAG, str2); callbackContext.error(str2); } private void queueCleanup(CallbackContext callbackContext, String str) { Peripheral peripheral = this.peripherals.get(str); if (peripheral != null) { peripheral.queueCleanup(); } callbackContext.success(); } private void setPin(CallbackContext callbackContext, final String str) { try { if (this.broadCastReceiver != null) { this.webView.getContext().unregisterReceiver(this.broadCastReceiver); } this.broadCastReceiver = new BroadcastReceiver() { /* class com.megster.cordova.ble.central.BLECentralPlugin.AnonymousClass3 */ public void onReceive(Context context, Intent intent) { if ("android.bluetooth.device.action.PAIRING_REQUEST".equals(intent.getAction())) { BluetoothDevice bluetoothDevice = (BluetoothDevice) intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE"); if (intent.getIntExtra("android.bluetooth.device.extra.PAIRING_VARIANT", EditorInfoCompat.IME_FLAG_FORCE_ASCII) == 0) { bluetoothDevice.setPin(str.getBytes()); abortBroadcast(); } } } }; IntentFilter intentFilter = new IntentFilter("android.bluetooth.device.action.PAIRING_REQUEST"); intentFilter.setPriority(1000); this.webView.getContext().registerReceiver(this.broadCastReceiver, intentFilter); callbackContext.success("OK"); } catch (Exception e) { callbackContext.error("Error: " + e.getMessage()); } } private void requestMtu(CallbackContext callbackContext, String str, int i) { Peripheral peripheral = this.peripherals.get(str); if (peripheral != null) { peripheral.requestMtu(callbackContext, i); return; } String str2 = "Peripheral " + str + " not found."; LOG.w(TAG, str2); callbackContext.error(str2); } private void requestConnectionPriority(CallbackContext callbackContext, String str, String str2) { Peripheral peripheral = this.peripherals.get(str); if (peripheral == null) { callbackContext.error("Peripheral " + str + " not found."); } else if (!peripheral.isConnected()) { callbackContext.error("Peripheral " + str + " is not connected."); } else { int i = 0; if (str2.equals("low")) { i = 2; } else if (!str2.equals("balanced") && str2.equals("high")) { i = 1; } peripheral.requestConnectionPriority(i); callbackContext.success(); } } private void refreshDeviceCache(CallbackContext callbackContext, String str, long j) { Peripheral peripheral = this.peripherals.get(str); if (peripheral != null) { peripheral.refreshDeviceCache(callbackContext, j); return; } String str2 = "Peripheral " + str + " not found."; LOG.w(TAG, str2); callbackContext.error(str2); } private void read(CallbackContext callbackContext, String str, UUID uuid, UUID uuid2) { Peripheral peripheral = this.peripherals.get(str); if (peripheral == null) { callbackContext.error("Peripheral " + str + " not found."); } else if (!peripheral.isConnected()) { callbackContext.error("Peripheral " + str + " is not connected."); } else { peripheral.queueRead(callbackContext, uuid, uuid2); } } private void readRSSI(CallbackContext callbackContext, String str) { Peripheral peripheral = this.peripherals.get(str); if (peripheral == null) { callbackContext.error("Peripheral " + str + " not found."); } else if (!peripheral.isConnected()) { callbackContext.error("Peripheral " + str + " is not connected."); } else { peripheral.queueReadRSSI(callbackContext); } } private void write(CallbackContext callbackContext, String str, UUID uuid, UUID uuid2, byte[] bArr, int i) { Peripheral peripheral = this.peripherals.get(str); if (peripheral == null) { callbackContext.error("Peripheral " + str + " not found."); } else if (!peripheral.isConnected()) { callbackContext.error("Peripheral " + str + " is not connected."); } else { peripheral.queueWrite(callbackContext, uuid, uuid2, bArr, i); } } private void registerNotifyCallback(CallbackContext callbackContext, String str, UUID uuid, UUID uuid2) { Peripheral peripheral = this.peripherals.get(str); if (peripheral == null) { callbackContext.error("Peripheral " + str + " not found"); } else if (!peripheral.isConnected()) { callbackContext.error("Peripheral " + str + " is not connected."); } else { peripheral.queueRegisterNotifyCallback(callbackContext, uuid, uuid2); } } private void removeNotifyCallback(CallbackContext callbackContext, String str, UUID uuid, UUID uuid2) { Peripheral peripheral = this.peripherals.get(str); if (peripheral == null) { callbackContext.error("Peripheral " + str + " not found"); } else if (!peripheral.isConnected()) { callbackContext.error("Peripheral " + str + " is not connected."); } else { peripheral.queueRemoveNotifyCallback(callbackContext, uuid, uuid2); } } private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] uuidArr, int i) { if (!locationServicesEnabled()) { LOG.w(TAG, "Location Services are disabled"); } if (!PermissionHelper.hasPermission(this, ACCESS_COARSE_LOCATION)) { this.permissionCallback = callbackContext; this.serviceUUIDs = uuidArr; this.scanSeconds = i; PermissionHelper.requestPermission(this, 2, ACCESS_COARSE_LOCATION); } else if (this.bluetoothAdapter.isDiscovering()) { LOG.w(TAG, "Tried to start scan while already running."); callbackContext.error("Tried to start scan while already running."); } else { Iterator<Map.Entry<String, Peripheral>> it = this.peripherals.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Peripheral> next = it.next(); Peripheral value = next.getValue(); boolean isConnecting = value.isConnecting(); if (isConnecting) { LOG.d(TAG, "Not removing connecting device: " + value.getDevice().getAddress()); } if (!next.getValue().isConnected() && !isConnecting) { it.remove(); } } this.discoverCallback = callbackContext; if (uuidArr == null || uuidArr.length <= 0) { this.bluetoothAdapter.startLeScan(this); } else { this.bluetoothAdapter.startLeScan(uuidArr, this); } if (i > 0) { new Handler().postDelayed(new Runnable() { /* class com.megster.cordova.ble.central.BLECentralPlugin.AnonymousClass4 */ public void run() { LOG.d(BLECentralPlugin.TAG, "Stopping Scan"); BLECentralPlugin.this.bluetoothAdapter.stopLeScan(BLECentralPlugin.this); } }, (long) (i * 1000)); } PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT); pluginResult.setKeepCallback(true); callbackContext.sendPluginResult(pluginResult); } } private boolean locationServicesEnabled() { int i; try { i = Settings.Secure.getInt(this.f0cordova.getActivity().getContentResolver(), "location_mode"); } catch (Settings.SettingNotFoundException e) { LOG.e(TAG, "Location Mode Setting Not Found", e); i = 0; } if (i > 0) { return true; } return false; } private void listKnownDevices(CallbackContext callbackContext) { JSONArray jSONArray = new JSONArray(); for (Map.Entry<String, Peripheral> entry : this.peripherals.entrySet()) { Peripheral value = entry.getValue(); if (!value.isUnscanned()) { jSONArray.put(value.asJSONObject()); } } callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, jSONArray)); } public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bArr) { String address = bluetoothDevice.getAddress(); if (!(this.peripherals.containsKey(address) && !this.peripherals.get(address).isUnscanned())) { Peripheral peripheral = new Peripheral(bluetoothDevice, i, bArr); this.peripherals.put(bluetoothDevice.getAddress(), peripheral); if (this.discoverCallback != null) { PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, peripheral.asJSONObject()); pluginResult.setKeepCallback(true); this.discoverCallback.sendPluginResult(pluginResult); return; } return; } Peripheral peripheral2 = this.peripherals.get(address); if (peripheral2 != null) { peripheral2.update(i, bArr); if (this.reportDuplicates && this.discoverCallback != null) { PluginResult pluginResult2 = new PluginResult(PluginResult.Status.OK, peripheral2.asJSONObject()); pluginResult2.setKeepCallback(true); this.discoverCallback.sendPluginResult(pluginResult2); } } } @Override // org.apache.cordova.CordovaPlugin public void onActivityResult(int i, int i2, Intent intent) { if (i == 1) { if (i2 == -1) { LOG.d(TAG, "User enabled Bluetooth"); CallbackContext callbackContext = this.enableBluetoothCallback; if (callbackContext != null) { callbackContext.success(); } } else { LOG.d(TAG, "User did *NOT* enable Bluetooth"); CallbackContext callbackContext2 = this.enableBluetoothCallback; if (callbackContext2 != null) { callbackContext2.error("User did not enable Bluetooth"); } } this.enableBluetoothCallback = null; } } @Override // org.apache.cordova.CordovaPlugin public void onRequestPermissionResult(int i, String[] strArr, int[] iArr) { for (int i2 : iArr) { if (i2 == -1) { LOG.d(TAG, "User *rejected* Coarse Location Access"); this.permissionCallback.error("Location permission not granted."); return; } } if (i == 2) { LOG.d(TAG, "User granted Coarse Location Access"); findLowEnergyDevices(this.permissionCallback, this.serviceUUIDs, this.scanSeconds); this.permissionCallback = null; this.serviceUUIDs = null; this.scanSeconds = -1; } } private UUID uuidFromString(String str) { return UUIDHelper.uuidFromString(str); } private void resetScanOptions() { this.reportDuplicates = false; } }