You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
657 lines
19 KiB
657 lines
19 KiB
import 'package:app_settings/app_settings.dart';
|
|
import 'package:get/get.dart';
|
|
import 'dart:async';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
import '../../../routes/app_routes.dart';
|
|
import '../../../core/utils/logger.dart';
|
|
import 'dart:io';
|
|
import '../../../data/services/bluetooth_manager.dart';
|
|
import '../../../data/services/ble_manager.dart';
|
|
|
|
/// 配对状态枚举
|
|
enum PairingState {
|
|
init, // 初始状态
|
|
btReady, // 蓝牙已就绪
|
|
classicOk, // 经典蓝牙已连接
|
|
serverVerify, // 服务器验证中
|
|
pairing, // 配对中
|
|
paired, // 已配对
|
|
// 错误/退出状态
|
|
exitNoPermission, // 无权限
|
|
exitBtOff, // 蓝牙关闭
|
|
exitNoClassic, // 无经典蓝牙连接
|
|
exitNotMatch, // 设备不匹配
|
|
exitIllegal, // 非法设备
|
|
exitBound, // 已被他人绑定
|
|
exitPairFailed, // 配对失败
|
|
exitTimeout, // 超时
|
|
}
|
|
|
|
/// 配对控制器 - 基于有限状态机的实现
|
|
class PairingController extends GetxController {
|
|
static PairingController get to => Get.find();
|
|
|
|
// ==================== 状态管理 ====================
|
|
final _state = PairingState.init.obs;
|
|
PairingState get state => _state.value;
|
|
|
|
// ==================== 状态数据 ====================
|
|
final statusDescription = ''.obs;
|
|
final actionButtonText = ''.obs;
|
|
final isLoading = false.obs;
|
|
|
|
// 当前设备信息
|
|
BluetoothDevice? _currentClassicDevice;
|
|
|
|
// 计时器和异步操作管理
|
|
Timer? _stateTimer;
|
|
StreamSubscription? _bluetoothStateSubscription;
|
|
StreamSubscription? _deviceSubscription;
|
|
StreamSubscription? _bleStatusSubscription;
|
|
|
|
// 常量定义
|
|
static const _tag = 'PairingController';
|
|
static const _validDeviceNamePattern = 'DeapSound';
|
|
|
|
// ==================== 生命周期 ====================
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
_setupStateObserver();
|
|
start();
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
_cancelAllOperations();
|
|
_bluetoothStateSubscription?.cancel();
|
|
_deviceSubscription?.cancel();
|
|
_bleStatusSubscription?.cancel();
|
|
super.onClose();
|
|
}
|
|
|
|
// ==================== 公共 API ====================
|
|
|
|
/// 启动配对流程
|
|
Future<void> start() async {
|
|
_transition(PairingState.init);
|
|
}
|
|
|
|
/// 重新开始配对流程
|
|
Future<void> restart() async {
|
|
_cancelAllOperations();
|
|
_transition(PairingState.init);
|
|
}
|
|
|
|
/// 处理按钮点击
|
|
void handleActionButtonTap() {
|
|
switch (state) {
|
|
case PairingState.exitNoPermission:
|
|
_openAppSettings();
|
|
break;
|
|
case PairingState.exitBtOff:
|
|
_openBluetoothSettings();
|
|
break;
|
|
case PairingState.exitNoClassic:
|
|
case PairingState.exitNotMatch:
|
|
_openBluetoothSettings();
|
|
break;
|
|
case PairingState.exitTimeout:
|
|
restart();
|
|
break;
|
|
case PairingState.exitPairFailed:
|
|
case PairingState.exitBound:
|
|
_retryPairing();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// 跳过配对
|
|
void skipPairing() async {
|
|
Logger.i(_tag, '用户选择跳过配对');
|
|
// 启动BLE后台服务
|
|
await BleManager.to.startAgentService();
|
|
Logger.i(_tag, 'BLE后台服务已启动');
|
|
_navigateToHome();
|
|
}
|
|
|
|
// ==================== FSM 核心实现 ====================
|
|
|
|
/// 状态转换
|
|
void _transition(PairingState next) {
|
|
final current = state;
|
|
|
|
// 允许init状态重复执行,其他状态避免重复转换
|
|
if (current == next && next != PairingState.init) return;
|
|
|
|
Logger.d(_tag, 'FSM: ${current.name} → ${next.name}');
|
|
_cancelStateTimer();
|
|
_state.value = next;
|
|
|
|
// 根据新状态执行对应的进入方法
|
|
switch (next) {
|
|
case PairingState.init:
|
|
_enterInit();
|
|
break;
|
|
case PairingState.btReady:
|
|
_enterBtReady();
|
|
break;
|
|
case PairingState.classicOk:
|
|
_enterClassicOk();
|
|
break;
|
|
case PairingState.serverVerify:
|
|
_enterServerVerify();
|
|
break;
|
|
case PairingState.pairing:
|
|
_enterPairing();
|
|
break;
|
|
case PairingState.paired:
|
|
_enterPaired();
|
|
break;
|
|
// 错误状态
|
|
case PairingState.exitNoPermission:
|
|
case PairingState.exitBtOff:
|
|
case PairingState.exitNoClassic:
|
|
case PairingState.exitNotMatch:
|
|
case PairingState.exitIllegal:
|
|
case PairingState.exitBound:
|
|
case PairingState.exitPairFailed:
|
|
case PairingState.exitTimeout:
|
|
_handleErrorState(next);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// 设置状态观察器
|
|
void _setupStateObserver() {
|
|
ever(_state, (PairingState state) {
|
|
_updateUI();
|
|
});
|
|
}
|
|
|
|
// ==================== 状态处理方法 ====================
|
|
|
|
/// INIT: 初始化,检查权限
|
|
Future<void> _enterInit() async {
|
|
Logger.i(_tag, '开始执行初始化流程');
|
|
|
|
isLoading.value = true;
|
|
statusDescription.value = '正在检查权限...';
|
|
|
|
try {
|
|
// iOS平台跳过权限检查
|
|
if (!Platform.isIOS) {
|
|
final hasPermission = await _checkAndRequestPermissions();
|
|
if (!hasPermission) {
|
|
_transition(PairingState.exitNoPermission);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 设置蓝牙状态监听
|
|
_setupBluetoothListener();
|
|
|
|
// 检查蓝牙状态
|
|
final btState = BluetoothManager.to.bluetoothState;
|
|
Logger.i(_tag, '当前蓝牙状态: $btState');
|
|
|
|
if (btState == 'on' || btState == 'unknown') {
|
|
_transition(PairingState.btReady);
|
|
} else {
|
|
_transition(PairingState.exitBtOff);
|
|
}
|
|
} catch (e) {
|
|
Logger.e(_tag, 'Init failed: $e');
|
|
_transition(PairingState.exitNoPermission);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
/// BT_READY: 蓝牙已就绪,检查经典蓝牙连接
|
|
Future<void> _enterBtReady() async {
|
|
isLoading.value = true;
|
|
statusDescription.value = '正在检查设备连接...';
|
|
|
|
try {
|
|
// 刷新设备连接
|
|
await BluetoothManager.to.refreshDeviceConnection();
|
|
|
|
// 获取当前连接的经典蓝牙设备
|
|
final device = BluetoothManager.to.currentDevice;
|
|
if (device == null) {
|
|
_transition(PairingState.exitNoClassic);
|
|
return;
|
|
}
|
|
|
|
_currentClassicDevice = device;
|
|
_transition(PairingState.classicOk);
|
|
} catch (e) {
|
|
Logger.e(_tag, 'BT ready check failed: $e');
|
|
_transition(PairingState.exitNoClassic);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
/// CLASSIC_OK: 经典蓝牙已连接,检查是否需要BLE配对
|
|
Future<void> _enterClassicOk() async {
|
|
if (_currentClassicDevice == null) {
|
|
_transition(PairingState.exitNoClassic);
|
|
return;
|
|
}
|
|
|
|
statusDescription.value = '已连接: ${_currentClassicDevice!.name}';
|
|
|
|
// 检查设备名称是否合规
|
|
if (!_isValidDeviceName(_currentClassicDevice!.name)) {
|
|
_transition(PairingState.exitNotMatch);
|
|
return;
|
|
}
|
|
|
|
// 检查是否已有BLE配对记录
|
|
try {
|
|
// 1. 检查平台是否支持Companion功能
|
|
final isSupported = await BleManager.to.isCompanionSupported();
|
|
if (!isSupported) {
|
|
Logger.i(_tag, '设备不支持Companion功能,跳过BLE配对');
|
|
_transition(PairingState.paired);
|
|
return;
|
|
}
|
|
|
|
// 2. 获取已配对的MAC地址
|
|
final pairedMac = await BleManager.to.getPairedMacAddress();
|
|
|
|
// 3. 检查是否已经配对且是同一设备
|
|
if (pairedMac != null && pairedMac == _currentClassicDevice!.address) {
|
|
Logger.i(_tag, '设备已完成BLE配对: $pairedMac');
|
|
|
|
// 4. 检查BLE连接状态
|
|
final isBleConnected = await BleManager.to.isBleConnected();
|
|
if (isBleConnected) {
|
|
Logger.i(_tag, 'BLE已连接,跳转到配对成功');
|
|
_transition(PairingState.paired);
|
|
} else {
|
|
Logger.i(_tag, 'BLE未连接,尝试重新配对');
|
|
_transition(PairingState.pairing);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 5. 如果没有配对或配对的是其他设备,进入验证流程
|
|
_transition(PairingState.serverVerify);
|
|
} catch (e) {
|
|
Logger.e(_tag, 'Classic OK check failed: $e');
|
|
// 出错时直接尝试配对
|
|
_transition(PairingState.pairing);
|
|
}
|
|
}
|
|
|
|
/// SERVER_VERIFY: 服务器验证
|
|
Future<void> _enterServerVerify() async {
|
|
isLoading.value = true;
|
|
statusDescription.value = '正在验证设备...';
|
|
|
|
try {
|
|
if (_currentClassicDevice == null) {
|
|
_transition(PairingState.exitNotMatch);
|
|
return;
|
|
}
|
|
|
|
// 验证设备是否可以配对
|
|
final canPair = await BleManager.to
|
|
.verifyDeviceCanPair(_currentClassicDevice!.address);
|
|
|
|
if (canPair) {
|
|
Logger.i(_tag, '设备验证通过,可以配对');
|
|
_transition(PairingState.pairing);
|
|
} else {
|
|
Logger.e(_tag, '设备验证失败,不能配对');
|
|
_transition(PairingState.exitIllegal);
|
|
}
|
|
} catch (e) {
|
|
Logger.e(_tag, 'Server verify failed: $e');
|
|
_transition(PairingState.exitIllegal);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
/// PAIRING: 执行配对
|
|
Future<void> _enterPairing() async {
|
|
isLoading.value = true;
|
|
statusDescription.value = '正在配对设备...';
|
|
|
|
try {
|
|
if (_currentClassicDevice == null) {
|
|
_transition(PairingState.exitPairFailed);
|
|
return;
|
|
}
|
|
|
|
// 启动BLE状态监听
|
|
_setupBleStatusListener();
|
|
|
|
// 检查当前BLE连接状态,防止错过已经连接的事件
|
|
final isBleConnected = await BleManager.to.isBleConnected();
|
|
if (isBleConnected) {
|
|
Logger.i(_tag, 'BLE已经连接,直接完成配对');
|
|
_onBleConnected();
|
|
return;
|
|
}
|
|
|
|
// 设置配对超时保护
|
|
_stateTimer = Timer(const Duration(seconds: 10), () {
|
|
if (state == PairingState.pairing) {
|
|
Logger.e(_tag, 'BLE配对超时');
|
|
_transition(PairingState.exitTimeout);
|
|
}
|
|
});
|
|
|
|
// 使用系统配对框架执行BLE配对
|
|
// iOS: AccessorySetupKit
|
|
// Android: Companion Device Manager
|
|
final success = await BleManager.to.startDeviceAssociation(
|
|
deviceNamePattern: _currentClassicDevice!.name,
|
|
macAddress: _currentClassicDevice!.address,
|
|
);
|
|
|
|
if (success) {
|
|
Logger.i(_tag, 'BLE配对启动成功,等待连接完成...');
|
|
// 配对结果将通过BLE状态监听回调处理
|
|
} else {
|
|
Logger.e(_tag, 'BLE配对启动失败');
|
|
_transition(PairingState.exitPairFailed);
|
|
}
|
|
} catch (e) {
|
|
Logger.e(_tag, 'Pairing failed: $e');
|
|
_transition(PairingState.exitPairFailed);
|
|
}
|
|
}
|
|
|
|
/// 设置BLE状态监听
|
|
void _setupBleStatusListener() {
|
|
Logger.d(_tag, '🎯 开始设置BLE状态监听器...');
|
|
|
|
_bleStatusSubscription?.cancel();
|
|
|
|
Logger.d(_tag, '📱 当前BLE连接状态: ${BleManager.to.connectionState}');
|
|
Logger.d(_tag, '🔗 开始监听BleManager.connectionStateRx...');
|
|
|
|
// 监听BleManager的连接状态变化,而不是直接监听BleService
|
|
_bleStatusSubscription = BleManager.to.connectionStateRx.listen(
|
|
(bleState) {
|
|
// 只在配对状态下处理BLE连接事件
|
|
if (state == PairingState.pairing) {
|
|
if (bleState == BleConnectionState.connected) {
|
|
Logger.i(_tag, 'BLE连接成功,处理配对完成');
|
|
_onBleConnected();
|
|
} else if (bleState == BleConnectionState.error) {
|
|
Logger.e(_tag, 'BLE连接错误');
|
|
_transition(PairingState.exitPairFailed);
|
|
} else {
|
|
Logger.d(_tag, 'BLE状态变化,但非关键状态: $bleState');
|
|
}
|
|
} else {
|
|
Logger.d(_tag, '当前状态为${state.name},忽略BLE连接事件');
|
|
}
|
|
},
|
|
onError: (error) {
|
|
Logger.e(_tag, 'BLE状态监听错误: $error');
|
|
},
|
|
onDone: () {
|
|
Logger.w(_tag, 'BLE状态监听流已关闭');
|
|
},
|
|
);
|
|
|
|
Logger.d(_tag, 'BLE状态监听器已设置完成');
|
|
}
|
|
|
|
/// 处理BLE连接成功
|
|
Future<void> _onBleConnected() async {
|
|
Logger.i(_tag, 'BLE连接成功');
|
|
|
|
// 取消超时计时器
|
|
_cancelStateTimer();
|
|
|
|
// BLE连接成功即表示配对完成
|
|
// iOS使用UUID,Android使用MAC地址,但连接成功都表示配对验证通过
|
|
Logger.i(_tag, 'BLE配对验证成功,经典蓝牙和BLE均已连接');
|
|
_transition(PairingState.paired);
|
|
}
|
|
|
|
/// PAIRED: 配对成功
|
|
Future<void> _enterPaired() async {
|
|
isLoading.value = false;
|
|
statusDescription.value = '配对成功!';
|
|
|
|
try {
|
|
if (_currentClassicDevice != null) {
|
|
// Android平台注册后台扫描
|
|
if (Platform.isAndroid) {
|
|
await BleManager.to.registerBackgroundScan(
|
|
macAddress: _currentClassicDevice!.address);
|
|
Logger.i(_tag, '已注册后台扫描');
|
|
}
|
|
}
|
|
|
|
// 启动BLE后台服务
|
|
await BleManager.to.startAgentService();
|
|
Logger.i(_tag, 'BLE后台服务已启动');
|
|
|
|
// 延迟后跳转到主页
|
|
await Future.delayed(const Duration(milliseconds: 500));
|
|
_navigateToHome();
|
|
} catch (e) {
|
|
Logger.e(_tag, 'Post pairing setup failed: $e');
|
|
// 即使失败也跳转到主页
|
|
// _navigateToHome();
|
|
}
|
|
}
|
|
|
|
/// 处理错误状态
|
|
void _handleErrorState(PairingState errorState) {
|
|
isLoading.value = false;
|
|
|
|
switch (errorState) {
|
|
case PairingState.exitNoPermission:
|
|
statusDescription.value = '需要蓝牙权限';
|
|
actionButtonText.value = '去设置';
|
|
break;
|
|
case PairingState.exitBtOff:
|
|
statusDescription.value = '请开启蓝牙';
|
|
actionButtonText.value = '去设置';
|
|
break;
|
|
case PairingState.exitNoClassic:
|
|
statusDescription.value = '请先连接DeapSound设备';
|
|
actionButtonText.value = '去设置';
|
|
break;
|
|
case PairingState.exitNotMatch:
|
|
statusDescription.value = '请连接DeapSound设备';
|
|
actionButtonText.value = '去设置';
|
|
break;
|
|
case PairingState.exitTimeout:
|
|
statusDescription.value = '扫描超时';
|
|
actionButtonText.value = '重试';
|
|
break;
|
|
case PairingState.exitPairFailed:
|
|
statusDescription.value = '配对失败';
|
|
actionButtonText.value = '重新配对';
|
|
break;
|
|
case PairingState.exitBound:
|
|
statusDescription.value = '设备已被其他用户绑定';
|
|
actionButtonText.value = '联系客服';
|
|
break;
|
|
case PairingState.exitIllegal:
|
|
statusDescription.value = '设备验证失败';
|
|
actionButtonText.value = '重试';
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ==================== 辅助方法 ====================
|
|
|
|
/// 检查并请求权限
|
|
Future<bool> _checkAndRequestPermissions() async {
|
|
try {
|
|
if (Platform.isIOS) {
|
|
// iOS权限由系统自动处理
|
|
return true;
|
|
}
|
|
|
|
// Android权限检查
|
|
final permissions = <Permission>[];
|
|
permissions.add(Permission.locationWhenInUse);
|
|
|
|
if (Platform.isAndroid && await _getAndroidSdkVersion() >= 31) {
|
|
permissions.add(Permission.bluetoothScan);
|
|
permissions.add(Permission.bluetoothConnect);
|
|
}
|
|
|
|
final statuses = await permissions.request();
|
|
return statuses.values.every((status) => status.isGranted);
|
|
} catch (e) {
|
|
Logger.e(_tag, '权限检查失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 设置蓝牙状态监听
|
|
void _setupBluetoothListener() {
|
|
_bluetoothStateSubscription?.cancel();
|
|
_bluetoothStateSubscription =
|
|
BluetoothManager.to.bluetoothState.obs.listen((btState) {
|
|
Logger.d(_tag, '蓝牙状态变化: $btState');
|
|
|
|
// 根据当前状态决定如何响应蓝牙状态变化
|
|
if (btState == 'off' && state != PairingState.exitBtOff) {
|
|
_transition(PairingState.exitBtOff);
|
|
} else if (btState == 'on' && state == PairingState.exitBtOff) {
|
|
_transition(PairingState.btReady);
|
|
}
|
|
});
|
|
|
|
// 监听设备连接状态
|
|
_deviceSubscription?.cancel();
|
|
_deviceSubscription = BluetoothManager.to.currentDeviceRx.listen((device) {
|
|
Logger.d(_tag, '设备连接状态变化: ${device?.name}');
|
|
|
|
// 根据当前状态决定如何响应设备变化
|
|
if (device != null && state == PairingState.exitNoClassic) {
|
|
_currentClassicDevice = device;
|
|
_transition(PairingState.classicOk);
|
|
} else if (device == null &&
|
|
(state == PairingState.classicOk ||
|
|
state == PairingState.pairing ||
|
|
state == PairingState.serverVerify)) {
|
|
_transition(PairingState.exitNoClassic);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 检查设备名称是否合规
|
|
bool _isValidDeviceName(String deviceName) {
|
|
return deviceName.contains(_validDeviceNamePattern);
|
|
}
|
|
|
|
/// 获取Android SDK版本
|
|
Future<int> _getAndroidSdkVersion() async {
|
|
// 简化处理,实际应该通过platform channel获取
|
|
return 31;
|
|
}
|
|
|
|
/// 更新UI
|
|
void _updateUI() {
|
|
// 根据状态更新UI文本
|
|
switch (state) {
|
|
case PairingState.init:
|
|
statusDescription.value = '初始化中...';
|
|
break;
|
|
case PairingState.btReady:
|
|
statusDescription.value = '蓝牙已就绪';
|
|
break;
|
|
case PairingState.classicOk:
|
|
if (_currentClassicDevice != null) {
|
|
statusDescription.value = '已连接: ${_currentClassicDevice!.name}';
|
|
}
|
|
break;
|
|
case PairingState.serverVerify:
|
|
statusDescription.value = '正在验证设备...';
|
|
break;
|
|
case PairingState.pairing:
|
|
statusDescription.value = '正在配对...';
|
|
break;
|
|
case PairingState.paired:
|
|
statusDescription.value = '配对成功!';
|
|
break;
|
|
default:
|
|
// 错误状态在_handleErrorState中处理
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// 取消所有异步操作
|
|
void _cancelAllOperations() {
|
|
_stateTimer?.cancel();
|
|
}
|
|
|
|
/// 取消状态计时器
|
|
void _cancelStateTimer() {
|
|
_stateTimer?.cancel();
|
|
}
|
|
|
|
/// 重试配对
|
|
void _retryPairing() {
|
|
if (_currentClassicDevice != null) {
|
|
_transition(PairingState.pairing);
|
|
} else {
|
|
restart();
|
|
}
|
|
}
|
|
|
|
/// 打开应用设置
|
|
void _openAppSettings() {
|
|
AppSettings.openAppSettings();
|
|
}
|
|
|
|
/// 打开蓝牙设置
|
|
void _openBluetoothSettings() {
|
|
if (Platform.isAndroid) {
|
|
AppSettings.openAppSettings(type: AppSettingsType.bluetooth);
|
|
} else {
|
|
AppSettings.openAppSettings();
|
|
}
|
|
}
|
|
|
|
/// 导航到主页
|
|
void _navigateToHome() {
|
|
Logger.i(_tag, '跳转到主页');
|
|
Get.offAllNamed(Routes.home);
|
|
}
|
|
|
|
// ==================== 公共属性 ====================
|
|
|
|
/// 当前连接的经典蓝牙设备
|
|
BluetoothDevice? get currentDevice => _currentClassicDevice;
|
|
|
|
/// 是否可以跳过配对
|
|
bool get canSkip =>
|
|
!isLoading.value &&
|
|
(state == PairingState.exitNoClassic ||
|
|
state == PairingState.exitNotMatch ||
|
|
state == PairingState.exitTimeout);
|
|
|
|
/// 是否显示操作按钮
|
|
bool get showActionButton =>
|
|
state == PairingState.exitNoPermission ||
|
|
state == PairingState.exitBtOff ||
|
|
state == PairingState.exitNoClassic ||
|
|
state == PairingState.exitNotMatch ||
|
|
state == PairingState.exitTimeout ||
|
|
state == PairingState.exitPairFailed ||
|
|
state == PairingState.exitBound ||
|
|
state == PairingState.exitIllegal;
|
|
}
|
|
|