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