package com.narutohuo.xindazhou.ble.util import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager import android.bluetooth.le.BluetoothLeScanner import android.bluetooth.le.ScanCallback import android.bluetooth.le.ScanFilter import android.bluetooth.le.ScanResult import android.bluetooth.le.ScanSettings import android.content.Context import android.os.Handler import android.os.Looper import com.narutohuo.xindazhou.core.log.ILog /** * BLE扫描器 * * 负责 BLE 设备的扫描,返回符合过滤条件的 BluetoothDevice 列表 * 使用 Android 12+ 推荐的 BluetoothLeScanner API */ class BleScanner( private val context: Context, private val onDeviceFound: (BluetoothDevice) -> Unit, private val onScanFinished: () -> Unit, private val filterDeviceName: String? = null, private val filterDeviceAddress: String? = null ) { private val tag = "BleScanner" private val bluetoothAdapter: BluetoothAdapter by lazy { val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager manager.adapter } private var scanner: BluetoothLeScanner? = null private var scanning = false private val scanCallback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { val device = result.device if (device != null) { ILog.d(tag, "========== 发现设备 ==========") ILog.d(tag, "设备名称: ${device.name ?: "未知"}") ILog.d(tag, "设备地址: ${device.address}") ILog.d(tag, "RSSI: ${result.rssi}") ILog.d(tag, "扫描类型: $callbackType (1=CALLBACK_TYPE_ALL_MATCHES, 2=BATCH)") ILog.d(tag, "====================================") // 设备过滤(OR逻辑:名称匹配或地址匹配) val nameMatches = filterDeviceName?.let { device.name?.equals(it, ignoreCase = true) == true } ?: false val addressMatches = filterDeviceAddress?.let { device.address.equals(it, ignoreCase = true) } ?: false val shouldConnect = if (filterDeviceName != null || filterDeviceAddress != null) { // 如果设置了过滤条件,则必须匹配(OR逻辑) nameMatches || addressMatches } else { // 如果没有设置过滤条件,则连接所有设备 true } if (shouldConnect) { ILog.d(tag, "✅ 设备匹配过滤条件(名称匹配=$nameMatches, 地址匹配=$addressMatches),准备连接") onDeviceFound(device) stopScan() } else { ILog.d(tag, "⏭️ 设备不匹配过滤条件,跳过") } } } override fun onBatchScanResults(results: List) { ILog.d(tag, "批量扫描结果: ${results.size} 个设备") results.forEach { it.device?.let { device -> ILog.d(tag, "发现设备: ${device.name ?: "未知"}, 地址: ${device.address}, RSSI: ${it.rssi}") onDeviceFound(device) stopScan() } } } override fun onScanFailed(errorCode: Int) { ILog.e(tag, "========== 扫描失败 ==========") ILog.e(tag, "错误码: $errorCode") ILog.e(tag, "1=SCAN_FAILED_ALREADY_STARTED, 2=SCAN_FAILED_APPLICATION_REGISTRATION_FAILED, 3=SCAN_FAILED_INTERNAL_ERROR, 4=SCAN_FAILED_FEATURE_UNSUPPORTED, 5=SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES") ILog.e(tag, "====================================") stopScan() } } /** * 开始扫描(默认 10 秒后自动停止) */ @android.annotation.SuppressLint("MissingPermission") fun startScan(filterNamePrefix: String? = null, timeoutMs: Long = 10_000L) { ILog.d(tag, "========== 开始扫描 ==========") ILog.d(tag, "过滤前缀: ${filterNamePrefix ?: "无"}") ILog.d(tag, "超时时间: ${timeoutMs}ms") ILog.d(tag, "蓝牙适配器状态: ${if (bluetoothAdapter.isEnabled) "已开启" else "已关闭"}") ILog.d(tag, "====================================") if (scanning) { ILog.w(tag, "⚠️ 扫描已在进行中,忽略重复请求") return } if (!bluetoothAdapter.isEnabled) { ILog.e(tag, "❌ 蓝牙未开启,无法扫描") return } scanner = bluetoothAdapter.bluetoothLeScanner if (scanner == null) { ILog.e(tag, "❌ BluetoothLeScanner 为 null") return } val settings = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .build() val filters = mutableListOf() filterNamePrefix?.let { filters.add(ScanFilter.Builder().setDeviceName(it).build()) ILog.d(tag, "已设置设备名称过滤: $it") } try { scanner?.startScan(filters, settings, scanCallback) scanning = true ILog.d(tag, "✅ 扫描已启动,等待设备发现...") // 超时自动停止 Handler(Looper.getMainLooper()).postDelayed({ if (scanning) { ILog.w(tag, "⏰ 扫描超时 (${timeoutMs}ms),自动停止") stopScan() } }, timeoutMs) } catch (e: SecurityException) { ILog.e(tag, "❌ 扫描权限不足", e) scanning = false } catch (e: Exception) { ILog.e(tag, "❌ 启动扫描失败", e) scanning = false } } /** * 停止扫描 */ @android.annotation.SuppressLint("MissingPermission") fun stopScan() { if (!scanning) { ILog.d(tag, "stopScan() 被调用,但未在扫描中") return } ILog.d(tag, "停止扫描...") try { scanner?.stopScan(scanCallback) scanning = false ILog.d(tag, "停止扫描") onScanFinished() } catch (e: SecurityException) { ILog.e(tag, "停止扫描权限不足", e) } catch (e: Exception) { ILog.e(tag, "停止扫描失败", e) } } }