|
|
|
@ -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}") |
|
|
|
} |
|
|
|
|