Browse Source

ble重构优化

weicu
fdp 1 year ago
parent
commit
d2d5b8614d
  1. 32
      lib/modules/pairing/controllers/pairing_controller.dart
  2. 240
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt
  3. 704
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCompanionManager.kt
  4. 74
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCompanionService.kt
  5. 1
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  6. 54
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt
  7. 12
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BootReceiver.kt

32
lib/modules/pairing/controllers/pairing_controller.dart

@ -659,22 +659,22 @@ class PairingController extends GetxController with WidgetsBindingObserver {
return; return;
} }
Logger.i(_tag, '开始BLE设备关联: $_verifiedDeviceName:$_verifiedDeviceAddress'); // Logger.i(_tag, '开始BLE设备关联: $_verifiedDeviceName:$_verifiedDeviceAddress');
// 调用BLE管理器的关联方法 // // 调用BLE管理器的关联方法
final success = await _bleManager.startDeviceAssociation( // final success = await _bleManager.startDeviceAssociation(
deviceNamePattern: _verifiedDeviceName!, // deviceNamePattern: _verifiedDeviceName!,
macAddress: _verifiedDeviceAddress!, // macAddress: _verifiedDeviceAddress!,
); // );
if (success) { // if (success) {
Logger.i(_tag, 'BLE设备关联成功'); // Logger.i(_tag, 'BLE设备关联成功');
// 关联成功后,转到等待BLE连接状态 // 关联成功后,转到等待BLE连接状态
_transition(PairingState.waitingBle); _transition(PairingState.waitingBle);
} else { // } else {
_setError(ErrorType.associationFailed, // _setError(ErrorType.associationFailed,
'bleDeviceAssociationFailed'.tr); //BLE设备关联失败 // 'bleDeviceAssociationFailed'.tr); //BLE设备关联失败
} // }
} catch (e) { } catch (e) {
Logger.e(_tag, 'BLE设备关联异常: $e'); Logger.e(_tag, 'BLE设备关联异常: $e');
_setError(ErrorType.associationFailed, _setError(ErrorType.associationFailed,

240
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt

@ -8,8 +8,10 @@ import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.AssetManager
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.LocaleList
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import android.util.Log import android.util.Log
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -21,6 +23,9 @@ import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import com.yunqiinnovation.ble_service.BleConst import com.yunqiinnovation.ble_service.BleConst
import com.yunqiinnovation.ble_service.BleService import com.yunqiinnovation.ble_service.BleService
import java.io.IOException
import java.util.Locale
import org.json.JSONObject
/** /**
* BLE代理服务 * BLE代理服务
@ -28,6 +33,7 @@ import com.yunqiinnovation.ble_service.BleService
* 1. 语音代理初始化和管理 * 1. 语音代理初始化和管理
* 2. BLE设备连接管理 * 2. BLE设备连接管理
* 3. 后台扫描结果处理 * 3. 后台扫描结果处理
* 4. 自主配置获取和管理
*/ */
class BleMediaService : Service(), CoroutineScope { class BleMediaService : Service(), CoroutineScope {
@ -48,12 +54,12 @@ class BleMediaService : Service(), CoroutineScope {
private const val KEY_MCP_SERVER = "mcpServer" private const val KEY_MCP_SERVER = "mcpServer"
private const val KEY_VOLCANO_APP_ID = "volcanoAppId" private const val KEY_VOLCANO_APP_ID = "volcanoAppId"
private const val KEY_VOLCANO_TOKEN = "volcanoToken" private const val KEY_VOLCANO_TOKEN = "volcanoToken"
private const val KEY_SERVER_URL = "serverurl" private const val KEY_SERVER_URL = "serverurl"
private const val KEY_USER_TOKEN = "usertoken" private const val KEY_USER_TOKEN = "usertoken"
private const val KEY_SUPPORTED_LANGUAGES = "supportedLanguages" private const val KEY_SUPPORTED_LANGUAGES = "supportedLanguages"
private const val KEY_MUSICSERVICE_TYPE = "musicServiceType" private const val KEY_MUSICSERVICE_TYPE = "musicServiceType"
private const val APP_NAVIGATION_MODE = "navigationMode" private const val APP_NAVIGATION_MODE = "navigationMode"
// 用于跟踪服务是否正在运行 // 用于跟踪服务是否正在运行
private val isRunning = AtomicBoolean(false) private val isRunning = AtomicBoolean(false)
@ -72,9 +78,6 @@ class BleMediaService : Service(), CoroutineScope {
// 语音代理是否已初始化 // 语音代理是否已初始化
private var isChatAgentInitialized = false private var isChatAgentInitialized = false
// 当前启动Intent
private var currentIntent: Intent? = null
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -90,9 +93,6 @@ class BleMediaService : Service(), CoroutineScope {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i(TAG, "BleAgentService.onStartCommand: ${intent?.action}") Log.i(TAG, "BleAgentService.onStartCommand: ${intent?.action}")
// 保存当前Intent,用于后续初始化
currentIntent = intent
// 初始化语音代理 // 初始化语音代理
if (!isChatAgentInitialized) { if (!isChatAgentInitialized) {
initializeChatAgent() initializeChatAgent()
@ -113,24 +113,14 @@ class BleMediaService : Service(), CoroutineScope {
/** /**
* 初始化语音交互代理 * 初始化语音交互代理
* 自主获取配置参数,不依赖Flutter层传入
*/ */
private fun initializeChatAgent() { private fun initializeChatAgent() {
try { try {
// 获取SharedPreferences Log.i(TAG, "开始自主获取配置参数...")
val prefs = applicationContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
// 尝试从Intent中获取参数
val configFromIntent = extractConfigFromIntent(currentIntent)
// 如果Intent中有参数,则保存到SharedPreferences
if (configFromIntent.isNotEmpty()) {
Log.d(TAG, "从Intent中获取到配置参数,保存到SharedPreferences")
saveConfigToPreferences(configFromIntent, prefs)
}
// 合并Intent和SharedPreferences中的参数 // 获取配置参数
val config = getConfigFromPreferences(prefs) val config = getServiceConfig()
Log.d(TAG, "当前配置参数: $config")
// 检查必要的配置是否存在 // 检查必要的配置是否存在
val azureSpeechKey = config[KEY_AZURE_SPEECH_KEY]?.toString() ?: "" val azureSpeechKey = config[KEY_AZURE_SPEECH_KEY]?.toString() ?: ""
@ -138,6 +128,8 @@ class BleMediaService : Service(), CoroutineScope {
if (azureSpeechKey.isEmpty() || openaiApiKey.isEmpty()) { if (azureSpeechKey.isEmpty() || openaiApiKey.isEmpty()) {
Log.w(TAG, "语音代理初始化失败: API密钥未配置") Log.w(TAG, "语音代理初始化失败: API密钥未配置")
Log.w(TAG, "Azure Speech Key: ${if (azureSpeechKey.isEmpty()) "未配置" else "已配置"}")
Log.w(TAG, "OpenAI API Key: ${if (openaiApiKey.isEmpty()) "未配置" else "已配置"}")
return return
} }
@ -148,7 +140,7 @@ class BleMediaService : Service(), CoroutineScope {
if(config.isNotEmpty()) { if(config.isNotEmpty()) {
// 初始化ChatAgent // 初始化ChatAgent
Log.i(TAG, "开始初始化ChatAgent") Log.i(TAG, "开始初始化ChatAgent,配置参数数量: ${config.size}")
val success = BleAgent.initialize(applicationContext, config) val success = BleAgent.initialize(applicationContext, config)
if (success) { if (success) {
isChatAgentInitialized = true isChatAgentInitialized = true
@ -163,98 +155,145 @@ class BleMediaService : Service(), CoroutineScope {
} }
/** /**
* 从Intent中提取配置参数 * 自主获取服务配置
* 从多个源获取配置参数:SharedPreferences、Assets、系统设置等
*/ */
private fun extractConfigFromIntent(intent: Intent?): Map<String, Any> { private fun getServiceConfig(): Map<String, Any> {
val config = mutableMapOf<String, Any>() val config = mutableMapOf<String, Any>()
intent?.let {
// 提取可能存在的配置参数 try {
it.getStringExtra(KEY_AZURE_SPEECH_KEY)?.let { value -> config[KEY_AZURE_SPEECH_KEY] = value } // 1. 从SharedPreferences获取基础配置
it.getStringExtra(KEY_AZURE_SPEECH_REGION)?.let { value -> config[KEY_AZURE_SPEECH_REGION] = value } val prefs = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
it.getStringExtra(KEY_OPENAI_API_KEY)?.let { value -> config[KEY_OPENAI_API_KEY] = value }
it.getStringExtra(KEY_OPENAI_BASE_URL)?.let { value -> config[KEY_OPENAI_BASE_URL] = value } // 添加字符串类型的配置参数(使用默认值)
it.getStringExtra(KEY_OPENAI_MODEL)?.let { value -> config[KEY_OPENAI_MODEL] = value } config[KEY_AZURE_SPEECH_KEY] = getConfigString(prefs, KEY_AZURE_SPEECH_KEY, "")
it.getStringExtra(KEY_OPENAI_VISION_MODEL)?.let { value -> config[KEY_OPENAI_VISION_MODEL] = value } config[KEY_AZURE_SPEECH_REGION] = getConfigString(prefs, KEY_AZURE_SPEECH_REGION, "eastasia")
it.getStringExtra(KEY_SYSTEM_PROMPT)?.let { value -> config[KEY_SYSTEM_PROMPT] = value } config[KEY_OPENAI_API_KEY] = getConfigString(prefs, KEY_OPENAI_API_KEY, "")
it.getStringExtra(KEY_MCP_SERVER)?.let { value -> config[KEY_MCP_SERVER] = value } config[KEY_OPENAI_BASE_URL] = getConfigString(prefs, KEY_OPENAI_BASE_URL, "https://api.openai.com/v1")
it.getStringExtra(KEY_VOLCANO_APP_ID)?.let { value -> config[KEY_VOLCANO_APP_ID] = value } config[KEY_OPENAI_MODEL] = getConfigString(prefs, KEY_OPENAI_MODEL, "gpt-3.5-turbo")
it.getStringExtra(KEY_VOLCANO_TOKEN)?.let { value -> config[KEY_VOLCANO_TOKEN] = value } config[KEY_OPENAI_VISION_MODEL] = getConfigString(prefs, KEY_OPENAI_VISION_MODEL, "gpt-4-vision-preview")
it.getStringExtra(KEY_SERVER_URL)?.let { value -> config[KEY_SERVER_URL] = value } config[KEY_SYSTEM_PROMPT] = getConfigString(prefs, KEY_SYSTEM_PROMPT, "")
it.getStringExtra(KEY_USER_TOKEN)?.let { value -> config[KEY_USER_TOKEN] = value } config[KEY_VOLCANO_APP_ID] = getConfigString(prefs, KEY_VOLCANO_APP_ID, "2316081933")
it.getStringExtra(KEY_MUSICSERVICE_TYPE)?.let { value -> config[KEY_MUSICSERVICE_TYPE] = value } config[KEY_VOLCANO_TOKEN] = getConfigString(prefs, KEY_VOLCANO_TOKEN, "mcJTh6OelBqU_L78lgGd77Nyrc_-5-En")
it.getStringExtra(APP_NAVIGATION_MODE)?.let { value -> config[APP_NAVIGATION_MODE] = value } config[KEY_SERVER_URL] = getConfigString(prefs, KEY_SERVER_URL, "")
// 提取 supportedLanguages 参数 config[KEY_USER_TOKEN] = getConfigString(prefs, KEY_USER_TOKEN, "")
it.getStringArrayListExtra(KEY_SUPPORTED_LANGUAGES)?.let { languages -> config[KEY_MUSICSERVICE_TYPE] = getConfigString(prefs, KEY_MUSICSERVICE_TYPE, "AUTO")
if (languages.isNotEmpty()) { config[APP_NAVIGATION_MODE] = getConfigString(prefs, APP_NAVIGATION_MODE, "external_auto")
config[KEY_SUPPORTED_LANGUAGES] = languages
} // 2. 从Assets获取MCP配置
val mcpConfig = loadMcpConfigFromAssets()
if (mcpConfig.isNotEmpty()) {
config[KEY_MCP_SERVER] = mcpConfig
Log.d(TAG, "成功从Assets加载MCP配置")
} else {
Log.w(TAG, "未能从Assets加载MCP配置,使用空配置")
config[KEY_MCP_SERVER] = "{\"mcpServers\":{}}"
} }
// 3. 获取系统语言配置
val supportedLanguages = getSystemSupportedLanguages()
config[KEY_SUPPORTED_LANGUAGES] = supportedLanguages
Log.d(TAG, "系统支持的语言: ${supportedLanguages.joinToString(", ")}")
Log.d(TAG, "配置获取完成,参数数量: ${config.size}")
} catch (e: Exception) {
Log.e(TAG, "获取服务配置时发生异常", e)
} }
Log.i(TAG, "从Intent中提取到配置参数: $config")
return config return config
} }
/** /**
* 将配置参数保存到SharedPreferences * 从Assets目录加载MCP配置文件
*/ */
private fun saveConfigToPreferences(config: Map<String, Any>, prefs: SharedPreferences) { private fun loadMcpConfigFromAssets(): String {
prefs.edit().apply { return try {
config.forEach { (key, value) -> val assetManager: AssetManager = assets
when (value) { val inputStream = assetManager.open(".mcp.json")
is String -> { val size = inputStream.available()
if (value.isNotEmpty()) { val buffer = ByteArray(size)
putString(key, value) inputStream.read(buffer)
} inputStream.close()
} val jsonString = String(buffer, Charsets.UTF_8)
is List<*> -> { Log.d(TAG, "成功从Assets加载.mcp.json文件")
// 处理 supportedLanguages 列表 jsonString
if (key == KEY_SUPPORTED_LANGUAGES) { } catch (e: IOException) {
val languageList = value.filterIsInstance<String>() Log.w(TAG, "无法从Assets加载.mcp.json文件: ${e.message}")
if (languageList.isNotEmpty()) { // 返回默认的空MCP配置
// 将语言列表转换为逗号分隔的字符串保存 "{\"mcpServers\":{}}"
putString(key, languageList.joinToString(","))
}
}
}
}
}
apply()
} }
} }
/** /**
* 从SharedPreferences获取配置 * 获取系统支持的语言列表
* 根据系统当前语言设置动态配置语音识别语言
*/ */
private fun getConfigFromPreferences(prefs: SharedPreferences): Map<String, Any> { private fun getSystemSupportedLanguages(): List<String> {
val config = mutableMapOf<String, Any>() val supportedLanguages = mutableListOf<String>()
// 添加字符串类型的配置参数 try {
config[KEY_AZURE_SPEECH_KEY] = getConfigString(prefs, KEY_AZURE_SPEECH_KEY, "") // 获取系统当前语言设置
config[KEY_AZURE_SPEECH_REGION] = getConfigString(prefs, KEY_AZURE_SPEECH_REGION, "eastasia") val currentLocale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config[KEY_OPENAI_API_KEY] = getConfigString(prefs, KEY_OPENAI_API_KEY, "") LocaleList.getDefault().get(0)
config[KEY_OPENAI_BASE_URL] = getConfigString(prefs, KEY_OPENAI_BASE_URL, "https://api.openai.com/v1") } else {
config[KEY_OPENAI_MODEL] = getConfigString(prefs, KEY_OPENAI_MODEL, "gpt-3.5-turbo") @Suppress("DEPRECATION")
config[KEY_OPENAI_VISION_MODEL] = getConfigString(prefs, KEY_OPENAI_VISION_MODEL, "gpt-4-vision-preview") Locale.getDefault()
config[KEY_SYSTEM_PROMPT] = getConfigString(prefs, KEY_SYSTEM_PROMPT, "gpt-3.5-turbo") }
config[KEY_MCP_SERVER] = getConfigString(prefs, KEY_MCP_SERVER, "")
config[KEY_VOLCANO_APP_ID] = getConfigString(prefs, KEY_VOLCANO_APP_ID, "") val language = currentLocale.language
config[KEY_VOLCANO_TOKEN] = getConfigString(prefs, KEY_VOLCANO_TOKEN, "") val country = currentLocale.country
config[KEY_SERVER_URL] = getConfigString(prefs, KEY_SERVER_URL, "")
config[KEY_USER_TOKEN] = getConfigString(prefs, KEY_USER_TOKEN, "") // 构造Azure Speech SDK需要的语言格式
config[KEY_MUSICSERVICE_TYPE] = getConfigString(prefs, KEY_MUSICSERVICE_TYPE, "") when (language) {
config[APP_NAVIGATION_MODE] = getConfigString(prefs, APP_NAVIGATION_MODE, "") "zh" -> {
when (country) {
// 添加 supportedLanguages 参数处理 "CN" -> supportedLanguages.add("zh-CN")
val supportedLanguagesStr = getConfigString(prefs, KEY_SUPPORTED_LANGUAGES, "") "TW" -> supportedLanguages.add("zh-TW")
if (supportedLanguagesStr.isNotEmpty()) { "HK" -> supportedLanguages.add("zh-HK")
// 将逗号分隔的字符串转换回语言列表 else -> supportedLanguages.add("zh-CN") // 默认简体中文
val languageList = supportedLanguagesStr.split(",").map { it.trim() }.filter { it.isNotEmpty() } }
if (languageList.isNotEmpty()) { }
config[KEY_SUPPORTED_LANGUAGES] = languageList "en" -> {
when (country) {
"US" -> supportedLanguages.add("en-US")
"GB" -> supportedLanguages.add("en-GB")
"AU" -> supportedLanguages.add("en-AU")
"CA" -> supportedLanguages.add("en-CA")
else -> supportedLanguages.add("en-US") // 默认美式英语
}
}
"ja" -> supportedLanguages.add("ja-JP")
"ko" -> supportedLanguages.add("ko-KR")
"fr" -> supportedLanguages.add("fr-FR")
"de" -> supportedLanguages.add("de-DE")
"es" -> supportedLanguages.add("es-ES")
"it" -> supportedLanguages.add("it-IT")
"pt" -> supportedLanguages.add("pt-BR")
"ru" -> supportedLanguages.add("ru-RU")
else -> {
// 对于不支持的语言,添加通用格式
if (country.isNotEmpty()) {
supportedLanguages.add("$language-$country")
} else {
supportedLanguages.add("en-US") // 最终兜底
}
}
}
// 确保至少有一个语言
if (supportedLanguages.isEmpty()) {
supportedLanguages.add("en-US")
} }
Log.d(TAG, "检测到系统语言: $language-$country,配置语音识别语言: ${supportedLanguages.joinToString(", ")}")
} catch (e: Exception) {
Log.e(TAG, "获取系统语言配置失败,使用默认语言: $e")
supportedLanguages.add("en-US")
} }
return config return supportedLanguages
} }
/** /**
@ -278,6 +317,9 @@ class BleMediaService : Service(), CoroutineScope {
super.onDestroy() super.onDestroy()
} }
/**
* 检查BLE连接状态并尝试连接
*/
private fun checkBleConnection() { private fun checkBleConnection() {
Log.i(TAG, "checkBleConnection收到连接请求") Log.i(TAG, "checkBleConnection收到连接请求")
@ -335,4 +377,4 @@ class BleMediaService : Service(), CoroutineScope {
return builder.build() return builder.build()
} }
} }

704
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCompanionManager.kt

@ -34,6 +34,7 @@ class BleCompanionManager(private val context: Context) {
private const val PREF_NAME = "ble_companion_prefs" private const val PREF_NAME = "ble_companion_prefs"
private const val KEY_ASSOCIATION_IDS = "association_ids" private const val KEY_ASSOCIATION_IDS = "association_ids"
private const val KEY_PAIRED_MAC = "paired_mac" private const val KEY_PAIRED_MAC = "paired_mac"
private const val KEY_PAIRED_UUID = "paired_uuid"
} }
// 会话保存 // 会话保存
@ -43,7 +44,21 @@ class BleCompanionManager(private val context: Context) {
// 用于验证的临时MAC地址 // 用于验证的临时MAC地址
private var pendingMacAddress: String? = null private var pendingMacAddress: String? = null
// 创建PendingIntent
// 创建PendingIntent
val scanIntent = Intent(BleConst.ACTION_SCAN_RESULT)
.setClass(context, BleWakeReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
scanIntent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
)
/** /**
* 获取CompanionDeviceManager服务(使用Activity上下文) * 获取CompanionDeviceManager服务(使用Activity上下文)
*/ */
@ -63,313 +78,344 @@ class BleCompanionManager(private val context: Context) {
} }
/** /**
* 获取已关联的设备MAC地址 * 获取已连接的设备MAC地址
*/ */
fun getPairedMacAddress(): String? { fun getPairedMacAddress(): String? {
return sharedPreferences.getString(KEY_PAIRED_MAC, null) return sharedPreferences.getString(KEY_PAIRED_MAC, null)
} }
/**
/** * 保存连接的设备UUID
* 保存已关联的设备MAC地址
*/
fun savePairedMacAddress(macAddress: String) {
sharedPreferences.edit().putString(KEY_PAIRED_MAC, macAddress).apply()
Log.i(TAG, "已保存配对设备MAC: $macAddress")
}
/**
* 清除已关联的设备MAC地址
*/ */
fun clearPairedMacAddress() {
sharedPreferences.edit().remove(KEY_PAIRED_MAC).apply()
Log.i(TAG, "已清除配对设备MAC")
}
/**
* 检查是否有已关联的设备
*/
@RequiresApi(Build.VERSION_CODES.O)
fun hasAssociations(): Boolean {
val associationIds = getAssociationIds()
return associationIds.isNotEmpty()
}
/** /**
* 获取所有关联ID * 保存连接的设备UUID列表
* 将UUID列表转换为JSON字符串进行存储
*/ */
@RequiresApi(Build.VERSION_CODES.O) fun savePairedUuid(uuids: List<String>) {
fun getAssociationIds(): List<String> { if (uuids.isNotEmpty()) {
// 使用默认上下文获取CompanionDeviceManager // 将列表转换为JSON字符串格式进行存储
val companionDeviceManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val uuidsJson = uuids.joinToString(",") { "\"$it\"" }
context.getSystemService(Context.COMPANION_DEVICE_SERVICE) as? CompanionDeviceManager val jsonString = "[$uuidsJson]"
} else { sharedPreferences.edit().putString(KEY_PAIRED_UUID, jsonString).apply()
null Log.i(TAG, "已保存连接设备UUID列表: $uuids")
} }
return getAssociationIds(companionDeviceManager)
} }
/** /**
* 获取所有关联ID(使用指定的CompanionDeviceManager) * 获取已连接的设备UUID列表
* 从JSON字符串解析为UUID列表
*/ */
@RequiresApi(Build.VERSION_CODES.O) fun getPairedUuid(): List<String> {
private fun getAssociationIds(companionDeviceManager: CompanionDeviceManager?): List<String> { val jsonString = sharedPreferences.getString(KEY_PAIRED_UUID, null)
if (companionDeviceManager == null) { return if (!jsonString.isNullOrEmpty()) {
return emptyList()
}
// 注意: allAssociations 仅在API 33 (Android 13)及以上版本可用
// 在早期版本中,使用associations (返回的是String数组)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
try { try {
// 由于AssociationInfo类在API 30+引入,使用反射获取更安全 // 简单的JSON解析,移除方括号和引号,然后分割
val method = CompanionDeviceManager::class.java.getMethod("getAllAssociations") jsonString.trim('[', ']')
@Suppress("UNCHECKED_CAST") .split(",")
val associations = method.invoke(companionDeviceManager) as? List<Any> .map { it.trim().trim('"') }
associations?.map { association -> .filter { it.isNotEmpty() }
// 尝试获取AssociationInfo.id
val idField = association.javaClass.getField("id")
idField.get(association).toString()
} ?: emptyList()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "获取关联ID失败: ${e.message}", e) Log.e(TAG, "解析UUID列表失败: ${e.message}")
emptyList() emptyList()
} }
} else { } else {
// API 26-32 使用 associations 方法 emptyList()
companionDeviceManager.associations?.toList() ?: emptyList()
} }
} }
/**
* 保存连接的设备MAC地址
*/
fun savePairedMacAddress(macAddress: String) {
sharedPreferences.edit().putString(KEY_PAIRED_MAC, macAddress).apply()
Log.i(TAG, "已保存连接设备MAC: $macAddress")
}
/** /**
* 开始设备关联流程 * 清除已连接的设备MAC地址
*
* @param activity 当前Activity,用于启动系统关联对话框
* @param deviceNamePattern 设备名称正则表达式,例如"MyHeadset.*"
* @param serviceUuid 可选的服务UUID
* @param macAddress 可选的MAC地址
*/ */
@RequiresApi(Build.VERSION_CODES.O) fun clearPairedMacAddress() {
fun startAssociation( sharedPreferences.edit().remove(KEY_PAIRED_MAC).apply()
activity: Activity, Log.i(TAG, "已清除连接设备MAC")
deviceNamePattern: String? = null, }
serviceUuid: String? = null,
macAddress: String? = null // /**
): Boolean { // * 检查是否有已关联的设备
// 使用Activity上下文获取CompanionDeviceManager // */
val companionDeviceManager = getCompanionDeviceManager(activity) // @RequiresApi(Build.VERSION_CODES.O)
if (companionDeviceManager == null) { // fun hasAssociations(): Boolean {
Log.e(TAG, "设备不支持CompanionDeviceManager") // val associationIds = getAssociationIds()
return false // return associationIds.isNotEmpty()
} // }
// /**
// * 获取所有关联ID
// */
// @RequiresApi(Build.VERSION_CODES.O)
// fun getAssociationIds(): List<String> {
// // 使用默认上下文获取CompanionDeviceManager
// val companionDeviceManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// context.getSystemService(Context.COMPANION_DEVICE_SERVICE) as? CompanionDeviceManager
// } else {
// null
// }
// return getAssociationIds(companionDeviceManager)
// }
// /**
// * 获取所有关联ID(使用指定的CompanionDeviceManager)
// */
// @RequiresApi(Build.VERSION_CODES.O)
// private fun getAssociationIds(companionDeviceManager: CompanionDeviceManager?): List<String> {
// if (companionDeviceManager == null) {
// return emptyList()
// }
try { // // 注意: allAssociations 仅在API 33 (Android 13)及以上版本可用
// 构建设备过滤器 // // 在早期版本中,使用associations (返回的是String数组)
val deviceFilterBuilder = BluetoothDeviceFilter.Builder() // return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// try {
// // 由于AssociationInfo类在API 30+引入,使用反射获取更安全
// val method = CompanionDeviceManager::class.java.getMethod("getAllAssociations")
// @Suppress("UNCHECKED_CAST")
// val associations = method.invoke(companionDeviceManager) as? List<Any>
// associations?.map { association ->
// // 尝试获取AssociationInfo.id
// val idField = association.javaClass.getField("id")
// idField.get(association).toString()
// } ?: emptyList()
// } catch (e: Exception) {
// Log.e(TAG, "获取关联ID失败: ${e.message}", e)
// emptyList()
// }
// } else {
// // API 26-32 使用 associations 方法
// companionDeviceManager.associations?.toList() ?: emptyList()
// }
// }
// /**
// * 开始设备关联流程
// *
// * @param activity 当前Activity,用于启动系统关联对话框
// * @param deviceNamePattern 设备名称正则表达式,例如"MyHeadset.*"
// * @param serviceUuid 可选的服务UUID
// * @param macAddress 可选的MAC地址
// */
// @RequiresApi(Build.VERSION_CODES.O)
// fun startAssociation(
// activity: Activity,
// deviceNamePattern: String? = null,
// serviceUuid: String? = null,
// macAddress: String? = null
// ): Boolean {
// // 使用Activity上下文获取CompanionDeviceManager
// val companionDeviceManager = getCompanionDeviceManager(activity)
// if (companionDeviceManager == null) {
// Log.e(TAG, "设备不支持CompanionDeviceManager")
// return false
// }
// try {
// // 构建设备过滤器
// val deviceFilterBuilder = BluetoothDeviceFilter.Builder()
// 添加设备名称 - 严格匹配模式 // // 添加设备名称 - 严格匹配模式
deviceNamePattern?.let { // deviceNamePattern?.let {
// 使用"^设备名称$"格式创建精确匹配的正则表达式 // // 使用"^设备名称$"格式创建精确匹配的正则表达式
val exactNamePattern = Pattern.compile("^$it$") // val exactNamePattern = Pattern.compile("^$it$")
deviceFilterBuilder.setNamePattern(exactNamePattern) // deviceFilterBuilder.setNamePattern(exactNamePattern)
Log.i(TAG, "设置严格名称匹配: $it") // Log.i(TAG, "设置严格名称匹配: $it")
} // }
// 添加服务UUID // // 添加服务UUID
serviceUuid?.let { // serviceUuid?.let {
try { // try {
val uuid = ParcelUuid(java.util.UUID.fromString(it)) // val uuid = ParcelUuid(java.util.UUID.fromString(it))
deviceFilterBuilder.addServiceUuid(uuid, null) // deviceFilterBuilder.addServiceUuid(uuid, null)
Log.i(TAG, "设置服务UUID匹配: $it") // Log.i(TAG, "设置服务UUID匹配: $it")
} catch (e: Exception) { // } catch (e: Exception) {
Log.e(TAG, "设置服务UUID失败: ${e.message}", e) // Log.e(TAG, "设置服务UUID失败: ${e.message}", e)
} // }
} // }
// 添加MAC地址 - 严格匹配 // // 添加MAC地址 - 严格匹配
macAddress?.let { // macAddress?.let {
try { // try {
// 检查是否是有效的MAC地址格式 // // 检查是否是有效的MAC地址格式
if (!isValidMacAddress(it)) { // if (!isValidMacAddress(it)) {
Log.e(TAG, "无效的MAC地址格式: $it") // Log.e(TAG, "无效的MAC地址格式: $it")
return false // return false
} // }
// 存储MAC地址到临时变量,成功匹配后再保存 // // 存储MAC地址到临时变量,成功匹配后再保存
pendingMacAddress = it // pendingMacAddress = it
// 设置MAC地址严格匹配 // // 设置MAC地址严格匹配
deviceFilterBuilder.setAddress(it) // deviceFilterBuilder.setAddress(it)
Log.i(TAG, "设置MAC地址严格匹配: $it") // Log.i(TAG, "设置MAC地址严格匹配: $it")
} catch (e: Exception) { // } catch (e: Exception) {
Log.e(TAG, "设置MAC地址失败: ${e.message}", e) // Log.e(TAG, "设置MAC地址失败: ${e.message}", e)
return false // return false
} // }
} ?: run { // } ?: run {
// 如果没有提供MAC地址也没有提供设备名称,则返回错误 // // 如果没有提供MAC地址也没有提供设备名称,则返回错误
if (deviceNamePattern == null && serviceUuid == null) { // if (deviceNamePattern == null && serviceUuid == null) {
Log.e(TAG, "严格匹配模式下必须提供MAC地址、设备名称或服务UUID之一") // Log.e(TAG, "严格匹配模式下必须提供MAC地址、设备名称或服务UUID之一")
return false // return false
} // }
} // }
// 创建请求 // // 创建请求
val associationRequest = AssociationRequest.Builder() // val associationRequest = AssociationRequest.Builder()
.addDeviceFilter(deviceFilterBuilder.build()) // .addDeviceFilter(deviceFilterBuilder.build())
.setSingleDevice(true) // 找到一个即停止 // .setSingleDevice(true) // 找到一个即停止
.build() // .build()
// 发起关联请求 // // 发起关联请求
companionDeviceManager.associate( // companionDeviceManager.associate(
associationRequest, // associationRequest,
object : CompanionDeviceManager.Callback() { // object : CompanionDeviceManager.Callback() {
override fun onDeviceFound(intentSender: IntentSender) { // override fun onDeviceFound(intentSender: IntentSender) {
try { // try {
activity.startIntentSenderForResult( // activity.startIntentSenderForResult(
intentSender, // intentSender,
REQUEST_CODE_ASSOCIATE, // REQUEST_CODE_ASSOCIATE,
null, // null,
0, // 0,
0, // 0,
0 // 0
) // )
Log.i(TAG, "已启动设备选择对话框") // Log.i(TAG, "已启动设备选择对话框")
} catch (e: Exception) { // } catch (e: Exception) {
Log.e(TAG, "启动设备选择对话框失败: ${e.message}", e) // Log.e(TAG, "启动设备选择对话框失败: ${e.message}", e)
} // }
} // }
// API 26 (Android 8) 使用 onDeviceFound // // API 26 (Android 8) 使用 onDeviceFound
// API 28+ (Android 9+) 使用 onAssociationPending // // API 28+ (Android 9+) 使用 onAssociationPending
@RequiresApi(Build.VERSION_CODES.P) // @RequiresApi(Build.VERSION_CODES.P)
override fun onAssociationPending(intentSender: IntentSender) { // override fun onAssociationPending(intentSender: IntentSender) {
try { // try {
activity.startIntentSenderForResult( // activity.startIntentSenderForResult(
intentSender, // intentSender,
REQUEST_CODE_ASSOCIATE, // REQUEST_CODE_ASSOCIATE,
null, // null,
0, // 0,
0, // 0,
0 // 0
) // )
Log.i(TAG, "已启动设备选择对话框") // Log.i(TAG, "已启动设备选择对话框")
} catch (e: Exception) { // } catch (e: Exception) {
Log.e(TAG, "启动设备选择对话框失败: ${e.message}", e) // Log.e(TAG, "启动设备选择对话框失败: ${e.message}", e)
} // }
} // }
// API 33+ (Android 13+) 使用 onAssociationCreated // // API 33+ (Android 13+) 使用 onAssociationCreated
@RequiresApi(Build.VERSION_CODES.TIRAMISU) // @RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onAssociationCreated(associationInfo: AssociationInfo) { // override fun onAssociationCreated(associationInfo: AssociationInfo) {
Log.i(TAG, "设备关联已创建: ${associationInfo.displayName}, ID: ${associationInfo.id}") // Log.i(TAG, "设备关联已创建: ${associationInfo.displayName}, ID: ${associationInfo.id}")
// 保存关联ID // // 保存关联ID
try { // try {
val macAddressMethod = associationInfo.javaClass.getMethod("getDeviceMacAddress") // val macAddressMethod = associationInfo.javaClass.getMethod("getDeviceMacAddress")
val mac = macAddressMethod.invoke(associationInfo) as? String // val mac = macAddressMethod.invoke(associationInfo) as? String
mac?.let { savePairedMacAddress(it) } // mac?.let { savePairedMacAddress(it) }
} catch (e: Exception) { // } catch (e: Exception) {
Log.e(TAG, "获取设备MAC地址失败: ${e.message}", e) // Log.e(TAG, "获取设备MAC地址失败: ${e.message}", e)
} // }
} // }
override fun onFailure(error: CharSequence?) { // override fun onFailure(error: CharSequence?) {
Log.e(TAG, "设备关联失败: $error") // Log.e(TAG, "设备关联失败: $error")
} // }
}, // },
null // null
) // )
return true // return true
} catch (e: Exception) { // } catch (e: Exception) {
Log.e(TAG, "启动设备关联失败: ${e.message}", e) // Log.e(TAG, "启动设备关联失败: ${e.message}", e)
return false // return false
} // }
} // }
/** // /**
* 处理关联结果 // * 处理关联结果
* // *
* @param requestCode 请求码 // * @param requestCode 请求码
* @param resultCode 结果码 // * @param resultCode 结果码
* @param data Intent数据 // * @param data Intent数据
* @return 是否处理了关联结果 // * @return 是否处理了关联结果
*/ // */
@RequiresApi(Build.VERSION_CODES.O) // @RequiresApi(Build.VERSION_CODES.O)
fun handleAssociationResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { // fun handleAssociationResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
if (requestCode != REQUEST_CODE_ASSOCIATE) { // if (requestCode != REQUEST_CODE_ASSOCIATE) {
return false // return false
} // }
Log.i(TAG, "收到关联结果: resultCode=$resultCode") // Log.i(TAG, "收到关联结果: resultCode=$resultCode")
if (resultCode == Activity.RESULT_OK && data != null) { // if (resultCode == Activity.RESULT_OK && data != null) {
// 获取选中的蓝牙设备 // // 获取选中的蓝牙设备
val bluetoothDevice: BluetoothDevice? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // val bluetoothDevice: BluetoothDevice? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE, BluetoothDevice::class.java) // data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE, BluetoothDevice::class.java)
} else { // } else {
@Suppress("DEPRECATION") // @Suppress("DEPRECATION")
data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE) // data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
} // }
// 获取关联ID (Android 13+) // // 获取关联ID (Android 13+)
val associationId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // val associationId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
try { // try {
// 由于EXTRA_ASSOCIATION_ID常量仅在API 33+可用,直接使用已知的常量字符串值 // // 由于EXTRA_ASSOCIATION_ID常量仅在API 33+可用,直接使用已知的常量字符串值
// 不再尝试使用反射获取字段 // // 不再尝试使用反射获取字段
val extraAssociationIdKey = "android.companion.extra.ASSOCIATION_ID" // val extraAssociationIdKey = "android.companion.extra.ASSOCIATION_ID"
data.getStringExtra(extraAssociationIdKey) // data.getStringExtra(extraAssociationIdKey)
} catch (e: Exception) { // } catch (e: Exception) {
Log.e(TAG, "获取关联ID失败: ${e.message}", e) // Log.e(TAG, "获取关联ID失败: ${e.message}", e)
null // null
} // }
} else { // } else {
null // null
} // }
// 获取并验证MAC地址 // bluetoothDevice?.let { device ->
if (bluetoothDevice != null) { // val deviceMac = device.address
val deviceMac = bluetoothDevice.address // Log.i(TAG, "设备关联成功: ${device.name} ($deviceMac)")
val deviceName = bluetoothDevice.name ?: "未知设备"
// 验证MAC地址格式 // // 验证MAC地址是否匹配预期
if (!isValidMacAddress(deviceMac)) { // pendingMacAddress?.let { expectedMac ->
Log.e(TAG, "设备 '$deviceName' 返回的MAC地址格式无效: $deviceMac") // if (deviceMac.equals(expectedMac, ignoreCase = true)) {
return false // Log.i(TAG, "MAC地址验证成功: $deviceMac")
} // } else {
// Log.w(TAG, "MAC地址不匹配: 预期=$expectedMac, 实际=$deviceMac")
// 检查是否与预期的MAC地址匹配 // }
if (pendingMacAddress != null && pendingMacAddress != deviceMac) { // }
Log.w(TAG, "选中的设备MAC地址 ($deviceMac) 与预期的MAC地址 ($pendingMacAddress) 不匹配")
// 如果要严格匹配MAC,可以在这里返回false
return false
}
// 匹配成功后,保存新的MAC地址 // // 保存设备MAC地址
savePairedMacAddress(deviceMac) // savePairedMacAddress(deviceMac)
Log.i(TAG, "设备关联成功: $deviceName ($deviceMac), 关联ID: $associationId")
// 清除临时MAC地址 // // 保存关联ID (如果可用)
pendingMacAddress = null // associationId?.let { id ->
// Log.i(TAG, "保存关联ID: $id")
// // 这里可以保存关联ID到SharedPreferences或其他存储
// }
// 🎯 立即发起BLE连接 // // 清除临时MAC地址
// try { // pendingMacAddress = null
// Log.i(TAG, "配对成功,立即发起BLE连接: $deviceMac")
// BleService.connect(deviceMac)
// } catch (e: Exception) {
// Log.e(TAG, "配对成功后发起BLE连接失败: ${e.message}", e)
// }
return true // return true
} else { // } ?: run {
Log.e(TAG, "无法获取关联的蓝牙设备信息") // Log.e(TAG, "无法获取蓝牙设备信息")
} // return false
} else { // }
Log.w(TAG, "设备关联被取消或失败: resultCode=$resultCode") // } else {
// 清除临时MAC地址 // Log.w(TAG, "设备关联被取消或失败")
pendingMacAddress = null // // 清除临时MAC地址
} // pendingMacAddress = null
// return false
return false // }
} // }
/** /**
* 注册后台扫描 * 注册后台扫描
@ -382,12 +428,14 @@ class BleCompanionManager(private val context: Context) {
) { ) {
try { try {
Log.d(TAG, "添加后台扫描过滤器UUID: $uuids, macAddress: $macAddress") Log.d(TAG, "添加后台扫描过滤器UUID: $uuids, macAddress: $macAddress")
val mac = getPairedMacAddress() //val mac = getPairedMacAddress()
if (mac.isNullOrEmpty()) {
if (macAddress.isNullOrEmpty()||uuids.isNullOrEmpty()) {
Log.e(TAG, "注册后台扫描失败: 没有配对的设备MAC地址") Log.e(TAG, "注册后台扫描失败: 没有配对的设备MAC地址")
return return
} }
savePairedMacAddress(macAddress)
savePairedUuid(uuids)
val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager
val bluetoothAdapter = bluetoothManager?.adapter val bluetoothAdapter = bluetoothManager?.adapter
val bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner val bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner
@ -440,24 +488,24 @@ class BleCompanionManager(private val context: Context) {
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.build() .build()
// 创建PendingIntent // // 创建PendingIntent
val scanIntent = Intent(BleConst.ACTION_SCAN_RESULT) // val scanIntent = Intent(BleConst.ACTION_SCAN_RESULT)
.setClass(context, BleWakeReceiver::class.java) // .setClass(context, BleWakeReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast( // val pendingIntent = PendingIntent.getBroadcast(
context, // context,
0, // 0,
scanIntent, // scanIntent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE // PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
} else { // } else {
PendingIntent.FLAG_UPDATE_CURRENT // PendingIntent.FLAG_UPDATE_CURRENT
} // }
) // )
// 启动后台扫描 // 启动后台扫描
bluetoothLeScanner.startScan(filters, settings, pendingIntent) bluetoothLeScanner.startScan(filters, settings, pendingIntent)
Log.i(TAG, "已注册后台扫描,目标设备MAC: $mac") Log.i(TAG, "已注册后台扫描,目标设备MAC: $macAddress")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "注册后台扫描失败: ${e.message}", e) Log.e(TAG, "注册后台扫描失败: ${e.message}", e)
} }
@ -477,20 +525,20 @@ class BleCompanionManager(private val context: Context) {
return return
} }
// 创建PendingIntent // // 创建PendingIntent
val scanIntent = Intent(BleConst.ACTION_SCAN_RESULT) // val scanIntent = Intent(BleConst.ACTION_SCAN_RESULT)
.setClass(context, BleWakeReceiver::class.java) // .setClass(context, BleWakeReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast( // val pendingIntent = PendingIntent.getBroadcast(
context, // context,
0, // 0,
scanIntent, // scanIntent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE // PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
} else { // } else {
PendingIntent.FLAG_UPDATE_CURRENT // PendingIntent.FLAG_UPDATE_CURRENT
} // }
) // )
// 停止后台扫描 // 停止后台扫描
bluetoothLeScanner.stopScan(pendingIntent) bluetoothLeScanner.stopScan(pendingIntent)
@ -500,42 +548,42 @@ class BleCompanionManager(private val context: Context) {
} }
} }
/** // /**
* 清除所有关联 // * 清除所有关联
*/ // */
@RequiresApi(Build.VERSION_CODES.O) // @RequiresApi(Build.VERSION_CODES.O)
fun clearAllAssociations() { // fun clearAllAssociations() {
try { // try {
// 获取CompanionDeviceManager // // 获取CompanionDeviceManager
val companionDeviceManager = context.getSystemService(Context.COMPANION_DEVICE_SERVICE) as? CompanionDeviceManager // val companionDeviceManager = context.getSystemService(Context.COMPANION_DEVICE_SERVICE) as? CompanionDeviceManager
if (companionDeviceManager == null) { // if (companionDeviceManager == null) {
Log.e(TAG, "无法获取CompanionDeviceManager") // Log.e(TAG, "无法获取CompanionDeviceManager")
return // return
} // }
// 清除本地保存的MAC地址 // // 清除本地保存的MAC地址
clearPairedMacAddress() // clearPairedMacAddress()
// 获取所有关联ID // // 获取所有关联ID
val associationIds = getAssociationIds(companionDeviceManager) // val associationIds = getAssociationIds(companionDeviceManager)
// 遍历解除关联 // // 遍历解除关联
for (id in associationIds) { // for (id in associationIds) {
try { // try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
companionDeviceManager.disassociate(id) // companionDeviceManager.disassociate(id)
} else { // } else {
companionDeviceManager.disassociate(id) // companionDeviceManager.disassociate(id)
} // }
Log.i(TAG, "已解除设备关联: $id") // Log.i(TAG, "已解除设备关联: $id")
} catch (e: Exception) { // } catch (e: Exception) {
Log.e(TAG, "解除设备关联失败: $id, ${e.message}", e) // Log.e(TAG, "解除设备关联失败: $id, ${e.message}", e)
} // }
} // }
} catch (e: Exception) { // } catch (e: Exception) {
Log.e(TAG, "清除所有关联失败: ${e.message}", e) // Log.e(TAG, "清除所有关联失败: ${e.message}", e)
} // }
} // }
/** /**
* 验证是否是有效的MAC地址格式 * 验证是否是有效的MAC地址格式

74
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCompanionService.kt

@ -1,74 +0,0 @@
package com.yunqiinnovation.ble_service
import android.companion.AssociationInfo
import android.companion.CompanionDeviceService
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.annotation.RequiresApi
import android.util.Log
/**
* BleCompanionService
* 用于监听配对设备的出现和消失
* 注:此服务仅在Android 11 (API 30)及以上版本可用
*/
@RequiresApi(Build.VERSION_CODES.R)
class BleCompanionService : CompanionDeviceService() {
private val TAG = "BleCompanionService"
override fun onCreate() {
super.onCreate()
Log.i(TAG, "BleCompanionService已创建")
}
override fun onDeviceAppeared(info: AssociationInfo) {
super.onDeviceAppeared(info)
Log.i(TAG, "检测到配对设备出现: ${info.displayName}, ID: ${info.id}")
// 当配对设备出现时,启动前台服务
try {
val serviceIntent = Intent().apply {
// 尝试使用BleMediaService,如果不存在则回退到BleService
val serviceClassName = try {
Log.d(TAG, "BleMediaService存在")
Class.forName("com.yunqiinnovation.agent_service.BleMediaService").name
} catch (e: ClassNotFoundException) {
Log.d(TAG, "BleMediaService不存在,回退到BleService")
BleService::class.java.name
}
setClassName(this@BleCompanionService, serviceClassName)
action = "android.intent.action.DEVICE_APPEARED"
putExtra("assocId", info.id)
putExtra("displayName", info.displayName)
// 如果设备信息中有MAC地址,也可以传递
// 使用反射获取MAC地址,因为deviceMacAddress在不同API版本可能不同或不存在
try {
val macAddressMethod = info.javaClass.getMethod("getDeviceMacAddress")
val mac = macAddressMethod.invoke(info) as? String
mac?.let { putExtra("mac", it) }
} catch (e: Exception) {
Log.e(TAG, "获取设备MAC地址失败: ${e.message}", e)
}
}
// 启动前台服务
startForegroundService(serviceIntent)
Log.i(TAG, "已启动服务")
} catch (e: Exception) {
Log.e(TAG, "启动服务失败: ${e.message}", e)
}
}
override fun onDeviceDisappeared(info: AssociationInfo) {
super.onDeviceDisappeared(info)
Log.i(TAG, "配对设备消失: ${info.displayName}, ID: ${info.id}")
// 可以选择停止服务或执行其他清理操作
}
// 注意: 不需要重写 onBindCompanionDeviceService 和 onUnbindCompanionDeviceService
// 这些方法在 Android 12+ 才添加,我们可以使用父类的默认实现
}

1
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -520,6 +520,7 @@ object BleService {
*/ */
fun connect() { fun connect() {
try { try {
Log.i(TAG, "设备连接与通信: 自动连接已配对的BLE设备")
// 创建 BleCompanionManager 实例 // 创建 BleCompanionManager 实例
val companionManager = BleCompanionManager(context) val companionManager = BleCompanionManager(context)

54
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt

@ -123,21 +123,21 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
// 实现ActivityResultListener接口 // 实现ActivityResultListener接口
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
// 处理设备关联结果 // // 处理设备关联结果
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val handled = bleCompanionManager.handleAssociationResult(requestCode, resultCode, data) // val handled = bleCompanionManager.handleAssociationResult(requestCode, resultCode, data)
if (handled) { // if (handled) {
// 返回设备关联结果 // // 返回设备关联结果
pendingAssociationResult?.success(true) // pendingAssociationResult?.success(true)
pendingAssociationResult = null // pendingAssociationResult = null
return true // return true
} else { // } else {
pendingAssociationResult?.success(false) // pendingAssociationResult?.success(false)
pendingAssociationResult = null // pendingAssociationResult = null
return true // return true
} // }
} // }
return false return true
} }
// 添加sendEvent函数 // 添加sendEvent函数
@ -247,18 +247,18 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
return return
} }
// 启动设备关联 // // 启动设备关联
val success = bleCompanionManager.startAssociation( // val success = bleCompanionManager.startAssociation(
currentActivity, // currentActivity,
deviceNamePattern, // deviceNamePattern,
serviceUuid, // serviceUuid,
macAddress // macAddress
) // )
if (!success) { // if (!success) {
pendingAssociationResult = null // pendingAssociationResult = null
result.error("FAILED", "启动设备关联失败", null) // result.error("FAILED", "启动设备关联失败", null)
} // }
// 不要立即返回结果,等待onActivityResult回调 // 不要立即返回结果,等待onActivityResult回调
} }
"getPairedMacAddress" -> { "getPairedMacAddress" -> {
@ -304,7 +304,7 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
} }
"clearAssociations" -> { "clearAssociations" -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
bleCompanionManager.clearAllAssociations() // bleCompanionManager.clearAllAssociations()
result.success(true) result.success(true)
} else { } else {
result.error("UNSUPPORTED", "设备不支持Companion Device Manager", null) result.error("UNSUPPORTED", "设备不支持Companion Device Manager", null)

12
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BootReceiver.kt

@ -12,16 +12,22 @@ class BootReceiver : BroadcastReceiver() {
private val TAG = "BootReceiver" private val TAG = "BootReceiver"
override fun onReceive(ctx: Context, i: Intent) { override fun onReceive(ctx: Context, i: Intent) {
val serviceClass = try { val serviceClass = try {
Log.i(TAG, "BleMediaService不存在") Log.d(TAG, "BleMediaService不存在")
Class.forName("com.yunqiinnovation.agent_service.BleMediaService") Class.forName("com.yunqiinnovation.agent_service.BleMediaService")
} catch (e: ClassNotFoundException) { } catch (e: ClassNotFoundException) {
Log.i(TAG, "BleMediaService不存在") Log.d(TAG, "BleMediaService不存在")
return return
} }
if (i.action == Intent.ACTION_BOOT_COMPLETED) { if (i.action == Intent.ACTION_BOOT_COMPLETED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
Log.d(TAG, "BleMediaService")
ctx.startForegroundService(Intent(ctx, serviceClass)) ctx.startForegroundService(Intent(ctx, serviceClass))
else ctx.startService(Intent(ctx, serviceClass)) }
else{
Log.d(TAG, "BleMediaService")
ctx.startService(Intent(ctx, serviceClass))
}
} }
} }
} }
Loading…
Cancel
Save