From fd1b5c972698fb44c00df86bfbfcdd28956a48af Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Wed, 22 Apr 2026 15:07:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=9F=B3=E4=B9=90=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent_service/AgentService.kt | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 281b0dfd8..56158a387 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -161,6 +161,12 @@ object AgentService : CoroutineScope { // ========== 音频焦点(唤醒 AI 时打断外部和内部其他音频)========== private var audioManagerForFocus: AudioManager? = null private var aiAudioFocusRequest: AudioFocusRequest? = null + // 本轮 wake 发起时外部是否有音乐在响。决定 abandon 时是否允许系统把外部 App 叫回来: + // true → 用 AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,外部 App 收到 LOSS_TRANSIENT, + // 我们归还焦点后系统会通知它自动恢复(保持原有 UX) + // false → 用 AUDIOFOCUS_GAIN(非 transient),外部 App 收到 LOSS, + // 我们归还焦点时系统不会把它叫起来,避免"本来没在播的音乐被莫名拉起" + private var wasExternalMusicActiveBeforeFocus = false // focusChange 整数 → 可读名字,方便导出的日志里一眼看懂 private fun focusChangeName(code: Int): String = when (code) { @@ -202,7 +208,15 @@ object AgentService : CoroutineScope { // 请求前快照:系统此刻是否有音乐在响 / 音频模式 / 本地是否还留着上次的 request val musicActiveBefore = try { am.isMusicActive } catch (_: Throwable) { false } val audioMode = try { am.mode } catch (_: Throwable) { -1 } - _log("INFO", "[Agent][焦点] requestAiAudioFocus 进入: isMusicActive=$musicActiveBefore audioMode=$audioMode cachedReq=${aiAudioFocusRequest != null} sdk=${Build.VERSION.SDK_INT}") + // 记录本轮 wake 外部是否有音乐在响,abandon 时据此决定是否让系统把外部 App 叫回来 + wasExternalMusicActiveBeforeFocus = musicActiveBefore + val focusGainType = if (musicActiveBefore) { + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE + } else { + AudioManager.AUDIOFOCUS_GAIN + } + val focusGainName = if (musicActiveBefore) "TRANSIENT_EXCLUSIVE" else "GAIN" + _log("INFO", "[Agent][焦点] requestAiAudioFocus 进入: isMusicActive=$musicActiveBefore audioMode=$audioMode cachedReq=${aiAudioFocusRequest != null} sdk=${Build.VERSION.SDK_INT} focusType=$focusGainName") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // 每次唤醒强制重抢焦点: @@ -219,7 +233,7 @@ object AgentService : CoroutineScope { } aiAudioFocusRequest = null - val req = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) + val req = AudioFocusRequest.Builder(focusGainType) .setAudioAttributes( AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) @@ -234,11 +248,11 @@ object AgentService : CoroutineScope { AudioManager.AUDIOFOCUS_REQUEST_GRANTED, AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> { aiAudioFocusRequest = req - _log("INFO", "[Agent][焦点] 强制重抢音频焦点(EXCLUSIVE) 结果=$resultName") + _log("INFO", "[Agent][焦点] 强制重抢音频焦点($focusGainName) 结果=$resultName") } else -> { aiAudioFocusRequest = null - _log("WARNING", "[Agent][焦点] 强制重抢音频焦点(EXCLUSIVE) 失败 结果=$resultName —— 其他 App 正占用且拒绝让出") + _log("WARNING", "[Agent][焦点] 强制重抢音频焦点($focusGainName) 失败 结果=$resultName —— 其他 App 正占用且拒绝让出") } } // 请求完给系统一点时间把 focus loss 通知到其他 App,再复查一次 @@ -267,9 +281,9 @@ object AgentService : CoroutineScope { val result = am.requestAudioFocus( aiAudioFocusListener, AudioManager.STREAM_VOICE_CALL, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE + focusGainType ) - _log("INFO", "[Agent][焦点] 强制重抢音频焦点(EXCLUSIVE, legacy) 结果=${requestResultName(result)}") + _log("INFO", "[Agent][焦点] 强制重抢音频焦点($focusGainName, legacy) 结果=${requestResultName(result)}") } } catch (e: Exception) { _log("ERROR", "[Agent][焦点] 请求音频焦点异常: ${e.message}") @@ -280,6 +294,7 @@ object AgentService : CoroutineScope { try { val am = audioManagerForFocus ?: return val had = aiAudioFocusRequest != null + val autoResumeExternal = wasExternalMusicActiveBeforeFocus if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { aiAudioFocusRequest?.let { am.abandonAudioFocusRequest(it) } aiAudioFocusRequest = null @@ -287,7 +302,9 @@ object AgentService : CoroutineScope { @Suppress("DEPRECATION") am.abandonAudioFocus(aiAudioFocusListener) } - _log("INFO", "[Agent][焦点] 已归还音频焦点 (之前 cached=$had)") + // 本轮结束,清掉标记,下一次 wake 会重新采样 + wasExternalMusicActiveBeforeFocus = false + _log("INFO", "[Agent][焦点] 已归还音频焦点 (之前 cached=$had autoResumeExternal=$autoResumeExternal)") } catch (e: Exception) { _log("ERROR", "[Agent][焦点] 归还音频焦点异常: ${e.message}") }