|
@@ -2,31 +2,26 @@ package com.luojigou.xiaodou
|
|
|
|
|
|
import android.annotation.SuppressLint
|
|
|
import android.app.Service
|
|
|
+import android.bluetooth.BluetoothAdapter
|
|
|
+import android.bluetooth.BluetoothDevice
|
|
|
+import android.bluetooth.BluetoothManager
|
|
|
+import android.bluetooth.le.ScanCallback
|
|
|
+import android.bluetooth.le.ScanFilter
|
|
|
+import android.bluetooth.le.ScanResult
|
|
|
+import android.bluetooth.le.ScanSettings
|
|
|
+import android.content.BroadcastReceiver
|
|
|
+import android.content.ComponentName
|
|
|
+import android.content.Context
|
|
|
import android.content.Intent
|
|
|
-import android.os.Binder
|
|
|
+import android.content.IntentFilter
|
|
|
import android.os.IBinder
|
|
|
-import android.util.ArraySet
|
|
|
import android.util.Log
|
|
|
-import androidx.bluetooth.BluetoothDevice
|
|
|
-import androidx.bluetooth.BluetoothLe
|
|
|
-import androidx.bluetooth.ScanFilter
|
|
|
-import androidx.bluetooth.ScanResult
|
|
|
-import com.luojigou.xiaodou.ble.XDBLEConnectStatus
|
|
|
-import com.luojigou.xiaodou.ble.XDBLEUtils
|
|
|
+import androidx.core.app.NotificationChannelCompat
|
|
|
+import androidx.core.app.NotificationCompat
|
|
|
+import androidx.core.app.NotificationManagerCompat
|
|
|
import com.luojigou.xiaodou.ble.XDScanBLEDevice
|
|
|
-import com.luojigou.xiaodou.ble.XDScanBLEStatus
|
|
|
-import kotlinx.coroutines.CoroutineScope
|
|
|
-import kotlinx.coroutines.Dispatchers
|
|
|
-import kotlinx.coroutines.SupervisorJob
|
|
|
-import kotlinx.coroutines.currentCoroutineContext
|
|
|
-import kotlinx.coroutines.flow.Flow
|
|
|
-import kotlinx.coroutines.flow.MutableStateFlow
|
|
|
-import kotlinx.coroutines.flow.launchIn
|
|
|
-import kotlinx.coroutines.flow.onCompletion
|
|
|
-import kotlinx.coroutines.flow.onEach
|
|
|
-import kotlinx.coroutines.flow.onStart
|
|
|
-import kotlinx.coroutines.withContext
|
|
|
import java.util.UUID
|
|
|
+import kotlin.concurrent.thread
|
|
|
|
|
|
|
|
|
@SuppressLint("MissingPermission")
|
|
@@ -35,9 +30,10 @@ class XDScanBLEService : Service() {
|
|
|
const val ACTION_START_SCAN = "com.luojigou.xiaodou.ble.ACTION_START_SCAN"
|
|
|
const val ACTION_STOP_SCAN = "com.luojigou.xiaodou.ble.ACTION_STOP_SCAN"
|
|
|
|
|
|
+ const val ACTION_SCAN_RESULT = "com.luojigou.xiaodou.ble.ACTION_SCAN_RESULT"
|
|
|
+ const val ACTION_SCAN_RESULT_DATA = "com.luojigou.xiaodou.ble.ACTION_SCAN_RESULT_DATA"
|
|
|
+
|
|
|
const val ACTION_CONNECT_WIFI = "com.luojigou.xiaodou.ble.ACTION_CONNECT_WIFI"
|
|
|
- const val ACTION_CONNECT_WIFI_RESULT = "com.luojigou.xiaodou.ble.ACTION_CONNECT_WIFI_RESULT"
|
|
|
- const val ACTION_CONNECT_WIFI_RESULT_DATA = "com.luojigou.xiaodou.ble.ACTION_CONNECT_WIFI_RESULT_DATA"
|
|
|
const val ACTION_CONNECT_WIFI_RESULT_ERROR = "com.luojigou.xiaodou.ble.ACTION_CONNECT_WIFI_RESULT_ERROR"
|
|
|
const val ACTION_CONNECT_WIFI_RESULT_SUCCESS = "com.luojigou.xiaodou.ble.ACTION_CONNECT_WIFI_RESULT_SUCCESS"
|
|
|
const val ACTION_CONNECT_WIFI_RESULT_FAILED = "com.luojigou.xiaodou.ble.ACTION_CONNECT_WIFI_RESULT_FAILED"
|
|
@@ -45,63 +41,172 @@ class XDScanBLEService : Service() {
|
|
|
private val wifiConnectServiceUUID = UUID.fromString("0000ae80-0000-1000-8000-00805f9b34fb")
|
|
|
private val wifiConnectWriteUUID = UUID.fromString("0000ae81-0000-1000-8000-00805f9b34fb")
|
|
|
private val wifiConnectNotifyUUID = UUID.fromString("0000ae82-0000-1000-8000-00805f9b34fb")
|
|
|
- private val filter: List<ScanFilter> = listOf(ScanFilter())
|
|
|
+
|
|
|
+ private val bleChannelName = "BLE-CHANNEL"
|
|
|
}
|
|
|
|
|
|
- private val bluetoothLe: BluetoothLe by lazy { BluetoothLe(this) }
|
|
|
+ private val deviceMap: MutableMap<XDScanBLEDevice, BluetoothDevice> = HashMap()
|
|
|
+
|
|
|
+ private lateinit var bluetoothManager: BluetoothManager
|
|
|
+
|
|
|
+ private lateinit var bluetoothAdapter: BluetoothAdapter
|
|
|
+
|
|
|
+ private lateinit var bluetoothReceiver: BroadcastReceiver
|
|
|
+
|
|
|
+ @Volatile
|
|
|
+ private var bleScanning = false
|
|
|
+
|
|
|
+ private val bleScanSettings = ScanSettings.Builder()
|
|
|
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
|
|
+ .build()
|
|
|
|
|
|
- private val deviceSet: MutableSet<XDScanBLEDevice> = ArraySet()
|
|
|
+ private val bleScanFilters = mutableListOf<ScanFilter>()
|
|
|
|
|
|
- private val deviceListFlow = MutableStateFlow<List<XDScanBLEDevice>>(listOf())
|
|
|
+ private val bleScanCallback = object : ScanCallback() {
|
|
|
+ override fun onScanResult(callbackType: Int, result: ScanResult) {
|
|
|
+ super.onScanResult(callbackType, result)
|
|
|
+ val oldSize = deviceMap.size
|
|
|
+ addDevice(result)
|
|
|
+ val newSize = deviceMap.size
|
|
|
+
|
|
|
+ if (oldSize != newSize) {
|
|
|
+ sendResult()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onBatchScanResults(results: MutableList<ScanResult>) {
|
|
|
+ super.onBatchScanResults(results)
|
|
|
+ val oldSize = deviceMap.size
|
|
|
+ results.forEach { addDevice(it) }
|
|
|
+ val newSize = deviceMap.size
|
|
|
+
|
|
|
+ if (oldSize != newSize) {
|
|
|
+ sendResult()
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onScanFailed(errorCode: Int) {
|
|
|
+ super.onScanFailed(errorCode)
|
|
|
+ Log.d("XDScanBLEService", "onScanFailed: $errorCode")
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun addDevice(result: ScanResult) {
|
|
|
+ result.apply {
|
|
|
+ if (device.name == null) return
|
|
|
+
|
|
|
+ val device = XDScanBLEDevice(
|
|
|
+ name = device.name ?: "unknown",
|
|
|
+ bondState = device.bondState.toString(),
|
|
|
+ rssi = rssi.toString(),
|
|
|
+ address = device.address,
|
|
|
+ addressType = device.type.toString(),
|
|
|
+ )
|
|
|
+
|
|
|
+ if (!deviceMap.contains(device)) {
|
|
|
+ deviceMap[device] = result.device
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- private val connectStatus = MutableStateFlow<XDBLEConnectStatus>(XDBLEConnectStatus.Waiting)
|
|
|
override fun onCreate() {
|
|
|
super.onCreate()
|
|
|
+ goForeground()
|
|
|
+ initBluetooth()
|
|
|
}
|
|
|
|
|
|
- override fun onBind(intent: Intent?): IBinder {
|
|
|
- return XDScanBLEBinder(this)
|
|
|
- }
|
|
|
+ private fun goForeground() {
|
|
|
+ val manager = NotificationManagerCompat.from(this)
|
|
|
+ val notificationChannel = NotificationChannelCompat.Builder(
|
|
|
+ bleChannelName, NotificationManagerCompat.IMPORTANCE_LOW
|
|
|
+ ).setName("蓝牙扫描通知").setDescription("用于蓝牙扫描硬件设备并配网处理").build()
|
|
|
+
|
|
|
+ if (manager.areNotificationsEnabled()) {
|
|
|
+ if (manager.getNotificationChannel(bleChannelName) == null) {
|
|
|
+ manager.createNotificationChannel(notificationChannel)
|
|
|
+ }
|
|
|
|
|
|
- class XDScanBLEBinder(private val service: XDScanBLEService) : Binder(), XDScanBLEStatus.Manager {
|
|
|
- override suspend fun scanBle(): Flow<ScanResult> {
|
|
|
- return service.bluetoothLe.scan(filter)
|
|
|
+ val notification = NotificationCompat.Builder(this, bleChannelName)
|
|
|
+ .setContentTitle("XiaoDou")
|
|
|
+ .setContentText("XiaoDou is running")
|
|
|
+ .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
|
|
|
+ .setChannelId(bleChannelName)
|
|
|
+ .build()
|
|
|
+
|
|
|
+ startForeground(1, notification)
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun initBluetooth() {
|
|
|
+ bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
|
|
+ bluetoothAdapter = bluetoothManager.adapter
|
|
|
|
|
|
- override suspend fun connectWifi(device: BluetoothDevice, ssid: String, password: String): Unit = withContext(Dispatchers.IO) {
|
|
|
- service.connectStatus.emit(XDBLEConnectStatus.Connecting(device))
|
|
|
- service.bluetoothLe.connectGatt(device) {
|
|
|
- val connection = this
|
|
|
-
|
|
|
- val bleService = this.getService(wifiConnectServiceUUID)
|
|
|
- if (bleService != null) {
|
|
|
- service.connectStatus.emit(XDBLEConnectStatus.Connected(device))
|
|
|
-
|
|
|
- val write = bleService.getCharacteristic(wifiConnectWriteUUID)
|
|
|
- val notify = bleService.getCharacteristic(wifiConnectNotifyUUID)
|
|
|
-
|
|
|
- if (notify != null) {
|
|
|
- connection
|
|
|
- .subscribeToCharacteristic(notify)
|
|
|
- .onStart { Log.d("XDScanBLEBinder", "subscribeToCharacteristic: ") }
|
|
|
- .onEach { XDBLEUtils.parseConnectResponse(it) }
|
|
|
- .onCompletion { Log.d("XDScanBLEBinder", "onCompletion: ") }
|
|
|
- .launchIn(CoroutineScope(currentCoroutineContext()))
|
|
|
+ bluetoothReceiver = object : BroadcastReceiver() {
|
|
|
+ override fun onReceive(context: Context?, intent: Intent) {
|
|
|
+ when (intent.action) {
|
|
|
+ ACTION_START_SCAN -> {
|
|
|
+ startScan()
|
|
|
}
|
|
|
|
|
|
- if (write != null) {
|
|
|
- val request = XDBLEUtils.convertToConnectRequest(ssid, password)
|
|
|
- val result = connection.writeCharacteristic(write, request)
|
|
|
- if (result.isSuccess) {
|
|
|
- Log.d("XDScanBLEBinder", "connectWifi: success")
|
|
|
- } else {
|
|
|
- Log.d("XDScanBLEBinder", "connectWifi: failed")
|
|
|
- }
|
|
|
+ ACTION_STOP_SCAN -> {
|
|
|
+ stopScan()
|
|
|
}
|
|
|
- } else {
|
|
|
- service.connectStatus.emit(XDBLEConnectStatus.Failed(device, "蓝牙无服务"))
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ val intentFilter = IntentFilter()
|
|
|
+ intentFilter.addAction(ACTION_START_SCAN)
|
|
|
+ intentFilter.addAction(ACTION_STOP_SCAN)
|
|
|
+ registerReceiver(bluetoothReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
|
|
|
+
|
|
|
+ startScan()
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onBind(intent: Intent?): IBinder? {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun startScan() {
|
|
|
+ if (!bleScanning) {
|
|
|
+ bleScanning = true
|
|
|
+ bluetoothAdapter.bluetoothLeScanner.startScan(
|
|
|
+ bleScanFilters, bleScanSettings, bleScanCallback
|
|
|
+ )
|
|
|
+ }
|
|
|
+ // delayClose()
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun delayClose() {
|
|
|
+ thread {
|
|
|
+ Thread.sleep(5000)
|
|
|
+ if (bleScanning) {
|
|
|
+ stopScan()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun stopScan() {
|
|
|
+ if (bleScanning) {
|
|
|
+ bleScanning = false
|
|
|
+ bluetoothAdapter.bluetoothLeScanner.stopScan(bleScanCallback)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun sendResult() {
|
|
|
+ val intent = Intent(ACTION_SCAN_RESULT)
|
|
|
+ intent.putParcelableArrayListExtra(
|
|
|
+ ACTION_SCAN_RESULT_DATA, ArrayList(deviceMap.keys)
|
|
|
+ )
|
|
|
+ sendBroadcast(intent)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onDestroy() {
|
|
|
+ unregisterReceiver(bluetoothReceiver)
|
|
|
+ stopScan()
|
|
|
+ super.onDestroy()
|
|
|
+ Log.d("XDScanBLEService", "onDestroy: service destroyed")
|
|
|
}
|
|
|
}
|