From 30950b9bfe961acc86debe82a4b248e05ce0b9a4 Mon Sep 17 00:00:00 2001 From: TwistedUmbrellaX Date: Mon, 11 Sep 2023 22:51:55 -0400 Subject: [PATCH] Add detection of UUID to Bluetooth scans --- .../tagmo/bluetooth/GattService.kt | 121 +++++++++--------- .../hiddenramblings/tagmo/bluetooth/Nordic.kt | 6 +- .../tagmo/fragment/GattSlotFragment.kt | 34 ++++- app/src/main/res/values/strings.xml | 3 +- 4 files changed, 96 insertions(+), 68 deletions(-) diff --git a/app/src/main/java/com/hiddenramblings/tagmo/bluetooth/GattService.kt b/app/src/main/java/com/hiddenramblings/tagmo/bluetooth/GattService.kt index 5a44ad1c2..96b2320a0 100644 --- a/app/src/main/java/com/hiddenramblings/tagmo/bluetooth/GattService.kt +++ b/app/src/main/java/com/hiddenramblings/tagmo/bluetooth/GattService.kt @@ -46,53 +46,6 @@ import java.nio.charset.Charset import java.util.Objects import java.util.UUID -/** - * Service for managing connection and data communication with a GATT server - * hosted on a given Bluetooth LE device based on core/java/android/bluetooth - * - * Android Bluetooth Low Energy Status Codes - * - * 0 0x00 BLE_HCI_STATUS_CODE_SUCCESS - * 1 0x01 BLE_HCI_STATUS_CODE_UNKNOWN_BTLE_COMMAND - * 2 0x02 BLE_HCI_STATUS_CODE_UNKNOWN_CONNECTION_IDENTIFIER - * 5 0x05 BLE_HCI_AUTHENTICATION_FAILURE - * 6 0x06 BLE_HCI_STATUS_CODE_PIN_OR_KEY_MISSING - * 7 0x07 BLE_HCI_MEMORY_CAPACITY_EXCEEDED - * 8 0x08 BLE_HCI_CONNECTION_TIMEOUT - * 12 0x0C BLE_HCI_STATUS_CODE_COMMAND_DISALLOWED - * 18 0x12 BLE_HCI_STATUS_CODE_INVALID_BTLE_COMMAND_PARAMETERS - * 19 0x13 BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION - * 20 0x14 BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES - * 21 0x15 BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF - * 22 0x16 BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION - * 26 0x1A BLE_HCI_UNSUPPORTED_REMOTE_FEATURE - * 30 0x1E BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS - * 31 0x1F BLE_HCI_STATUS_CODE_UNSPECIFIED_ERROR - * 34 0x22 BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT - * 36 0x24 BLE_HCI_STATUS_CODE_LMP_PDU_NOT_ALLOWED - * 40 0x28 BLE_HCI_INSTANT_PASSED - * 41 0x29 BLE_HCI_PAIRING_WITH_UNIT_KEY_UNSUPPORTED - * 42 0x2A BLE_HCI_DIFFERENT_TRANSACTION_COLLISION - * 58 0x3A BLE_HCI_CONTROLLER_BUSY - * 59 0x3B BLE_HCI_CONN_INTERVAL_UNACCEPTABLE - * 60 0x3C BLE_HCI_DIRECTED_ADVERTISER_TIMEOUT - * 61 0x3D BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE - * 62 0x3E BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED - * 128 0x80 GATT_NO_RESSOURCES - * 129 0x81 GATT_INTERNAL_ERROR - * 130 0x82 GATT_WRONG_STATE - * 131 0x83 GATT_DB_FULL - * 132 0x84 GATT_BUSY - * 133 0x85 GATT_ERROR - * 135 0x87 GATT_ILLEGAL_PARAMETER - * 137 0x89 GATT_AUTH_FAIL - * 138 0x8A GATT_MORE - * 139 0x8B GATT_INVALID_CFG - * 140 0x8C GATT_SERVICE_STARTED - * 141 0x8D GATT_ENCRYPED_NO_MITM - * 142 0x8E GATT_NOT_ENCRYPTED - **/ - @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @SuppressLint("MissingPermission") class GattService : Service() { @@ -703,9 +656,11 @@ class GattService : Service() { throw IllegalAccessException(getString(R.string.fail_bluetooth_adapter)) } val services = supportedGattServices - if (services.isNullOrEmpty()) throw UnsupportedOperationException( - getString(R.string.gatt_write_failed, serviceType.logTag) - ) + if (services.isNullOrEmpty()) { + throw UnsupportedOperationException( + getString(R.string.gatt_services_null, serviceType.logTag) + ) + } for (customService in services) { when (customService.uuid) { Nordic.NUS -> { @@ -724,6 +679,34 @@ class GattService : Service() { setCharacteristicRX() } + fun setOmllboServicesUUID() { + if (mBluetoothAdapter == null || mBluetoothGatt == null) { + throw IllegalAccessException(getString(R.string.fail_bluetooth_adapter)) + } + val services = supportedGattServices + if (services.isNullOrEmpty()) { + throw UnsupportedOperationException( + getString(R.string.gatt_services_null, serviceType.logTag) + ) + } + for (customService in services) { + when (customService.uuid) { + Nordic.NUS -> { + omllboInterface = false + break + } + Nordic.OmllboNUS -> { + omllboInterface = true + break + } + else -> { + continue + } + } + } + setCharacteristicRX() + } + private fun getCharacteristicRX(mCustomService: BluetoothGattService): BluetoothGattCharacteristic { var mReadCharacteristic = mCustomService.getCharacteristic(GattRX) if (mBluetoothGatt?.readCharacteristic(mReadCharacteristic) != true) run breaking@{ @@ -748,9 +731,11 @@ class GattService : Service() { val mCustomService = mBluetoothGatt!!.getService(GattNUS) if (null == mCustomService) { val services = supportedGattServices - if (services.isNullOrEmpty()) throw UnsupportedOperationException( - getString(R.string.gatt_write_failed, serviceType.logTag) - ) + if (services.isNullOrEmpty()) { + throw UnsupportedOperationException( + getString(R.string.gatt_services_null, serviceType.logTag) + ) + } for (service in services) { Debug.verbose(this.javaClass, "GattReadService: ${service.uuid}") mCharacteristicRX = getCharacteristicRX(service) @@ -786,9 +771,11 @@ class GattService : Service() { val mCustomService = mBluetoothGatt!!.getService(GattNUS) if (null == mCustomService) { val services = supportedGattServices - if (services.isNullOrEmpty()) throw UnsupportedOperationException( - getString(R.string.gatt_write_failed, serviceType.logTag) - ) + if (services.isNullOrEmpty()) { + throw UnsupportedOperationException( + getString(R.string.gatt_services_null, serviceType.logTag) + ) + } for (customService in services) { Debug.verbose(this.javaClass, "GattWriteService: ${customService.uuid}") mCharacteristicTX = getCharacteristicTX(customService) @@ -1298,8 +1285,24 @@ class GattService : Service() { companion object { private var legacyInterface = false - val GattNUS: UUID = if (legacyInterface) Nordic.LegacyNUS else Nordic.NUS - val GattTX: UUID = if (legacyInterface) Nordic.LegacyTX else Nordic.TX - val GattRX: UUID = if (legacyInterface) Nordic.LegacyRX else Nordic.RX + private var omllboInterface = false + val GattNUS: UUID = if (legacyInterface) + Nordic.LegacyNUS + else if (omllboInterface) + Nordic.OmllboNUS + else + Nordic.NUS + val GattTX: UUID = if (legacyInterface) + Nordic.LegacyTX + else if (omllboInterface) + Nordic.OmllboTX + else + Nordic.TX + val GattRX: UUID = if (legacyInterface) + Nordic.LegacyRX + else if (omllboInterface) + Nordic.OmllboRX + else + Nordic.RX } } \ No newline at end of file diff --git a/app/src/main/java/com/hiddenramblings/tagmo/bluetooth/Nordic.kt b/app/src/main/java/com/hiddenramblings/tagmo/bluetooth/Nordic.kt index 159e2c3c6..12d73237e 100644 --- a/app/src/main/java/com/hiddenramblings/tagmo/bluetooth/Nordic.kt +++ b/app/src/main/java/com/hiddenramblings/tagmo/bluetooth/Nordic.kt @@ -17,9 +17,9 @@ object Nordic { val LegacyTX = UUID.fromString("78290002-d52e-473f-a9f4-f03da7c67dd1") val LegacyRX = UUID.fromString("78290003-d52e-473f-a9f4-f03da7c67dd1") - val omllboNUS: UUID = UUID.fromString("0000ff10-0000-1000-8000-00805f9b34fb") - val omllboTX: UUID = UUID.fromString("0000ff11-0000-1000-8000-00805f9b34fb") - val omllboRX: UUID = UUID.fromString("0000ff12-0000-1000-8000-00805f9b34fb") + val OmllboNUS: UUID = UUID.fromString("0000ff10-0000-1000-8000-00805f9b34fb") + val OmllboTX: UUID = UUID.fromString("0000ff11-0000-1000-8000-00805f9b34fb") + val OmllboRX: UUID = UUID.fromString("0000ff12-0000-1000-8000-00805f9b34fb") fun UUID.isUUID(uuid: UUID): Boolean { return this.compareTo(uuid) == 0 diff --git a/app/src/main/java/com/hiddenramblings/tagmo/fragment/GattSlotFragment.kt b/app/src/main/java/com/hiddenramblings/tagmo/fragment/GattSlotFragment.kt index d5326b207..317a612fb 100644 --- a/app/src/main/java/com/hiddenramblings/tagmo/fragment/GattSlotFragment.kt +++ b/app/src/main/java/com/hiddenramblings/tagmo/fragment/GattSlotFragment.kt @@ -67,6 +67,8 @@ import org.json.JSONException import org.json.JSONObject import java.io.IOException import java.text.ParseException +import java.util.UUID + @SuppressLint("NewApi") open class GattSlotFragment : Fragment(), GattSlotAdapter.OnAmiiboClickListener, BluetoothListener { @@ -129,6 +131,8 @@ open class GattSlotFragment : Fragment(), GattSlotAdapter.OnAmiiboClickListener, LOCKED, AMIIBO, MENU, WRITE } + val separator = System.getProperty("line.separator") ?: "\n" + private var browserActivity: BrowserActivity? = null private val fragmentHandler = Handler(Looper.getMainLooper()) @@ -641,12 +645,24 @@ open class GattSlotFragment : Fragment(), GattSlotAdapter.OnAmiiboClickListener, @SuppressLint("InflateParams") private fun displayScanResult( - deviceDialog: AlertDialog, device: BluetoothDevice) : View { + deviceDialog: AlertDialog, device: BluetoothDevice, services: List = listOf()) : View { val detectedType = getDeviceType(device) val item = this.layoutInflater.inflate(R.layout.device_bluetooth, null) item.findViewById(R.id.device_name).text = device.name - item.findViewById(R.id.device_address).text = - requireActivity().getString(R.string.device_address, device.address) + if (services.isNotEmpty()) { + val serviceList = StringBuilder() + services.forEachIndexed { index, uuid -> + if (services.lastIndex == index) + serviceList.append(uuid) + else + serviceList.append(separator).append(uuid) + } + item.findViewById(R.id.device_address).text = + requireActivity().getString(R.string.device_services, serviceList.toString()) + } else { + item.findViewById(R.id.device_address).text = + requireActivity().getString(R.string.device_address, device.address) + } item.findViewById(R.id.gatt_type_spinner).apply { onItemSelectedListener = object : OnItemSelectedListener { @@ -692,6 +708,14 @@ open class GattSlotFragment : Fragment(), GattSlotAdapter.OnAmiiboClickListener, } } + private fun getServiceUUIDs(scanResult: ScanResult): List { + val serviceList: MutableList = ArrayList() + scanResult.scanRecord!!.serviceUuids.forEach { + if (!serviceList.contains(it.uuid)) serviceList.add(it.uuid) + } + return serviceList + } + @SuppressLint("MissingPermission", "NewApi") private fun scanBluetoothServices(deviceDialog: AlertDialog) { mBluetoothAdapter = mBluetoothAdapter @@ -716,7 +740,7 @@ open class GattSlotFragment : Fragment(), GattSlotAdapter.OnAmiiboClickListener, if (!devices.contains(result.device)) { devices.add(result.device) deviceDialog.findViewById(R.id.bluetooth_result)?.addView( - displayScanResult(deviceDialog, result.device) + displayScanResult(deviceDialog, result.device, getServiceUUIDs(result)) ) } } @@ -730,7 +754,7 @@ open class GattSlotFragment : Fragment(), GattSlotAdapter.OnAmiiboClickListener, if (!devices.contains(result.device)) { devices.add(result.device) deviceDialog.findViewById(R.id.bluetooth_result)?.addView( - displayScanResult(deviceDialog, result.device) + displayScanResult(deviceDialog, result.device, getServiceUUIDs(result)) ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 087cba9b1..5725783c7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -314,7 +314,7 @@ Shop amiibo hardware No nearby GATT devices found! Not a valid GATT device! - %1$s write characteristic missing! + %1$s services not found! Empty collection can\'t be reduced Connect device Scanning for Bluetooth GATT… @@ -341,6 +341,7 @@ Paired Bluetooth Devices MAC: %1$s + UUID: %1$s Switch / disconnect device Clone with random serial