Skip to content

android: OS-level mic mute on stem press during calls + independent head gesture toggles#580

Open
ntsour wants to merge 1 commit intokavishdevar:mainfrom
ntsour:feat/head-gesture-toggles
Open

android: OS-level mic mute on stem press during calls + independent head gesture toggles#580
ntsour wants to merge 1 commit intokavishdevar:mainfrom
ntsour:feat/head-gesture-toggles

Conversation

@ntsour
Copy link
Copy Markdown

@ntsour ntsour commented May 6, 2026

Two related changes to call audio control, both landing in the same service refactor.


1. OS-level mic mute on stem press during a call

the problem

Previously, pressing the AirPods stem during a call only triggered whatever the Bluetooth stack or the app natively did. The OS microphone was never actually muted — AudioManager.setMicrophoneMute() was never called from the stem press path.

what changed

  • New StemAction.MUTE_CALL enum value; setupStemActions() forces the firmware to report the stem press event during a call (the mute press type is read from CALL_MANAGEMENT_CONFIG so double-press devices are handled correctly)
  • toggleMicMute() calls AudioManager.setMicrophoneMute() and updates the notification
  • Stem press during a call plays a confirmation tone: accept sound (↑) on unmute, decline sound (↓) on mute via GestureFeedback.playConfirmation()
  • A low-volume reminder tone plays every 15 seconds while the mic is muted during a call so the user doesn't forget they're muted
  • Once any press is marked customized during a call, the firmware stops handling end-call natively and forwards every press to the app, so handleCallStemPress also intercepts the end-call press (the opposite of the mute press) and routes it through rejectCall(). This works for both telephony (TelecomManager.endCall) and VoIP (HEADSETHOOK fallback for Teams/Zoom/Meet)

2. Independent head gesture toggles for calls

the problem

head_gestures was a single boolean that controlled all gesture behaviour. There was no way to use gestures to mute/unmute during an active call without also enabling nod/shake to answer/decline incoming calls.

what changed

  • head_gestures pref replaced by two independent booleans: head_gestures_answer_call and head_gestures_mute_call
  • HeadTrackingScreen shows two separate toggles with independent descriptions
  • Nod/shake to answer or decline an incoming (ringing) call: head_gestures_answer_call
  • Shake to mute / nod to unmute the mic during an active call: head_gestures_mute_call
  • VoIP calls (detected via AudioManager.MODE_IN_COMMUNICATION) also trigger the mute gesture loop, not just telephony calls
  • AirPodsSettingsScreen nav button shows On if either toggle is enabled
  • Android 9 fallback disables both prefs instead of the old single one

what this does NOT change

  • Nod/shake detection logic and thresholds are unchanged
  • The Head Gestures screen entry point in settings is unchanged

Replace the single "Head Gestures" master toggle with two independent feature
toggles in the Head Tracking screen:
  * Answer/decline incoming calls (nod = answer, shake = decline)
  * Mute/unmute during a call (shake = mute, nod = unmute)

The two behaviors operate in different call phases (ringing vs. active) and
never overlap, so they are exposed as separate prefs (head_gestures_answer_call
and head_gestures_mute_call) rather than a single switch.

Additional gesture-related polish:
  * Stem-press toggleMicMute now plays the same confirm_yes/confirm_no tones
    used for head gestures (audible mute/unmute feedback over the AirPods).
  * New MUTE_CALL StemAction so a stem press can be mapped to mute toggling.
  * Periodic 15-second low-volume "still muted" reminder while the mic is muted
    during a call, cancelled automatically on unmute or call end.
  * Active-call gesture loop now mutes/unmutes directionally (shake mutes, nod
    unmutes) instead of toggling, fixing the case where the gesture detector
    re-fires the same direction during a missed restart.
  * Restart bug fix in GestureDetector: callback is now invoked AFTER
    stopDetection() so a callback that re-arms detection doesn't hit
    isRunning=true and silently no-op.
  * Removed per-movement "blip" sounds on every head turn; only the final
    confirmation tone is played.
  * Stale-state fix in testHeadGestures(): force a stop before re-arming so
    isRunning=true left over from a previous test no longer breaks the next.
  * Threshold tuning for the gesture detector to reduce accidental triggers.
@ntsour ntsour changed the title android: split head gesture call action into two independent toggles android: OS-level mic mute on stem press during calls + independent head gesture toggles May 6, 2026
@ntsour ntsour force-pushed the feat/head-gesture-toggles branch from 0343554 to ac5079b Compare May 6, 2026 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant