From c543859108313fe2e0f1187251088d8bc19c5af3 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 21 Oct 2023 20:51:22 +0200 Subject: [PATCH] POC --- .../bluetoothlespam/Services/BleService.kt | 220 +++++++++++++++++- .../bluetoothlespam/ui/home/HomeFragment.kt | 25 +- 2 files changed, 224 insertions(+), 21 deletions(-) diff --git a/BluetoothLESpam/app/src/main/java/de/simonkelmann/bluetoothlespam/Services/BleService.kt b/BluetoothLESpam/app/src/main/java/de/simonkelmann/bluetoothlespam/Services/BleService.kt index b86a64f5..6565f664 100644 --- a/BluetoothLESpam/app/src/main/java/de/simonkelmann/bluetoothlespam/Services/BleService.kt +++ b/BluetoothLESpam/app/src/main/java/de/simonkelmann/bluetoothlespam/Services/BleService.kt @@ -2,34 +2,181 @@ package de.simonkelmann.bluetoothlespam.Services import android.Manifest import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothManager +import android.bluetooth.le.AdvertiseCallback import android.bluetooth.le.AdvertiseData +import android.bluetooth.le.AdvertiseSettings import android.bluetooth.le.AdvertisingSet import android.bluetooth.le.AdvertisingSetCallback import android.bluetooth.le.AdvertisingSetParameters import android.bluetooth.le.BluetoothLeAdvertiser +import android.bluetooth.le.PeriodicAdvertisingParameters +import android.content.Context import android.content.pm.PackageManager import android.os.Build import android.os.ParcelUuid import android.util.Log +import android.widget.Toast import androidx.annotation.RequiresApi import androidx.core.app.ActivityCompat import de.simonkelmann.bluetoothlespam.AppContext.AppContext import de.simonkelmann.bluetoothlespam.PermissionCheck.PermissionCheck +import java.util.Dictionary import java.util.UUID -class BleService { +class BleService (_bluetoothAdapter: BluetoothAdapter){ private val _logTag = "BleService" - private var _currentAdvertisingSet:AdvertisingSet? = null - private var _macAddress = "" + private val _advertiser: BluetoothLeAdvertiser = _bluetoothAdapter.bluetoothLeAdvertiser + + val serviceUuid = ParcelUuid(UUID.fromString("0000fe2c-0000-1000-8000-00805f9b34fb")) + val serviceData = "CD8256".decodeHex()//byteArrayOf("DC".toInt(16).toByte(), 0xE9, 0xEA) + + + /* + + {0xCD8256, "Bose NC 700"}, + {0xF52494, "JBL Buds Pro"}, + {0x718FA4, "JBL Live 300TWS"}, + {0x821F66, "JBL Flip 6"}, + {0x92BBBD, "Pixel Buds"}, + {0xD446A7, "Sony XM5"}, + {0x2D7A23, "Sony WF-1000XM4"}, + {0x0E30C3, "Razer Hammerhead TWS"}, + {0x72EF8D, "Razer Hammerhead TWS X"}, + {0x72FB00, "Soundcore Spirit Pro GVA"}, + + */ + + var manufacturerId = 0x009E// 0xFE21 => 65057 => BOSE + var manufacturerSpecificData:ByteArray = byteArrayOf() //byteArrayOf(0x1e, 0xff.toByte(), 0x4c, 0x00, 0x07, 0x19, 0x07, 0x02, 0x20, 0x75, 0xaa.toByte(), 0x30, 0x01, 0x00, 0x00, 0x45, 0x12, 0x12, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + + init { + if(_bluetoothAdapter == null){ + Log.e(_logTag, "Adapter is null") + } + + if(_advertiser == null){ + Log.e(_logTag, "Advertiser is null") + } + } + + @RequiresApi(Build.VERSION_CODES.O) + fun startAdvertising(){ + + val parameters = AdvertisingSetParameters.Builder() + .setLegacyMode(true) + //.setConnectable(true) + //.setScannable(true) + .setInterval(AdvertisingSetParameters.INTERVAL_HIGH) + .setTxPowerLevel(AdvertisingSetParameters.TX_POWER_HIGH) + //.setPrimaryPhy(BluetoothDevice.PHY_LE_CODED) + //.setSecondaryPhy(BluetoothDevice.PHY_LE_2M) + .build() + + val advertiseData: AdvertiseData = AdvertiseData.Builder() + .setIncludeDeviceName(false) + .addServiceUuid(serviceUuid) + .addServiceData(serviceUuid, serviceData) + //.addManufacturerData(manufacturerId, serviceData) + //.addManufacturerData(manufacturerId, manufacturerSpecificData) + .setIncludeTxPowerLevel(true) + .build() + + val periodicParameters: PeriodicAdvertisingParameters = PeriodicAdvertisingParameters.Builder() + .setInterval(800) + .setIncludeTxPower(true) + .build() + + val scanResponse = AdvertiseData.Builder().addServiceUuid(serviceUuid).build() + + + if(PermissionCheck.checkPermission(Manifest.permission.BLUETOOTH_ADVERTISE, AppContext.getActivity())){ + _advertiser.startAdvertisingSet(parameters, advertiseData, null, null, null, advertiseSetCallback); + Log.d(_logTag, "Executed advertisement Start") + } + } - private val _advertiser: BluetoothLeAdvertiser = BluetoothAdapter.getDefaultAdapter().bluetoothLeAdvertiser + @RequiresApi(Build.VERSION_CODES.O) + fun stopAdvertising(){ + if(PermissionCheck.checkPermission(Manifest.permission.BLUETOOTH_ADVERTISE, AppContext.getActivity())){ + _advertiser.stopAdvertisingSet(advertiseSetCallback) + Log.d(_logTag, "Executed advertisement Start") + } + } + + fun String.decodeHex(): ByteArray { + check(length % 2 == 0) { "Must have an even length" } + + return chunked(2) + .map { it.toInt(16).toByte() } + .toByteArray() + } + + @RequiresApi(Build.VERSION_CODES.O) + private val advertiseSetCallback = object : AdvertisingSetCallback() { + override fun onAdvertisingSetStarted(advertisingSet: AdvertisingSet?, txPower: Int, status: Int) { + val context = AppContext.getActivity() + + if (status==AdvertisingSetCallback.ADVERTISE_FAILED_ALREADY_STARTED) + Toast.makeText(context, "ADVERTISE_FAILED_ALREADY_STARTED", Toast.LENGTH_SHORT).show(); + else if (status==AdvertisingSetCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED) + Toast.makeText(context, "ADVERTISE_FAILED_FEATURE_UNSUPPORTED", Toast.LENGTH_SHORT).show(); + else if (status==AdvertisingSetCallback.ADVERTISE_FAILED_DATA_TOO_LARGE) + Toast.makeText(context, "ADVERTISE_FAILED_DATA_TOO_LARGE", Toast.LENGTH_SHORT).show(); + else if (status==AdvertisingSetCallback.ADVERTISE_FAILED_INTERNAL_ERROR) + Toast.makeText(context, "ADVERTISE_FAILED_INTERNAL_ERROR", Toast.LENGTH_SHORT).show(); + else if (status==AdvertisingSetCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS) + Toast.makeText(context, "ADVERTISE_FAILED_TOO_MANY_ADVERTISERS", Toast.LENGTH_SHORT).show(); + else if (status==AdvertisingSetCallback.ADVERTISE_SUCCESS) + Toast.makeText(context, "ADVERTISE_SUCCESS", Toast.LENGTH_SHORT).show(); + + Log.i(_logTag, "onAdvertisingSetStarted(): txPower:" + txPower + " , status: " + status) + + if(advertisingSet != null){ + if(PermissionCheck.checkPermission(Manifest.permission.BLUETOOTH_ADVERTISE, AppContext.getActivity())){ + //advertisingSet!!.setScanResponseData(AdvertiseData.Builder().addServiceUuid(ParcelUuid(UUID.randomUUID())).build()) + Log.d(_logTag,"added scanresponse data") + } + } else { + Log.d(_logTag,"advertising set is null"); + } + } + override fun onAdvertisingDataSet(advertisingSet: AdvertisingSet, status: Int) { + Log.i(_logTag, "onAdvertisingDataSet() :status:$status") + } + + override fun onScanResponseDataSet(advertisingSet: AdvertisingSet, status: Int) { + Log.i(_logTag, "onScanResponseDataSet(): status:$status") + } + + override fun onAdvertisingSetStopped(advertisingSet: AdvertisingSet) { + Log.i(_logTag, "onAdvertisingSetStopped():") + } + } + + private val advertiseCallback = object : AdvertiseCallback() { + override fun onStartSuccess(settingsInEffect: AdvertiseSettings) { + Log.i(_logTag, "LE Advertise Started.") + } + + override fun onStartFailure(errorCode: Int) { + Log.w(_logTag, "LE Advertise Failed: $errorCode") + } + } + + + /* + private var _currentAdvertisingSet:AdvertisingSet? = null + private var _macAddress = "" private val _callback: AdvertisingSetCallback = @RequiresApi(Build.VERSION_CODES.O) + object : AdvertisingSetCallback() { override fun onAdvertisingSetStarted( - advertisingSet: AdvertisingSet, + advertisingSet: AdvertisingSet?, txPower: Int, status: Int ) { @@ -54,6 +201,22 @@ class BleService { } + init { + if(_bluetoothAdapter == null){ + Log.d(_logTag, "Adapter is null !!!") + } else { + Log.d(_logTag, "Adapter is not null") + } + + if(!_bluetoothAdapter.isMultipleAdvertisementSupported){ + Log.d(_logTag, "isMultipleAdvertisementSupported is false !!!") + } else { + Log.d(_logTag, "isMultipleAdvertisementSupported is true") + } + + } + + // apple = 004C => 76 /* var manufacturerId = 76 @@ -61,9 +224,19 @@ class BleService { 0xff.toByte(), 0x4c, 0x00, 0x07, 0x19, 0x07, 0x02, 0x20, 0x75, 0xaa.toByte(), 0x30, 0x01, 0x00, 0x00, 0x45, 0x12, 0x12, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) */ - var manufacturerId = 0xFE21 - var manufacturerSpecificData:ByteArray = byteArrayOf(0x1e, 0xff.toByte(), 0x4c, 0x00, 0x07, 0x19, 0x07, 0x02, 0x20, 0x75, 0xaa.toByte(), 0x30, 0x01, 0x00, 0x00, 0x45, 0x12, 0x12, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + var manufacturerId = 0xFE21 // 0xFE21 => BOSE + var manufacturerSpecificData:ByteArray = byteArrayOf() //byteArrayOf(0x1e, 0xff.toByte(), 0x4c, 0x00, 0x07, 0x19, 0x07, 0x02, 0x20, 0x75, 0xaa.toByte(), 0x30, 0x01, 0x00, 0x00, 0x45, 0x12, 0x12, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + + val serviceUuid = ParcelUuid(UUID.fromString("0000fe2c-0000-1000-8000-00805f9b34fb")) + val serviceData = "DATA".getBytes() //byteArrayOf() //"DCE9EA".decodeHex()//byteArrayOf("DC".toInt(16).toByte(), 0xE9, 0xEA) + + fun String.decodeHex(): ByteArray { + check(length % 2 == 0) { "Must have an even length" } + return chunked(2) + .map { it.toInt(16).toByte() } + .toByteArray() + } @RequiresApi(Build.VERSION_CODES.O) fun stopAdvertising(){ @@ -80,23 +253,46 @@ class BleService { //val advertiser: BluetoothLeAdvertiser = BluetoothAdapter.getDefaultAdapter().bluetoothLeAdvertiser + /* val parameters = AdvertisingSetParameters.Builder() .setLegacyMode(false) // True by default, but set here as a reminder. .setConnectable(true) //.setScannable(true) .setInterval(AdvertisingSetParameters.INTERVAL_HIGH) .setTxPowerLevel(AdvertisingSetParameters.TX_POWER_HIGH) + .build()*/ + + val parameters = AdvertisingSetParameters.Builder() + .setLegacyMode(true) + .setConnectable(true) + .setScannable(true) + .setInterval(AdvertisingSetParameters.INTERVAL_HIGH) + .setTxPowerLevel(AdvertisingSetParameters.TX_POWER_MEDIUM) + .setPrimaryPhy(BluetoothDevice.PHY_LE_CODED) + .setSecondaryPhy(BluetoothDevice.PHY_LE_2M) .build() + /* val data = AdvertiseData.Builder() - .addServiceUuid(ParcelUuid(UUID.fromString("0000fe2c-0000-1000-8000-00805f9b34fb"))) - .addServiceData(ParcelUuid(UUID.fromString("0000fe2c-0000-1000-8000-00805f9b34fb")), byteArrayOf(0xDC.toByte(), 0xE9.toByte(), 0xEA.toByte())) + .addServiceUuid(serviceUuid) + .addServiceData(serviceUuid, serviceData) .setIncludeDeviceName(true) - .addManufacturerData(manufacturerId, manufacturerSpecificData) + //.addManufacturerData(manufacturerId, manufacturerSpecificData) + .build()*/ + + val advertiseData: AdvertiseData = AdvertiseData.Builder() + .setIncludeDeviceName(true) + .addServiceUuid(serviceUuid) + .addServiceData(serviceUuid, serviceData) + .build() + + val periodicParameters: PeriodicAdvertisingParameters = PeriodicAdvertisingParameters.Builder() + .setInterval(800) .build() + if(PermissionCheck.checkPermission(Manifest.permission.BLUETOOTH_ADVERTISE, AppContext.getActivity())){ - _advertiser.startAdvertisingSet(parameters, data, null, null, null, _callback); + _advertiser.startAdvertisingSet(parameters, advertiseData, null, periodicParameters, null, _callback); } // After onAdvertisingSetStarted callback is called, you can modify the @@ -129,5 +325,5 @@ class BleService { - } + }*/ } \ No newline at end of file diff --git a/BluetoothLESpam/app/src/main/java/de/simonkelmann/bluetoothlespam/ui/home/HomeFragment.kt b/BluetoothLESpam/app/src/main/java/de/simonkelmann/bluetoothlespam/ui/home/HomeFragment.kt index 3a292d59..c8a2c224 100644 --- a/BluetoothLESpam/app/src/main/java/de/simonkelmann/bluetoothlespam/ui/home/HomeFragment.kt +++ b/BluetoothLESpam/app/src/main/java/de/simonkelmann/bluetoothlespam/ui/home/HomeFragment.kt @@ -1,6 +1,9 @@ package de.simonkelmann.bluetoothlespam.ui.home import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothManager +import android.content.Context +import android.os.Build import android.os.Bundle import android.util.Log import android.view.LayoutInflater @@ -8,8 +11,10 @@ import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.TextView +import androidx.annotation.RequiresApi import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import de.simonkelmann.bluetoothlespam.AppContext.AppContext import de.simonkelmann.bluetoothlespam.Services.BleService import de.simonkelmann.bluetoothlespam.databinding.FragmentHomeBinding @@ -17,21 +22,23 @@ class HomeFragment : Fragment() { private var _binding: FragmentHomeBinding? = null private var _viewModel:HomeViewModel? = null - private var bleService: BleService = BleService() + private var _bleService: BleService = BleService(AppContext.getContext().bluetoothAdapter()!!) private val _logTag = "HomeFragment" - // This property is only valid between onCreateView and // onDestroyView. private val binding get() = _binding!! + fun Context.bluetoothAdapter(): BluetoothAdapter? = + (this.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter + + @RequiresApi(Build.VERSION_CODES.O) override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - val homeViewModel = - ViewModelProvider(this).get(HomeViewModel::class.java) + val homeViewModel = ViewModelProvider(this).get(HomeViewModel::class.java) _viewModel = homeViewModel _binding = FragmentHomeBinding.inflate(inflater, container, false) @@ -39,17 +46,17 @@ class HomeFragment : Fragment() { var startBtn: Button = binding.advertiseButton startBtn.setOnClickListener{view -> - Log.d(_logTag, "OnClick"); - var macAddress = BluetoothAdapter.getDefaultAdapter().address + + var adapter = AppContext.getContext().bluetoothAdapter() + val macAddress = adapter!!.address _viewModel!!.setText("Started Advertising on $macAddress") - bleService.advertise() + _bleService.startAdvertising() } var stopBtn: Button = binding.stopAdvertiseButton stopBtn.setOnClickListener{view -> - Log.d(_logTag, "OnClick"); _viewModel!!.setText("Stopped Advertising") - bleService.stopAdvertising() + _bleService.stopAdvertising() } val textView: TextView = binding.textHome