# 01 — 系统语音助手集成规范 > **目标**:让昔涟成为 Android 系统级默认语音助手,替换 Google Assistant / Bixby > **核心 API**:`VoiceInteractionService` + `VoiceInteractionSession` --- ## 1. 功能目标 - 用户可在 **系统设置 → 默认应用 → 数字助理** 中选择昔涟 - 长按 Home 键呼出昔涟(非全屏,悬浮覆盖层) - 屏幕底部两角向内滑动触发昔涟 - 长按电源键可配置为呼出昔涟 - 息屏状态下热词唤醒昔涟 - 有线/蓝牙耳机按键呼出昔涟 ## 2. AndroidManifest.xml 声明 ```xml ``` ## 3. 配置文件 ### res/xml/voice_interaction_config.xml ```xml ``` ## 4. VoiceInteractionService 实现 ```kotlin class CyreneVoiceInteractionService : VoiceInteractionService() { override fun onReady() { super.onReady() // 服务就绪,可在此初始化 TTS 引擎等 } override fun onCreateSession(args: Bundle?): VoiceInteractionSession { return CyreneVoiceInteractionSession(this) } override fun onLaunchVoiceAssistFromKeyguard() { // 锁屏启动 → 进入简化模式,仅显示对话,IoT 控制等需先解锁 } // Android 14+: AssistAction 回调 override fun onHandleAssist( request: AssistRequest?, cancellationSignal: CancellationSignal?, callback: OutcomeCallback? ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { request?.let { val assistContent = it.assistContent // 提取当前屏幕上下文(可选,用于后续上下文感知) callback?.onResult(AssistResult(assistContent)) } } } } ``` ## 5. VoiceInteractionSession 实现(悬浮窗界面) ```kotlin class CyreneVoiceInteractionSession(context: Context) : VoiceInteractionSession(context) { override fun onCreateContentView(): View { // 返回 ComposeView 作为悬浮窗的内容 return ComposeView(context).apply { setContent { CyreneTheme { OverlayScreen( viewModel = overlayViewModel, onDismiss = { finish() } ) } } } } override fun onShow(args: Bundle?, showFlags: Int) { super.onShow(args, showFlags) // 设置窗口属性:透明背景 + 底部卡片式布局 window?.apply { // 半透明遮罩 setBackgroundDrawable(ColorDrawable(0x80000000.toInt())) // FLAG_DIM_BEHIND 可实现模糊效果 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { addSystemGestureExclusionRects(...) } } } override fun onComputeInsets(outInsets: Insets?) { super.onComputeInsets(outInsets) // 控制悬浮窗内容区域 } override fun onHide() { super.onHide() // 悬浮窗隐藏时清理状态 } } ``` ### 关键窗口属性 | 属性 | 值 | 说明 | |------|-----|------| | 背景 | `ColorDrawable(0x80000000)` | 半透明黑色遮罩,透出底层 APP | | 内容区域 | 自适应高度 | 底部弹出,类似 Google Assistant | | 触摸外区域行为 | 关闭悬浮窗 | 用户点击遮罩区域关闭 | | 键盘弹出 | 推高内容区域 | 文本输入时自动调整 | ## 6. 权限清单 ```xml ``` ## 7. 引导用户设为默认助手 首次启动时检测并引导: ```kotlin fun checkAndPromptDefaultAssistant(context: Context) { val isDefault = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val componentName = ComponentName(context, CyreneVoiceInteractionService::class.java) context.packageManager .queryIntentServices( Intent(VoiceInteractionService.SERVICE_INTERFACE), PackageManager.MATCH_DEFAULT_ONLY ) .any { it.serviceInfo.packageName == context.packageName } } else { false } if (!isDefault) { // 显示引导 UI → 跳转到 Settings.ACTION_VOICE_INPUT_SETTINGS val intent = Intent(Settings.ACTION_VOICE_INPUT_SETTINGS) context.startActivity(intent) } } ``` ## 8. 热词唤醒检测 ### 方案选型 | 方案 | 优点 | 缺点 | 适用场景 | |------|------|------|---------| | 系统 Always-On Hotword API | 低功耗、系统级支持 | 限 Android 8+,某些 ROM 不支持 | **首选** | | Porcupine (Picovoice) | 跨平台、离线 | 商业许可,需额外集成 | 兜底 | | 自建模型 (openWakeWord) | 完全可控、低成本 | 需要本地推理能力 | 长期方案 | ### 唤醒词配置 | 优先级 | 唤醒词 | 说明 | |--------|--------|------| | P0 | "昔涟" (Xī Lián) | 角色名,默认唤醒词 | | P1 | "Hey 昔涟" | 与 "Hey Google" 习惯对齐 | | P2 | 自定义 | 用户可在设置中自定义 | ### 息屏唤醒流程 ``` 用户说出唤醒词 → HotwordDetector 识别成功(<800ms) → 系统触发 VoiceInteractionService → CyreneVoiceInteractionSession.onCreateContentView() → Overlay 显示,播放连接提示音 → 用户说话 → STT → AI-Core → TTS → 语音回复 → 对话结束 → finish() → 息屏 ``` ## 9. Dismiss 时机 悬浮窗在以下情况关闭: | 条件 | 行为 | |------|------| | 用户说"再见" / "退下" | 自然对话结束,收起悬浮窗 | | 用户点击遮罩区域 | 立即关闭 | | 对话静默 10 秒 | 自动收起 | | 用户主动滑动关闭 | 手势关闭,同 Google Assistant | | 收到系统电话等中断 | 暂停语音,进入后台等待 | ## 10. 降级策略 当系统不支持 `VoiceInteractionService` 或未设为默认助手时: - **保底方案**:PWA(利用主项目已有的 PWA 支持) - **WebView 封装**:内嵌 H5 对话界面作为过渡 - **通知栏常驻**:提供快速对话入口,但功能受限