Files
amethyst/nestsClient/plans/2026-04-26-background-audio-audit.md
T
Claude cc829f6b0b fix(audio-rooms): request audio focus + Tier-3 audit notes
Tier-3 background-audio audit (item #4 found the only gap):
AudioTrackPlayer.start() never called requestAudioFocus, so an
inbound phone call mixed on top of the room audio instead of
ducking it. Fixed by acquiring focus at the SERVICE level (one
owner per room session, not per player) in
AudioRoomForegroundService.requestAudioFocus():

  * AUDIOFOCUS_GAIN with USAGE_VOICE_COMMUNICATION +
    CONTENT_TYPE_SPEECH so the OS treats the room like a phone
    call.
  * setAcceptsDelayedFocusGain(false) — immediate focus or
    nothing.
  * On-change listener is a no-op; the OS handles duck-on-loss.
  * abandonAudioFocusRequest in onDestroy.
  * Best-effort acquire — runCatching swallows denials so a
    refused request doesn't fail service start (the OS will
    duck/pause us by its own policy, which is degraded but
    acceptable).

Audit notes file under nestsClient/plans/ documents items 1-5
with commit-time citations:
  1. PARTIAL_WAKE_LOCK — already correct
  2. FG type microphone (14+) — already correct
  3. FG type media-playback for listen-only — already correct
  4. Audio focus — fixed in THIS commit
  5. PIP keep-alive — already correct
2026-04-26 23:40:34 +00:00

2.8 KiB

Background-audio audit (T3 #2)

Audit run for the Tier-3 plan checklist. Items pass-by-default; deltas captured below.

1. PARTIAL_WAKE_LOCK during a broadcast — PASS

amethyst/src/main/java/com/vitorpamplona/amethyst/service/audiorooms/AudioRoomForegroundService.kt

  • Acquired in onCreate() (line 64) via newWakeLock(PARTIAL_WAKE_LOCK, "amethyst:audio-room") with setReferenceCounted(false).
  • Acquire on every onStartCommand if not held (line 99-101) with a 4-hour safety cap (WAKE_LOCK_TIMEOUT_MS).
  • Released in onDestroy() (line 174-175) via wakeLock?.takeIf { it.isHeld }?.release().

2. Foreground-service type microphone for Android 14+ — PASS

Same file, startForegroundWithType(includeMic):

  • When includeMic = true (broadcast mode), uses FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or FOREGROUND_SERVICE_TYPE_MICROPHONE (lines 111-112). Required by Android 14+ while the mic is open.
  • The manifest already declares android:foregroundServiceType="microphone|mediaPlayback" (verified separately in AndroidManifest.xml).

3. Listener-only foreground type — PASS

Same call, includeMic = false path uses just FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK (line 114). Play Store policy requires the elevated microphone permission only when the mic is actually open; listen-only sessions correctly stick to media-playback.

4. Audio focus — WAS MISSING; FIXED IN THIS COMMIT

Pre-fix: AudioTrackPlayer.start() constructed an AudioTrack but never called AudioManager.requestAudioFocus(...). A phone call would mix on top of the room audio instead of ducking it.

Fix: focus is now requested at the SERVICE level (one focus owner per room session, not per player) in AudioRoomForegroundService.requestAudioFocus():

  • AUDIOFOCUS_GAIN with USAGE_VOICE_COMMUNICATION + CONTENT_TYPE_SPEECH so the OS treats the room like a phone call.
  • setAcceptsDelayedFocusGain(false) — we want the focus immediately or not at all.
  • On-change listener is a no-op; the OS handles duck-on-loss for us.
  • abandonAudioFocusRequest in onDestroy().

Refused focus requests are caught with runCatching — a denial means the OS will duck/pause us based on its own policy, which is a degraded but acceptable experience and shouldn't fail service start.

5. PIP keep-alive — PASS (existing)

AudioRoomActivity already survives the PIP transition without dropping the foreground service. The activity's onPictureInPictureModeChanged doesn't stop the service; the service tracks its own promoted state independent of activity lifecycle.

Summary

  • 4/5 items passed before this commit
  • 1 fix in AudioRoomForegroundService adds the audio-focus request

No follow-ups remaining for the Tier-3 audit checklist.