11 changed files with 1444 additions and 1408 deletions
@ -0,0 +1,242 @@ |
|||||
|
package com.example.deep_voice |
||||
|
|
||||
|
import android.bluetooth.BluetoothAdapter |
||||
|
import android.bluetooth.BluetoothProfile |
||||
|
import android.content.Context |
||||
|
import android.content.pm.PackageManager |
||||
|
import android.os.Build |
||||
|
import android.util.Log |
||||
|
import android.Manifest |
||||
|
import androidx.core.content.ContextCompat |
||||
|
import java.util.concurrent.atomic.AtomicBoolean |
||||
|
import java.util.concurrent.CountDownLatch |
||||
|
import java.util.concurrent.TimeUnit |
||||
|
|
||||
|
/** |
||||
|
* 经典蓝牙辅助类,用于获取已连接的蓝牙设备信息 |
||||
|
*/ |
||||
|
class ClassicBluetoothHelper(private val context: Context) { |
||||
|
|
||||
|
private val TAG = "ClassicBluetoothHelper" |
||||
|
|
||||
|
// 缓存蓝牙代理对象 |
||||
|
private var a2dpProxy: BluetoothProfile? = null |
||||
|
private var headsetProxy: BluetoothProfile? = null |
||||
|
|
||||
|
/** |
||||
|
* 获取已连接的A2DP设备(音频输出设备) |
||||
|
* @param callback 回调接口,返回设备列表或错误信息 |
||||
|
*/ |
||||
|
fun getConnectedA2dpDevices(callback: BluetoothDevicesCallback) { |
||||
|
getConnectedBluetoothDevices(BluetoothProfile.A2DP, callback) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取已连接的蓝牙耳机设备 |
||||
|
* @param callback 回调接口,返回设备列表或错误信息 |
||||
|
*/ |
||||
|
fun getConnectedHeadsetDevices(callback: BluetoothDevicesCallback) { |
||||
|
getConnectedBluetoothDevices(BluetoothProfile.HEADSET, callback) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查是否有蓝牙连接权限 |
||||
|
* @return 是否有权限 |
||||
|
*/ |
||||
|
private fun hasBluetoothConnectPermission(): Boolean { |
||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
||||
|
ContextCompat.checkSelfPermission( |
||||
|
context, |
||||
|
Manifest.permission.BLUETOOTH_CONNECT |
||||
|
) == PackageManager.PERMISSION_GRANTED |
||||
|
} else { |
||||
|
true |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查蓝牙是否启用 |
||||
|
* @return 蓝牙是否启用 |
||||
|
*/ |
||||
|
fun isBluetoothEnabled(): Boolean { |
||||
|
try { |
||||
|
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() |
||||
|
if (bluetoothAdapter == null) { |
||||
|
Log.w(TAG, "设备不支持蓝牙") |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
return bluetoothAdapter.isEnabled |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "检查蓝牙状态失败: ${e.message}") |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取指定配置文件的已连接蓝牙设备 |
||||
|
* @param profile 蓝牙配置文件类型,如 BluetoothProfile.A2DP 或 BluetoothProfile.HEADSET |
||||
|
* @param callback 回调接口,返回设备列表或错误信息 |
||||
|
*/ |
||||
|
private fun getConnectedBluetoothDevices(profile: Int, callback: BluetoothDevicesCallback) { |
||||
|
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() |
||||
|
if (bluetoothAdapter == null) { |
||||
|
Log.e(TAG, "没有找到蓝牙适配器") |
||||
|
callback.onError("NO_ADAPTER", "没有找到蓝牙适配器") |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 检查蓝牙权限 |
||||
|
if (!hasBluetoothConnectPermission()) { |
||||
|
Log.e(TAG, "缺少BLUETOOTH_CONNECT权限") |
||||
|
callback.onError("PERMISSION_DENIED", "缺少蓝牙连接权限,请在应用设置中授予该权限") |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 检查是否已经有缓存的代理对象 |
||||
|
val cachedProxy = when (profile) { |
||||
|
BluetoothProfile.A2DP -> a2dpProxy |
||||
|
BluetoothProfile.HEADSET -> headsetProxy |
||||
|
else -> null |
||||
|
} |
||||
|
|
||||
|
if (cachedProxy != null) { |
||||
|
// 使用已缓存的代理对象 |
||||
|
processDeviceList(cachedProxy, profile, callback) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 使用原子布尔值确保回调只被调用一次 |
||||
|
val callbackCalled = AtomicBoolean(false) |
||||
|
|
||||
|
// 使用CountDownLatch等待服务连接 |
||||
|
val latch = CountDownLatch(1) |
||||
|
|
||||
|
// 通过 getProfileProxy 获取指定 Profile (A2DP/HEADSET) 的连接状态 |
||||
|
bluetoothAdapter.getProfileProxy(context, |
||||
|
object : BluetoothProfile.ServiceListener { |
||||
|
override fun onServiceConnected(profileId: Int, proxy: BluetoothProfile) { |
||||
|
Log.d(TAG, "蓝牙配置文件服务已连接: $profileId") |
||||
|
if (profileId == profile) { |
||||
|
// 缓存代理对象 |
||||
|
when (profileId) { |
||||
|
BluetoothProfile.A2DP -> a2dpProxy = proxy |
||||
|
BluetoothProfile.HEADSET -> headsetProxy = proxy |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
processDeviceList(proxy, profileId, callback) |
||||
|
latch.countDown() |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "处理设备信息时出错: ${e.message}") |
||||
|
|
||||
|
// 确保回调只被调用一次 |
||||
|
if (callbackCalled.compareAndSet(false, true)) { |
||||
|
callback.onError("DEVICE_ERROR", "处理设备信息时出错: ${e.message}") |
||||
|
} |
||||
|
latch.countDown() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
override fun onServiceDisconnected(profileId: Int) { |
||||
|
// Profile 断开连接 |
||||
|
Log.e(TAG, "蓝牙配置文件服务断开连接: $profileId") |
||||
|
|
||||
|
// 清除对应的缓存代理 |
||||
|
when (profileId) { |
||||
|
BluetoothProfile.A2DP -> a2dpProxy = null |
||||
|
BluetoothProfile.HEADSET -> headsetProxy = null |
||||
|
} |
||||
|
|
||||
|
// 确保回调只被调用一次,但不在这里调用错误回调 |
||||
|
// 避免服务断开时也返回错误导致重复回调 |
||||
|
if (callbackCalled.compareAndSet(false, true) && !latch.await(0, TimeUnit.MILLISECONDS)) { |
||||
|
callback.onError("SERVICE_DISCONNECTED", "蓝牙配置文件服务断开连接") |
||||
|
latch.countDown() |
||||
|
} |
||||
|
} |
||||
|
}, profile |
||||
|
) |
||||
|
|
||||
|
// 设置超时处理 |
||||
|
Thread { |
||||
|
try { |
||||
|
// 最多等待3秒 |
||||
|
if (!latch.await(3, TimeUnit.SECONDS)) { |
||||
|
Log.e(TAG, "获取蓝牙设备超时") |
||||
|
if (callbackCalled.compareAndSet(false, true)) { |
||||
|
callback.onError("TIMEOUT", "获取蓝牙设备超时") |
||||
|
} |
||||
|
} |
||||
|
} catch (e: InterruptedException) { |
||||
|
Log.e(TAG, "等待蓝牙服务被中断: ${e.message}") |
||||
|
if (callbackCalled.compareAndSet(false, true)) { |
||||
|
callback.onError("INTERRUPTED", "等待蓝牙服务被中断: ${e.message}") |
||||
|
} |
||||
|
} |
||||
|
}.start() |
||||
|
|
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "获取蓝牙设备失败: ${e.message}") |
||||
|
callback.onError("BLUETOOTH_ERROR", "获取蓝牙设备失败: ${e.message}") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理设备列表 |
||||
|
*/ |
||||
|
private fun processDeviceList(proxy: BluetoothProfile, profileId: Int, callback: BluetoothDevicesCallback) { |
||||
|
// 获取已连接设备 |
||||
|
val connectedDevices = proxy.connectedDevices |
||||
|
|
||||
|
// 转换为设备信息列表 |
||||
|
val devicesList = connectedDevices.map { device -> |
||||
|
mapOf( |
||||
|
"name" to (device.name ?: "未知设备"), |
||||
|
"address" to device.address |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
Log.d(TAG, "获取到的设备列表:${devicesList.size}个设备") |
||||
|
|
||||
|
// 返回设备列表(不关闭代理) |
||||
|
callback.onSuccess(devicesList) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 释放资源 |
||||
|
*/ |
||||
|
fun dispose() { |
||||
|
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() ?: return |
||||
|
|
||||
|
// 释放A2DP代理 |
||||
|
a2dpProxy?.let { |
||||
|
try { |
||||
|
bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, it) |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "关闭A2DP代理失败: ${e.message}") |
||||
|
} |
||||
|
a2dpProxy = null |
||||
|
} |
||||
|
|
||||
|
// 释放HEADSET代理 |
||||
|
headsetProxy?.let { |
||||
|
try { |
||||
|
bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, it) |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "关闭HEADSET代理失败: ${e.message}") |
||||
|
} |
||||
|
headsetProxy = null |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 蓝牙设备回调接口 |
||||
|
*/ |
||||
|
interface BluetoothDevicesCallback { |
||||
|
fun onSuccess(devices: List<Map<String, String>>) |
||||
|
fun onError(code: String, message: String) |
||||
|
} |
||||
|
} |
||||
@ -1,436 +0,0 @@ |
|||||
import 'dart:async'; |
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart'; |
|
||||
import 'package:get/get.dart'; |
|
||||
import '../../core/utils/logger.dart'; |
|
||||
import 'dart:io' show Platform; |
|
||||
import 'package:permission_handler/permission_handler.dart'; |
|
||||
|
|
||||
/// 设备类型枚举 |
|
||||
enum DeviceType { |
|
||||
unknown, // 未知设备类型 |
|
||||
classicAudio, // 经典蓝牙音频设备(耳机等) |
|
||||
ble, // BLE设备 |
|
||||
dual // 双模设备(同时支持经典蓝牙和BLE) |
|
||||
} |
|
||||
|
|
||||
/// 简化的蓝牙设备服务,仅用于获取已连接或配对的蓝牙耳机信息 |
|
||||
class BluetoothService extends GetxService { |
|
||||
static BluetoothService get to => Get.find<BluetoothService>(); |
|
||||
|
|
||||
// 内部实例 |
|
||||
BluetoothService._internal(); |
|
||||
static final BluetoothService _instance = BluetoothService._internal(); |
|
||||
factory BluetoothService() => _instance; |
|
||||
|
|
||||
// ====== 状态管理 ====== |
|
||||
// 蓝牙适配器状态 |
|
||||
final _isAvailable = false.obs; |
|
||||
final _isOn = false.obs; |
|
||||
final _isInitialized = false.obs; |
|
||||
|
|
||||
// 权限状态 |
|
||||
final _permissionsGranted = false.obs; |
|
||||
|
|
||||
// 设备管理 |
|
||||
final _connectedDevices = <BluetoothDevice>[].obs; |
|
||||
final _systemPairedDevices = <BluetoothDevice>[].obs; |
|
||||
final _selectedDevice = Rx<BluetoothDevice?>(null); |
|
||||
final _deviceType = Rx<DeviceType>(DeviceType.unknown); |
|
||||
final _connectionState = Rx<BluetoothConnectionState>(BluetoothConnectionState.disconnected); |
|
||||
final _lastConnectedDeviceInfo = Rx<Map<String, dynamic>>({}); |
|
||||
|
|
||||
// 流订阅 |
|
||||
StreamSubscription? _stateSubscription; |
|
||||
|
|
||||
// 防止重复刷新的标志 |
|
||||
bool _isRefreshingDevices = false; |
|
||||
|
|
||||
// ====== Getters ====== |
|
||||
// 基本getter |
|
||||
bool get isAvailable => _isAvailable.value; |
|
||||
bool get isOn => _isOn.value; |
|
||||
bool get isInitialized => _isInitialized.value; |
|
||||
bool get permissionsGranted => _permissionsGranted.value; |
|
||||
|
|
||||
// 设备相关getter |
|
||||
List<BluetoothDevice> get connectedDevices => _connectedDevices; |
|
||||
List<BluetoothDevice> get systemPairedDevices => _systemPairedDevices; |
|
||||
BluetoothDevice? get selectedDevice => _selectedDevice.value; |
|
||||
DeviceType get deviceType => _deviceType.value; |
|
||||
BluetoothConnectionState get connectionState => _connectionState.value; |
|
||||
Map<String, dynamic> get lastConnectedDeviceInfo => _lastConnectedDeviceInfo.value; |
|
||||
|
|
||||
// Observable getters |
|
||||
RxBool get isAvailableObs => _isAvailable; |
|
||||
RxBool get isOnObs => _isOn; |
|
||||
RxBool get isInitializedObs => _isInitialized; |
|
||||
RxBool get permissionsGrantedObs => _permissionsGranted; |
|
||||
RxList<BluetoothDevice> get connectedDevicesObs => _connectedDevices; |
|
||||
Rx<BluetoothDevice?> get selectedDeviceObs => _selectedDevice; |
|
||||
Rx<DeviceType> get deviceTypeObs => _deviceType; |
|
||||
Rx<BluetoothConnectionState> get connectionStateObs => _connectionState; |
|
||||
Rx<Map<String, dynamic>> get lastConnectedDeviceInfoObs => _lastConnectedDeviceInfo; |
|
||||
|
|
||||
/// 初始化蓝牙服务 |
|
||||
Future<BluetoothService> init() async { |
|
||||
if (_isInitialized.value) { |
|
||||
Logger.info('蓝牙服务已初始化,无需重复初始化'); |
|
||||
return this; |
|
||||
} |
|
||||
|
|
||||
Logger.info('开始初始化蓝牙服务'); |
|
||||
|
|
||||
try { |
|
||||
// 1. 设置蓝牙状态监听 |
|
||||
_setupBluetoothListeners(); |
|
||||
|
|
||||
// 2. 初始检查蓝牙状态 |
|
||||
_checkBluetoothState(); |
|
||||
|
|
||||
// 3. 请求权限 |
|
||||
await requestPermissions(); |
|
||||
|
|
||||
// 4. 刷新系统设备 |
|
||||
if (_isOn.value && _permissionsGranted.value) { |
|
||||
await _refreshSystemDevices(); |
|
||||
} |
|
||||
|
|
||||
// 5. 标记初始化完成 |
|
||||
_isInitialized.value = true; |
|
||||
Logger.info('蓝牙服务初始化完成'); |
|
||||
return this; |
|
||||
} catch (e) { |
|
||||
Logger.error('初始化蓝牙服务失败: $e'); |
|
||||
return this; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// 设置蓝牙状态监听 |
|
||||
void _setupBluetoothListeners() { |
|
||||
// 取消旧订阅(如果有) |
|
||||
_stateSubscription?.cancel(); |
|
||||
|
|
||||
// 监听蓝牙适配器状态变化 |
|
||||
_stateSubscription = FlutterBluePlus.adapterState.listen((state) { |
|
||||
_isOn.value = state == BluetoothAdapterState.on; |
|
||||
_isAvailable.value = state != BluetoothAdapterState.unavailable; |
|
||||
|
|
||||
if (state == BluetoothAdapterState.on) { |
|
||||
Logger.info('蓝牙已开启'); |
|
||||
if (_permissionsGranted.value) { |
|
||||
_refreshSystemDevices(); |
|
||||
} |
|
||||
} else if (state == BluetoothAdapterState.off) { |
|
||||
Logger.info('蓝牙已关闭'); |
|
||||
_resetDeviceState(); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
/// 初始检查蓝牙状态 |
|
||||
Future<void> _checkBluetoothState() async { |
|
||||
try { |
|
||||
bool isOn = await FlutterBluePlus.isOn |
|
||||
.timeout(const Duration(seconds: 5), onTimeout: () => false); |
|
||||
|
|
||||
_isOn.value = isOn; |
|
||||
_isAvailable.value = isOn; |
|
||||
Logger.info('当前蓝牙是否已开启: $isOn'); |
|
||||
} catch (e) { |
|
||||
Logger.error('检查蓝牙状态失败: $e'); |
|
||||
_isOn.value = false; |
|
||||
_isAvailable.value = false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// 重置设备状态 |
|
||||
void _resetDeviceState() { |
|
||||
_connectedDevices.clear(); |
|
||||
_systemPairedDevices.clear(); |
|
||||
_selectedDevice.value = null; |
|
||||
_lastConnectedDeviceInfo.value = {}; |
|
||||
_deviceType.value = DeviceType.unknown; |
|
||||
} |
|
||||
|
|
||||
/// 检查并请求蓝牙权限 |
|
||||
Future<bool> requestPermissions() async { |
|
||||
try { |
|
||||
// iOS无需额外权限 |
|
||||
if (!Platform.isAndroid) { |
|
||||
_permissionsGranted.value = true; |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
// 请求所有必要权限 |
|
||||
final permissions = <Permission>[ |
|
||||
// Permission.bluetoothScan, |
|
||||
// Permission.bluetoothConnect, |
|
||||
// Permission.location, // Android需要定位权限来扫描BLE设备 |
|
||||
Permission.microphone, |
|
||||
]; |
|
||||
|
|
||||
// 一次性请求所有权限 |
|
||||
Map<Permission, PermissionStatus> statuses = await permissions.request(); |
|
||||
|
|
||||
// 检查所有权限是否已授予 |
|
||||
bool allGranted = statuses.values.every((status) => status.isGranted); |
|
||||
_permissionsGranted.value = allGranted; |
|
||||
|
|
||||
if (!allGranted) { |
|
||||
// 检查哪些权限被拒绝 |
|
||||
List<String> deniedPermissions = []; |
|
||||
statuses.forEach((permission, status) { |
|
||||
if (!status.isGranted) { |
|
||||
deniedPermissions.add(permission.toString().split('.').last); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
Logger.warning('权限被拒绝: ${deniedPermissions.join(", ")}'); |
|
||||
} |
|
||||
|
|
||||
return allGranted; |
|
||||
} catch (e) { |
|
||||
Logger.error('请求权限失败: $e'); |
|
||||
_permissionsGranted.value = false; |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// 刷新系统蓝牙设备(已连接和已配对) |
|
||||
Future<void> _refreshSystemDevices() async { |
|
||||
if (!_permissionsGranted.value || !_isOn.value) { |
|
||||
Logger.warning('无法刷新系统设备:蓝牙未开启或缺少权限'); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
// 防止重复刷新 |
|
||||
if (_isRefreshingDevices) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
_isRefreshingDevices = true; |
|
||||
|
|
||||
try { |
|
||||
// 获取已连接的系统设备 |
|
||||
List<BluetoothDevice> devices = await FlutterBluePlus.connectedSystemDevices |
|
||||
.timeout(const Duration(seconds: 10), onTimeout: () => <BluetoothDevice>[]); |
|
||||
|
|
||||
_connectedDevices.value = devices; |
|
||||
Logger.info('获取到${devices.length}个系统已连接设备'); |
|
||||
|
|
||||
// 更新选中设备的连接状态(如果有) |
|
||||
if (_selectedDevice.value != null) { |
|
||||
bool stillConnected = _connectedDevices.any( |
|
||||
(d) => d.remoteId.str == _selectedDevice.value!.remoteId.str |
|
||||
); |
|
||||
|
|
||||
_connectionState.value = stillConnected |
|
||||
? BluetoothConnectionState.connected |
|
||||
: BluetoothConnectionState.disconnected; |
|
||||
|
|
||||
if (stillConnected) { |
|
||||
_updateLastConnectedDeviceInfo(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 获取系统已配对设备 |
|
||||
List<BluetoothDevice> pairedDevices = await FlutterBluePlus.bondedDevices |
|
||||
.timeout(const Duration(seconds: 10), onTimeout: () => <BluetoothDevice>[]); |
|
||||
|
|
||||
_systemPairedDevices.value = pairedDevices; |
|
||||
Logger.info('获取到${pairedDevices.length}个系统已配对设备'); |
|
||||
} catch (e) { |
|
||||
Logger.error('刷新系统设备失败: $e'); |
|
||||
} finally { |
|
||||
_isRefreshingDevices = false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// 查找音频设备 |
|
||||
BluetoothDevice? _findAudioDevice(List<BluetoothDevice> devices) { |
|
||||
if (devices.isEmpty) return null; |
|
||||
|
|
||||
// 音频设备的可能名称关键词 |
|
||||
final List<String> audioKeywords = [ |
|
||||
'headphone', 'earphone', 'earbuds', 'headset', |
|
||||
'speaker', 'audio', '耳机', '音箱', '音响' |
|
||||
]; |
|
||||
|
|
||||
// 尝试匹配音频设备名称 |
|
||||
for (BluetoothDevice device in devices) { |
|
||||
String name = device.platformName.toLowerCase(); |
|
||||
if (audioKeywords.any((keyword) => name.contains(keyword))) { |
|
||||
return device; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
/// 确定设备类型 |
|
||||
void _determineDeviceType(BluetoothDevice device) { |
|
||||
final String deviceName = device.platformName.toLowerCase(); |
|
||||
|
|
||||
// 音频设备关键词 |
|
||||
final List<String> audioKeywords = [ |
|
||||
'headphone', 'earphone', 'earbuds', 'headset', |
|
||||
'speaker', 'audio', '耳机', '音箱', '音响' |
|
||||
]; |
|
||||
|
|
||||
bool hasAudioKeyword = audioKeywords.any((keyword) => deviceName.contains(keyword)); |
|
||||
bool inConnectedList = _connectedDevices.any((d) => d.remoteId.str == device.remoteId.str); |
|
||||
|
|
||||
if (hasAudioKeyword || inConnectedList) { |
|
||||
_deviceType.value = DeviceType.classicAudio; |
|
||||
} else { |
|
||||
_deviceType.value = DeviceType.ble; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// 获取已连接设备的信息 |
|
||||
Map<String, dynamic> getConnectedDeviceInfo() { |
|
||||
if (_selectedDevice.value == null) { |
|
||||
return { |
|
||||
'connected': false, |
|
||||
'device': null, |
|
||||
'deviceType': _deviceType.value.toString().split('.').last |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
// 判断设备是否在已连接列表中 |
|
||||
bool isConnected = _connectedDevices.any( |
|
||||
(d) => d.remoteId.str == _selectedDevice.value!.remoteId.str |
|
||||
); |
|
||||
|
|
||||
// 判断设备是否在已配对列表中 |
|
||||
bool isPaired = _systemPairedDevices.any( |
|
||||
(d) => d.remoteId.str == _selectedDevice.value!.remoteId.str |
|
||||
); |
|
||||
|
|
||||
final deviceInfo = { |
|
||||
'connected': isConnected, |
|
||||
'paired': isPaired, |
|
||||
'device': { |
|
||||
'name': _selectedDevice.value!.platformName.isEmpty ? |
|
||||
'未命名设备' : _selectedDevice.value!.platformName, |
|
||||
'id': _selectedDevice.value!.remoteId.str, |
|
||||
'isConnected': isConnected, |
|
||||
'isPaired': isPaired, |
|
||||
}, |
|
||||
'deviceType': _deviceType.value.toString().split('.').last |
|
||||
}; |
|
||||
|
|
||||
return deviceInfo; |
|
||||
} |
|
||||
|
|
||||
/// 获取已配对设备的信息 |
|
||||
Map<String, dynamic> getPairedDeviceInfo(BluetoothDevice device) { |
|
||||
if (device == null) { |
|
||||
return { |
|
||||
'connected': false, |
|
||||
'paired': false, |
|
||||
'device': null, |
|
||||
'deviceType': DeviceType.unknown.toString().split('.').last |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
// 判断设备是否在已连接列表中 |
|
||||
bool isConnected = _connectedDevices.any( |
|
||||
(d) => d.remoteId.str == device.remoteId.str |
|
||||
); |
|
||||
|
|
||||
// 确定设备类型 |
|
||||
DeviceType type = DeviceType.unknown; |
|
||||
final String deviceName = device.platformName.toLowerCase(); |
|
||||
|
|
||||
// 音频设备关键词 |
|
||||
final List<String> audioKeywords = [ |
|
||||
'headphone', 'earphone', 'earbuds', 'headset', |
|
||||
'speaker', 'audio', '耳机', '音箱', '音响' |
|
||||
]; |
|
||||
|
|
||||
bool hasAudioKeyword = audioKeywords.any((keyword) => deviceName.contains(keyword)); |
|
||||
if (hasAudioKeyword || isConnected) { |
|
||||
type = DeviceType.classicAudio; |
|
||||
} else { |
|
||||
type = DeviceType.ble; |
|
||||
} |
|
||||
|
|
||||
final deviceInfo = { |
|
||||
'connected': isConnected, |
|
||||
'paired': true, |
|
||||
'device': { |
|
||||
'name': device.platformName.isEmpty ? |
|
||||
'未命名设备' : device.platformName, |
|
||||
'id': device.remoteId.str, |
|
||||
'isConnected': isConnected, |
|
||||
'isPaired': true, |
|
||||
}, |
|
||||
'deviceType': type.toString().split('.').last |
|
||||
}; |
|
||||
|
|
||||
return deviceInfo; |
|
||||
} |
|
||||
|
|
||||
/// 设置选中的设备(不进行连接操作) |
|
||||
void setSelectedDevice(BluetoothDevice device) { |
|
||||
if (device != null) { |
|
||||
_selectedDevice.value = device; |
|
||||
_determineDeviceType(device); |
|
||||
_updateLastConnectedDeviceInfo(); |
|
||||
Logger.info('已设置选中设备: ${device.platformName}'); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// 更新最后连接的设备信息 |
|
||||
void _updateLastConnectedDeviceInfo() { |
|
||||
if (_selectedDevice.value != null) { |
|
||||
_lastConnectedDeviceInfo.value = getConnectedDeviceInfo(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// 查找系统已连接设备 |
|
||||
Future<BluetoothDevice?> findConnectedSystemDevice() async { |
|
||||
if (!_permissionsGranted.value || !_isOn.value) { |
|
||||
Logger.warning('蓝牙未开启或缺少权限,无法查找系统连接设备'); |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
try { |
|
||||
// 刷新系统设备列表(避免重复刷新) |
|
||||
if (!_isRefreshingDevices) { |
|
||||
await _refreshSystemDevices(); |
|
||||
} |
|
||||
|
|
||||
// 如果没有已连接设备,直接返回null |
|
||||
if (_connectedDevices.isEmpty) { |
|
||||
Logger.info('未找到已连接的蓝牙设备'); |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
// 优先找出可能的音频设备 |
|
||||
BluetoothDevice? audioDevice = _findAudioDevice(_connectedDevices); |
|
||||
BluetoothDevice device = audioDevice ?? _connectedDevices.first; |
|
||||
|
|
||||
// 自动选择设备 |
|
||||
_selectedDevice.value = device; |
|
||||
_determineDeviceType(device); |
|
||||
_connectionState.value = BluetoothConnectionState.connected; |
|
||||
_updateLastConnectedDeviceInfo(); |
|
||||
|
|
||||
Logger.info('找到已连接的蓝牙设备: ${device.platformName}'); |
|
||||
return device; |
|
||||
} catch (e) { |
|
||||
Logger.error('查找系统已连接设备失败: $e'); |
|
||||
return null; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// 清理资源 |
|
||||
@override |
|
||||
void onClose() { |
|
||||
// 取消所有订阅 |
|
||||
_stateSubscription?.cancel(); |
|
||||
super.onClose(); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,452 @@ |
|||||
|
import 'dart:async'; |
||||
|
import 'package:flutter/services.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:permission_handler/permission_handler.dart'; |
||||
|
import 'package:app_settings/app_settings.dart'; |
||||
|
import '../../core/utils/logger.dart'; |
||||
|
|
||||
|
/// 蓝牙设备信息 |
||||
|
class BluetoothDevice { |
||||
|
final String name; |
||||
|
final String address; |
||||
|
final String type; |
||||
|
|
||||
|
BluetoothDevice({ |
||||
|
required this.name, |
||||
|
required this.address, |
||||
|
required this.type |
||||
|
}); |
||||
|
|
||||
|
@override |
||||
|
String toString() => 'BluetoothDevice(name: $name, address: $address, type: $type)'; |
||||
|
} |
||||
|
|
||||
|
/// 蓝牙事件类型 |
||||
|
enum BluetoothEventType { |
||||
|
/// 设备已连接 |
||||
|
deviceConnected, |
||||
|
|
||||
|
/// 设备已断开 |
||||
|
deviceDisconnected, |
||||
|
|
||||
|
/// 蓝牙状态改变(开启/关闭) |
||||
|
bluetoothStateChanged |
||||
|
} |
||||
|
|
||||
|
/// 蓝牙连接状态事件 |
||||
|
class BluetoothConnectionEvent { |
||||
|
/// 事件类型 |
||||
|
final BluetoothEventType type; |
||||
|
|
||||
|
/// 相关设备(可能为空,比如蓝牙状态改变事件) |
||||
|
final BluetoothDevice? device; |
||||
|
|
||||
|
/// 蓝牙状态(仅适用于bluetoothStateChanged事件) |
||||
|
final String? state; |
||||
|
|
||||
|
/// 事件发生时间戳 |
||||
|
final int timestamp; |
||||
|
|
||||
|
BluetoothConnectionEvent({ |
||||
|
required this.type, |
||||
|
this.device, |
||||
|
this.state, |
||||
|
required this.timestamp, |
||||
|
}); |
||||
|
|
||||
|
factory BluetoothConnectionEvent.fromMap(Map<dynamic, dynamic> map) { |
||||
|
BluetoothEventType eventType; |
||||
|
|
||||
|
switch (map['type']) { |
||||
|
case 'deviceConnected': |
||||
|
eventType = BluetoothEventType.deviceConnected; |
||||
|
break; |
||||
|
case 'deviceDisconnected': |
||||
|
eventType = BluetoothEventType.deviceDisconnected; |
||||
|
break; |
||||
|
case 'bluetoothStateChanged': |
||||
|
eventType = BluetoothEventType.bluetoothStateChanged; |
||||
|
break; |
||||
|
default: |
||||
|
throw ArgumentError('未知事件类型: ${map['type']}'); |
||||
|
} |
||||
|
|
||||
|
final deviceMap = map['device'] as Map<dynamic, dynamic>?; |
||||
|
final BluetoothDevice? device = deviceMap != null |
||||
|
? BluetoothDevice( |
||||
|
name: deviceMap['name'] ?? '未知设备', |
||||
|
address: deviceMap['address'] ?? '', |
||||
|
type: deviceMap['type'] ?? 'unknown' |
||||
|
) |
||||
|
: null; |
||||
|
|
||||
|
return BluetoothConnectionEvent( |
||||
|
type: eventType, |
||||
|
device: device, |
||||
|
state: map['state'] as String?, |
||||
|
timestamp: map['timestamp'] as int? ?? DateTime.now().millisecondsSinceEpoch, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
String toString() { |
||||
|
switch (type) { |
||||
|
case BluetoothEventType.deviceConnected: |
||||
|
return '蓝牙设备已连接: ${device?.name}'; |
||||
|
case BluetoothEventType.deviceDisconnected: |
||||
|
return '蓝牙设备已断开: ${device?.name}'; |
||||
|
case BluetoothEventType.bluetoothStateChanged: |
||||
|
return '蓝牙状态改变: $state'; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 经典蓝牙服务 |
||||
|
class ClassicBluetoothService extends GetxService { |
||||
|
static ClassicBluetoothService get to => Get.find<ClassicBluetoothService>(); |
||||
|
|
||||
|
// 注意: MainActivity.kt中定义的通道名称为 com.deep_voice.classic_bluetooth |
||||
|
final MethodChannel _channel = const MethodChannel('com.deep_voice.classic_bluetooth'); |
||||
|
final EventChannel _eventChannel = const EventChannel('com.deep_voice.classic_bluetooth_events'); |
||||
|
|
||||
|
/// 蓝牙事件流控制器 |
||||
|
final _connectionEventController = StreamController<BluetoothConnectionEvent>.broadcast(); |
||||
|
|
||||
|
/// 蓝牙事件流 |
||||
|
Stream<BluetoothConnectionEvent> get connectionEvents => _connectionEventController.stream; |
||||
|
|
||||
|
/// 当前连接的设备 |
||||
|
final currentDevice = Rx<BluetoothDevice?>(null); |
||||
|
|
||||
|
/// 已连接设备列表 |
||||
|
final connectedDevices = <BluetoothDevice>[].obs; |
||||
|
|
||||
|
/// 蓝牙是否可用 |
||||
|
final _isBluetoothEnabled = true.obs; |
||||
|
bool get isBluetoothEnabled => _isBluetoothEnabled.value; |
||||
|
|
||||
|
/// 是否正在加载 |
||||
|
final isLoading = false.obs; |
||||
|
|
||||
|
/// 错误信息 |
||||
|
final errorMessage = ''.obs; |
||||
|
|
||||
|
/// 蓝牙状态变化事件流 |
||||
|
StreamSubscription? _bluetoothStateListener; |
||||
|
|
||||
|
/// 设备连接回调 |
||||
|
final List<Function(BluetoothDevice)> _deviceConnectedCallbacks = []; |
||||
|
|
||||
|
/// 设备断开回调 |
||||
|
final List<Function(BluetoothDevice)> _deviceDisconnectedCallbacks = []; |
||||
|
|
||||
|
/// 蓝牙状态变化回调 |
||||
|
final List<Function(String)> _bluetoothStateChangedCallbacks = []; |
||||
|
|
||||
|
/// 服务是否已初始化 |
||||
|
bool _isInitialized = false; |
||||
|
|
||||
|
ClassicBluetoothService() { |
||||
|
// 创建服务时不立即初始化,避免还没有原生支持时就调用方法 |
||||
|
Logger.info('创建 ClassicBluetoothService 实例'); |
||||
|
} |
||||
|
|
||||
|
/// 初始化蓝牙服务 |
||||
|
/// 必须在使用任何方法前调用此方法确保通道已设置 |
||||
|
Future<bool> initialize() async { |
||||
|
if (_isInitialized) { |
||||
|
Logger.info('ClassicBluetoothService 已经初始化'); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
Logger.info('正在初始化 ClassicBluetoothService...'); |
||||
|
|
||||
|
// 检查通道是否可用 |
||||
|
try { |
||||
|
// 调用一个简单方法检查通道是否已设置 |
||||
|
await _channel.invokeMethod<bool>('isBluetoothEnabled'); |
||||
|
Logger.info('蓝牙方法通道可用'); |
||||
|
} catch (e) { |
||||
|
Logger.error('蓝牙方法通道不可用: $e'); |
||||
|
// 这里不要立即返回false,尝试继续设置其他内容 |
||||
|
} |
||||
|
|
||||
|
// 设置事件监听器 |
||||
|
_setupBluetoothStateListener(); |
||||
|
|
||||
|
_isInitialized = true; |
||||
|
Logger.info('ClassicBluetoothService 初始化成功'); |
||||
|
return true; |
||||
|
} catch (e) { |
||||
|
Logger.error('初始化 ClassicBluetoothService 失败: $e'); |
||||
|
errorMessage.value = '初始化蓝牙服务失败'; |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 检查并请求蓝牙权限 |
||||
|
Future<bool> checkAndRequestPermissions() async { |
||||
|
try { |
||||
|
// 首先确保服务已初始化 |
||||
|
if (!_isInitialized && !(await initialize())) { |
||||
|
Logger.error('无法初始化蓝牙服务'); |
||||
|
errorMessage.value = '无法初始化蓝牙服务'; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 检查蓝牙相关权限 |
||||
|
Map<Permission, PermissionStatus> statuses = await [ |
||||
|
Permission.bluetooth, |
||||
|
Permission.bluetoothConnect, |
||||
|
// Permission.bluetoothScan, |
||||
|
// Permission.location, |
||||
|
Permission.microphone, |
||||
|
].request(); |
||||
|
|
||||
|
// 检查蓝牙连接权限 |
||||
|
if (statuses[Permission.bluetoothConnect] != PermissionStatus.granted) { |
||||
|
Logger.error('蓝牙连接权限被拒绝'); |
||||
|
errorMessage.value = '蓝牙连接权限被拒绝'; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} catch (e) { |
||||
|
Logger.error('检查蓝牙权限失败: $e'); |
||||
|
errorMessage.value = '检查蓝牙权限失败'; |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 刷新已连接的蓝牙设备 |
||||
|
Future<bool> refreshConnectedDevices() async { |
||||
|
try { |
||||
|
// 首先确保服务已初始化 |
||||
|
if (!_isInitialized && !(await initialize())) { |
||||
|
Logger.error('无法初始化蓝牙服务'); |
||||
|
errorMessage.value = '无法初始化蓝牙服务'; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
errorMessage.value = ''; |
||||
|
|
||||
|
// 获取A2DP设备 |
||||
|
final a2dpDevices = await getConnectedA2dpDevices(); |
||||
|
Logger.info('已连接的A2DP设备: ${a2dpDevices.length}'); |
||||
|
|
||||
|
// 获取耳机设备 |
||||
|
final headsetDevices = await getConnectedHeadsetDevices(); |
||||
|
Logger.info('已连接的耳机设备: ${headsetDevices.length}'); |
||||
|
|
||||
|
// 更新设备列表 |
||||
|
connectedDevices.value = [...a2dpDevices, ...headsetDevices]; |
||||
|
|
||||
|
// 更新当前设备 |
||||
|
if (connectedDevices.isNotEmpty && currentDevice.value == null) { |
||||
|
currentDevice.value = connectedDevices.first; |
||||
|
} |
||||
|
|
||||
|
return connectedDevices.isNotEmpty; |
||||
|
} catch (e) { |
||||
|
Logger.error('刷新已连接设备失败: $e'); |
||||
|
errorMessage.value = '获取设备信息失败'; |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 获取已连接的A2DP设备(音频设备) |
||||
|
Future<List<BluetoothDevice>> getConnectedA2dpDevices() async { |
||||
|
try { |
||||
|
// 确保服务已初始化 |
||||
|
if (!_isInitialized && !(await initialize())) { |
||||
|
Logger.error('无法初始化蓝牙服务'); |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
final result = await _channel.invokeMethod('getConnectedA2dpDevices'); |
||||
|
Logger.info('获取已连接的A2DP设备结果: $result'); |
||||
|
|
||||
|
if (result is List) { |
||||
|
final devices = result.map((device) => BluetoothDevice( |
||||
|
name: device['name'] ?? '未知设备', |
||||
|
address: device['address'] ?? '', |
||||
|
type: 'a2dp' |
||||
|
)).toList(); |
||||
|
|
||||
|
return devices; |
||||
|
} |
||||
|
|
||||
|
return []; |
||||
|
} catch (e) { |
||||
|
Logger.error('获取已连接的A2DP设备失败: $e'); |
||||
|
return []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 获取已连接的耳机设备 |
||||
|
Future<List<BluetoothDevice>> getConnectedHeadsetDevices() async { |
||||
|
try { |
||||
|
// 确保服务已初始化 |
||||
|
if (!_isInitialized && !(await initialize())) { |
||||
|
Logger.error('无法初始化蓝牙服务'); |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
final result = await _channel.invokeMethod('getConnectedHeadsetDevices'); |
||||
|
Logger.info('获取已连接的耳机设备结果: $result'); |
||||
|
|
||||
|
if (result is List) { |
||||
|
final devices = result.map((device) => BluetoothDevice( |
||||
|
name: device['name'] ?? '未知设备', |
||||
|
address: device['address'] ?? '', |
||||
|
type: 'headset' |
||||
|
)).toList(); |
||||
|
|
||||
|
return devices; |
||||
|
} |
||||
|
|
||||
|
return []; |
||||
|
} catch (e) { |
||||
|
Logger.error('获取已连接的耳机设备失败: $e'); |
||||
|
return []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 设置蓝牙状态监听 |
||||
|
void _setupBluetoothStateListener() { |
||||
|
try { |
||||
|
_bluetoothStateListener = _eventChannel |
||||
|
.receiveBroadcastStream() |
||||
|
.listen((event) { |
||||
|
Logger.info('收到蓝牙事件: $event'); |
||||
|
|
||||
|
if (event is Map) { |
||||
|
final type = event['type'] as String?; |
||||
|
|
||||
|
if (type == 'stateChanged') { |
||||
|
final state = event['state'] as String?; |
||||
|
if (state != null) { |
||||
|
_handleBluetoothStateChange(state); |
||||
|
} |
||||
|
} else if (type == 'deviceConnected') { |
||||
|
final deviceMap = event['device'] as Map?; |
||||
|
if (deviceMap != null) { |
||||
|
final device = BluetoothDevice( |
||||
|
name: deviceMap['name'] ?? '未知设备', |
||||
|
address: deviceMap['address'] ?? '', |
||||
|
type: deviceMap['type'] ?? 'unknown' |
||||
|
); |
||||
|
_handleDeviceConnected(device); |
||||
|
} |
||||
|
} else if (type == 'deviceDisconnected') { |
||||
|
final deviceMap = event['device'] as Map?; |
||||
|
if (deviceMap != null) { |
||||
|
final device = BluetoothDevice( |
||||
|
name: deviceMap['name'] ?? '未知设备', |
||||
|
address: deviceMap['address'] ?? '', |
||||
|
type: deviceMap['type'] ?? 'unknown' |
||||
|
); |
||||
|
_handleDeviceDisconnected(device); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, onError: (error) { |
||||
|
Logger.error('蓝牙状态监听错误: $error'); |
||||
|
}); |
||||
|
} catch (e) { |
||||
|
Logger.error('设置蓝牙状态监听失败: $e'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 处理蓝牙状态变化 |
||||
|
void _handleBluetoothStateChange(String state) { |
||||
|
Logger.info('蓝牙状态变化: $state'); |
||||
|
|
||||
|
// 如果蓝牙关闭,清除设备信息 |
||||
|
if (state == 'off') { |
||||
|
connectedDevices.clear(); |
||||
|
currentDevice.value = null; |
||||
|
} |
||||
|
|
||||
|
// 通知所有回调 |
||||
|
for (var callback in _bluetoothStateChangedCallbacks) { |
||||
|
callback(state); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 处理设备连接 |
||||
|
void _handleDeviceConnected(BluetoothDevice device) { |
||||
|
Logger.info('设备已连接: ${device.name}'); |
||||
|
|
||||
|
// 更新设备列表 |
||||
|
if (!connectedDevices.any((d) => d.address == device.address)) { |
||||
|
connectedDevices.add(device); |
||||
|
} |
||||
|
|
||||
|
// 更新当前设备 |
||||
|
currentDevice.value = device; |
||||
|
|
||||
|
// 通知所有回调 |
||||
|
for (var callback in _deviceConnectedCallbacks) { |
||||
|
callback(device); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 处理设备断开 |
||||
|
void _handleDeviceDisconnected(BluetoothDevice device) { |
||||
|
Logger.info('设备已断开: ${device.name}'); |
||||
|
|
||||
|
// 更新设备列表 |
||||
|
connectedDevices.removeWhere((d) => d.address == device.address); |
||||
|
|
||||
|
// 如果是当前设备,清除当前设备 |
||||
|
if (currentDevice.value?.address == device.address) { |
||||
|
currentDevice.value = connectedDevices.isEmpty ? null : connectedDevices.first; |
||||
|
} |
||||
|
|
||||
|
// 通知所有回调 |
||||
|
for (var callback in _deviceDisconnectedCallbacks) { |
||||
|
callback(device); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 注册设备连接回调 |
||||
|
void onDeviceConnected(Function(BluetoothDevice) callback) { |
||||
|
_deviceConnectedCallbacks.add(callback); |
||||
|
} |
||||
|
|
||||
|
/// 注册设备断开回调 |
||||
|
void onDeviceDisconnected(Function(BluetoothDevice) callback) { |
||||
|
_deviceDisconnectedCallbacks.add(callback); |
||||
|
} |
||||
|
|
||||
|
/// 注册蓝牙状态变化回调 |
||||
|
void onBluetoothStateChanged(Function(String) callback) { |
||||
|
_bluetoothStateChangedCallbacks.add(callback); |
||||
|
} |
||||
|
|
||||
|
/// 打开蓝牙设置 |
||||
|
Future<bool> openBluetoothSettings() async { |
||||
|
try { |
||||
|
await AppSettings.openAppSettings(); |
||||
|
return true; |
||||
|
} catch (e) { |
||||
|
Logger.error('打开蓝牙设置失败: $e'); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
void onClose() { |
||||
|
// 取消蓝牙状态监听 |
||||
|
_bluetoothStateListener?.cancel(); |
||||
|
|
||||
|
// 清除回调 |
||||
|
_deviceConnectedCallbacks.clear(); |
||||
|
_deviceDisconnectedCallbacks.clear(); |
||||
|
_bluetoothStateChangedCallbacks.clear(); |
||||
|
|
||||
|
super.onClose(); |
||||
|
} |
||||
|
} |
||||
@ -1,303 +1,185 @@ |
|||||
import 'package:get/get.dart'; |
import 'package:get/get.dart'; |
||||
|
import 'dart:async'; |
||||
import '../../../routes/app_routes.dart'; |
import '../../../routes/app_routes.dart'; |
||||
import '../../../data/services/bluetooth_service.dart'; |
import '../../../data/services/classic_bluetooth_service.dart' as classic; |
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart' hide BluetoothService; |
|
||||
import '../../../core/utils/logger.dart'; |
import '../../../core/utils/logger.dart'; |
||||
import 'package:permission_handler/permission_handler.dart'; |
|
||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||
|
import 'package:permission_handler/permission_handler.dart'; |
||||
|
|
||||
/// 简化的配对控制器,仅用于显示已连接或配对的蓝牙耳机信息 |
/// 简化的配对控制器,仅用于显示已连接经典蓝牙设备和处理基本配对流程 |
||||
class PairingController extends GetxController { |
class PairingController extends GetxController { |
||||
// 蓝牙服务 |
static PairingController get to => Get.find(); |
||||
late BluetoothService _bluetoothService; |
|
||||
|
|
||||
// 设备信息 |
// 经典蓝牙服务 |
||||
final _deviceInfo = Rx<Map<String, dynamic>>({}); |
late classic.ClassicBluetoothService _bluetoothService; |
||||
Map<String, dynamic> get deviceInfo => _deviceInfo.value; |
|
||||
|
|
||||
// 蓝牙状态 |
// 是否开始搜索 |
||||
final _isBluetoothAvailable = false.obs; |
final _isSearching = false.obs; |
||||
final _isBluetoothOn = false.obs; |
bool get isSearching => _isSearching.value; |
||||
bool get isBluetoothAvailable => _isBluetoothAvailable.value; |
|
||||
bool get isBluetoothOn => _isBluetoothOn.value; |
|
||||
|
|
||||
// 错误消息 |
// 设备连接状态 |
||||
final _errorMessage = ''.obs; |
final _isConnecting = false.obs; |
||||
String get errorMessage => _errorMessage.value; |
bool get isConnecting => _isConnecting.value; |
||||
|
|
||||
// 已连接的设备列表 |
// 蓝牙服务是否可用 |
||||
final _connectedDevices = <BluetoothDevice>[].obs; |
final _isBluetoothServiceAvailable = true.obs; |
||||
List<BluetoothDevice> get connectedDevices => _connectedDevices; |
bool get isBluetoothServiceAvailable => _isBluetoothServiceAvailable.value; |
||||
|
|
||||
// 已配对的设备列表 |
// 当前连接设备 |
||||
final _pairedDevices = <BluetoothDevice>[].obs; |
Rx<classic.BluetoothDevice?> get currentDevice => _bluetoothService.currentDevice; |
||||
List<BluetoothDevice> get pairedDevices => _pairedDevices; |
|
||||
|
|
||||
// 状态描述(用于UI显示) |
// 连接设备列表 |
||||
final _statusDescription = ''.obs; |
RxList<classic.BluetoothDevice> get connectedDevices => _bluetoothService.connectedDevices; |
||||
String get statusDescription => _statusDescription.value; |
|
||||
|
// 错误信息 |
||||
|
RxString get errorMessage => _bluetoothService.errorMessage; |
||||
|
|
||||
// 防止重复刷新的标志 |
// 连接设备列表(兼容旧代码) |
||||
bool _isRefreshing = false; |
List<classic.BluetoothDevice> get connectedClassicDevices => |
||||
|
_bluetoothService.currentDevice.value != null |
||||
|
? [_bluetoothService.currentDevice.value!] |
||||
|
: []; |
||||
|
|
||||
// 上次刷新时间 |
// 状态描述(用于UI显示) |
||||
DateTime? _lastRefreshTime; |
final statusDescription = '正在初始化...'.obs; |
||||
|
|
||||
|
// 加载状态 |
||||
|
final isLoading = false.obs; |
||||
|
|
||||
@override |
@override |
||||
void onInit() { |
void onInit() { |
||||
super.onInit(); |
super.onInit(); |
||||
_initializeProcess(); |
_initialize(); |
||||
} |
} |
||||
|
|
||||
// 初始化流程 |
@override |
||||
Future<void> _initializeProcess() async { |
void onReady() { |
||||
_updateStatus('正在初始化...'); |
super.onReady(); |
||||
|
// 设置连接监听器,确保在页面完全加载后设置,避免过早监听可能导致的问题 |
||||
try { |
_setupDeviceListeners(); |
||||
// 获取或初始化蓝牙服务 |
|
||||
await _getBluetoothService(); |
|
||||
|
|
||||
// 检查权限并获取设备信息 |
|
||||
await _checkAndRequestPermissions(); |
|
||||
} catch (e) { |
|
||||
Logger.error('初始化过程失败: $e'); |
|
||||
_updateStatus('初始化失败'); |
|
||||
_setError('初始化蓝牙服务失败,请重试'); |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
// 获取蓝牙服务 |
/// 设置设备监听器 |
||||
Future<void> _getBluetoothService() async { |
void _setupDeviceListeners() { |
||||
try { |
try { |
||||
// 检查是否已注册 |
if (!_isBluetoothServiceAvailable.value) return; |
||||
if (Get.isRegistered<BluetoothService>()) { |
|
||||
_bluetoothService = Get.find<BluetoothService>(); |
|
||||
} else { |
|
||||
// 初始化并注册服务 |
|
||||
_bluetoothService = await Get.put(BluetoothService()).init(); |
|
||||
} |
|
||||
|
|
||||
// 设置蓝牙状态监听 |
|
||||
_setupBluetoothListeners(); |
|
||||
|
|
||||
// 获取设备信息 |
// 监听设备连接事件 |
||||
_refreshDeviceInfo(); |
_bluetoothService.onDeviceConnected((device) { |
||||
|
Logger.info('检测到蓝牙设备连接: ${device.name}'); |
||||
|
|
||||
|
// 更新状态并跳转到主页 |
||||
|
statusDescription.value = '已连接到设备: ${device.name}'; |
||||
|
|
||||
|
// 立即跳转到主页 |
||||
|
_navigateToHome(); |
||||
|
}); |
||||
} catch (e) { |
} catch (e) { |
||||
Logger.error('获取蓝牙服务失败: $e'); |
Logger.error('设置设备监听器失败: $e'); |
||||
throw Exception('获取蓝牙服务失败: $e'); |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
// 设置蓝牙状态监听 |
/// 立即跳转到主页 |
||||
void _setupBluetoothListeners() { |
void _navigateToHome() { |
||||
// 监听蓝牙可用性 |
Get.offAllNamed(Routes.home); |
||||
_isBluetoothAvailable.value = _bluetoothService.isAvailable; |
|
||||
ever(_bluetoothService.isAvailableObs, (bool available) { |
|
||||
_isBluetoothAvailable.value = available; |
|
||||
|
|
||||
if (!available) { |
|
||||
_updateStatus('蓝牙不可用'); |
|
||||
_setError('蓝牙不可用,请检查设备是否支持蓝牙功能'); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
// 监听蓝牙开关状态 |
|
||||
_isBluetoothOn.value = _bluetoothService.isOn; |
|
||||
ever(_bluetoothService.isOnObs, (bool on) { |
|
||||
_isBluetoothOn.value = on; |
|
||||
|
|
||||
if (on) { |
|
||||
// 蓝牙开启时,延迟一下再刷新设备信息,避免频繁刷新 |
|
||||
Future.delayed(const Duration(milliseconds: 500), () { |
|
||||
_refreshDeviceInfo(); |
|
||||
}); |
|
||||
} else { |
|
||||
_updateStatus('蓝牙已关闭'); |
|
||||
_setError('蓝牙已关闭,请开启蓝牙后再试'); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
// 监听已连接设备变化 |
|
||||
_connectedDevices.value = _bluetoothService.connectedDevices; |
|
||||
ever(_bluetoothService.connectedDevicesObs, (List<BluetoothDevice> devices) { |
|
||||
_connectedDevices.value = devices; |
|
||||
|
|
||||
// 只有当设备列表不为空时才刷新设备信息 |
|
||||
if (devices.isNotEmpty) { |
|
||||
// 延迟一下再刷新,避免频繁刷新 |
|
||||
Future.delayed(const Duration(milliseconds: 500), () { |
|
||||
_refreshDeviceInfo(); |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
// 监听已配对设备变化 |
|
||||
_pairedDevices.value = _bluetoothService.systemPairedDevices; |
|
||||
} |
} |
||||
|
|
||||
// 检查并请求权限 |
/// 完整的初始化流程 |
||||
Future<void> _checkAndRequestPermissions() async { |
Future<void> _initialize() async { |
||||
_updateStatus('正在请求权限...'); |
|
||||
|
|
||||
try { |
try { |
||||
bool hasPermissions = await _bluetoothService.requestPermissions(); |
isLoading.value = true; |
||||
|
statusDescription.value = '正在初始化...'; |
||||
|
|
||||
|
// 1. 获取蓝牙服务 |
||||
|
_bluetoothService = Get.find<classic.ClassicBluetoothService>(); |
||||
|
|
||||
if (!hasPermissions) { |
// 确保蓝牙服务已初始化 |
||||
// 权限被拒绝 |
if (!await _bluetoothService.initialize()) { |
||||
_handlePermissionDenied(); |
Logger.error('蓝牙服务初始化失败'); |
||||
|
statusDescription.value = '蓝牙服务初始化失败'; |
||||
|
_isBluetoothServiceAvailable.value = false; |
||||
|
isLoading.value = false; |
||||
return; |
return; |
||||
} |
} |
||||
|
|
||||
// 权限已授予,检查蓝牙状态 |
// 2. 检查权限 |
||||
if (!_bluetoothService.isOn) { |
statusDescription.value = '正在检查权限...'; |
||||
_updateStatus('蓝牙已关闭'); |
final hasPermission = await _bluetoothService.checkAndRequestPermissions(); |
||||
_setError('蓝牙已关闭,请开启蓝牙后再试'); |
if (!hasPermission) { |
||||
|
statusDescription.value = '缺少蓝牙权限'; |
||||
|
isLoading.value = false; |
||||
return; |
return; |
||||
} |
} |
||||
|
|
||||
// 刷新设备信息 |
// 3. 刷新设备列表 |
||||
_refreshDeviceInfo(); |
statusDescription.value = '正在搜索设备...'; |
||||
} catch (e) { |
await refreshConnectedDevices(); |
||||
Logger.error('权限请求失败: $e'); |
|
||||
_updateStatus('权限请求失败'); |
|
||||
_setError('权限请求失败,请手动授予蓝牙和位置权限'); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 处理权限被拒绝的情况 |
|
||||
void _handlePermissionDenied() async { |
|
||||
// 检查是否永久拒绝 |
|
||||
bool isPermanentlyDenied = false; |
|
||||
|
|
||||
if (GetPlatform.isAndroid) { |
|
||||
PermissionStatus bluetoothScan = await Permission.bluetoothScan.status; |
|
||||
PermissionStatus bluetoothConnect = await Permission.bluetoothConnect.status; |
|
||||
PermissionStatus location = await Permission.location.status; |
|
||||
|
|
||||
// 如果任一权限被永久拒绝 |
// 4. 处理结果 |
||||
isPermanentlyDenied = |
if (_bluetoothService.connectedDevices.isNotEmpty) { |
||||
bluetoothScan.isPermanentlyDenied || |
statusDescription.value = '找到已连接的设备'; |
||||
bluetoothConnect.isPermanentlyDenied || |
// 延迟跳转到主页 |
||||
location.isPermanentlyDenied; |
_autoNavigateToHome(); |
||||
} |
} else { |
||||
|
// 没有找到设备 |
||||
_updateStatus('缺少必要权限'); |
statusDescription.value = '未找到已连接的蓝牙设备'; |
||||
|
} |
||||
if (isPermanentlyDenied) { |
|
||||
_setError('缺少必要权限,您已永久拒绝权限,请前往系统设置中手动开启'); |
} catch (e) { |
||||
// 显示前往设置的提示 |
Logger.error('初始化配对控制器失败: $e'); |
||||
_showGoToSettingsDialog(); |
statusDescription.value = '初始化失败'; |
||||
} else { |
_isBluetoothServiceAvailable.value = false; |
||||
_setError('缺少必要权限,请授予蓝牙和位置权限以继续'); |
} finally { |
||||
|
isLoading.value = false; |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
// 显示前往设置的对话框 |
/// 刷新设备 |
||||
void _showGoToSettingsDialog() { |
Future<void> refreshConnectedDevices() async { |
||||
Get.dialog( |
|
||||
AlertDialog( |
|
||||
title: const Text('需要权限'), |
|
||||
content: const Text('蓝牙搜索需要特定权限,请前往系统设置开启'), |
|
||||
actions: [ |
|
||||
TextButton( |
|
||||
onPressed: () => Get.back(), |
|
||||
child: const Text('稍后'), |
|
||||
), |
|
||||
TextButton( |
|
||||
onPressed: () { |
|
||||
Get.back(); |
|
||||
openAppSettings(); |
|
||||
}, |
|
||||
child: const Text('前往设置'), |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
barrierDismissible: false, |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
// 刷新设备信息 |
|
||||
Future<void> _refreshDeviceInfo() async { |
|
||||
// 防止重复刷新 |
|
||||
if (_isRefreshing) return; |
|
||||
|
|
||||
_isRefreshing = true; |
|
||||
|
|
||||
if (!_isBluetoothOn.value) { |
|
||||
_updateStatus('蓝牙已关闭'); |
|
||||
_deviceInfo.value = {}; |
|
||||
_isRefreshing = false; |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
try { |
try { |
||||
// 更新已配对设备列表 |
isLoading.value = true; |
||||
_pairedDevices.value = _bluetoothService.systemPairedDevices; |
statusDescription.value = '正在搜索设备...'; |
||||
|
|
||||
// 查找系统已连接设备 |
final hasDevices = await _bluetoothService.refreshConnectedDevices(); |
||||
BluetoothDevice? device = await _bluetoothService.findConnectedSystemDevice(); |
|
||||
|
|
||||
if (device != null) { |
if (hasDevices) { |
||||
// 已连接设备 |
statusDescription.value = '找到已连接的设备'; |
||||
_deviceInfo.value = _bluetoothService.getConnectedDeviceInfo(); |
// 延迟跳转到主页 |
||||
_updateStatus('已连接到蓝牙设备'); |
_autoNavigateToHome(); |
||||
_goToHomePage(); |
|
||||
} else if (_bluetoothService.lastConnectedDeviceInfo.isNotEmpty) { |
|
||||
// 使用上次连接的设备信息 |
|
||||
_deviceInfo.value = _bluetoothService.lastConnectedDeviceInfo; |
|
||||
_updateStatus('使用上次连接的设备信息'); |
|
||||
_goToHomePage(); |
|
||||
} else if (_pairedDevices.isNotEmpty) { |
|
||||
// 使用已配对设备 |
|
||||
BluetoothDevice pairedDevice = _pairedDevices.first; |
|
||||
_deviceInfo.value = _bluetoothService.getPairedDeviceInfo(pairedDevice); |
|
||||
_bluetoothService.setSelectedDevice(pairedDevice); |
|
||||
_updateStatus('已找到配对设备'); |
|
||||
_goToHomePage(); |
|
||||
} else { |
} else { |
||||
// 没有找到任何设备 |
// 没有找到设备 |
||||
_deviceInfo.value = {}; |
statusDescription.value = '未找到已连接的蓝牙设备'; |
||||
_updateStatus('未找到已连接或配对的蓝牙设备'); |
|
||||
} |
} |
||||
} catch (e) { |
} catch (e) { |
||||
Logger.error('刷新设备信息失败: $e'); |
Logger.error('刷新设备失败: $e'); |
||||
_updateStatus('获取设备信息失败'); |
statusDescription.value = '刷新设备失败'; |
||||
} finally { |
} finally { |
||||
_isRefreshing = false; |
isLoading.value = false; |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
// 更新状态描述 |
/// 自动跳转到主页 |
||||
void _updateStatus(String description) { |
void _autoNavigateToHome() { |
||||
_statusDescription.value = description; |
if (_bluetoothService.connectedDevices.isEmpty) return; |
||||
Logger.info('状态更新: $description'); |
|
||||
} |
Logger.info('已找到连接的设备,自动跳转到主页'); |
||||
|
|
||||
// 设置错误信息 |
Future.delayed(const Duration(milliseconds: 500), () { |
||||
void _setError(String message) { |
Get.offAllNamed(Routes.home); |
||||
_errorMessage.value = message; |
}); |
||||
Logger.warning('错误: $message'); |
|
||||
} |
} |
||||
|
|
||||
// 跳转到主页 |
/// 打开系统蓝牙设置 |
||||
void _goToHomePage() { |
void openBluetoothSettings() { |
||||
Get.offAllNamed(Routes.home, arguments: _deviceInfo.value); |
_bluetoothService.openBluetoothSettings(); |
||||
} |
} |
||||
|
|
||||
// 刷新搜索 |
/// 刷新设备(兼容旧代码) |
||||
void refreshSearch() { |
Future<void> refreshDevices() async { |
||||
// 防止频繁刷新,限制最小刷新间隔为2秒 |
await refreshConnectedDevices(); |
||||
final now = DateTime.now(); |
|
||||
if (_lastRefreshTime != null && |
|
||||
now.difference(_lastRefreshTime!).inSeconds < 2) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
_lastRefreshTime = now; |
|
||||
_refreshDeviceInfo(); |
|
||||
} |
} |
||||
|
|
||||
// 跳过连接 |
/// 跳过连接 |
||||
void skipConnecting() { |
void skipConnecting() { |
||||
Get.offAllNamed(Routes.home); |
Get.offAllNamed(Routes.home); |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue