Skip to content

android: sync OS mic mute state with Microsoft Teams via NotificationListenerService#581

Open
ntsour wants to merge 2 commits intokavishdevar:mainfrom
ntsour:feat/teams-mute-sync
Open

android: sync OS mic mute state with Microsoft Teams via NotificationListenerService#581
ntsour wants to merge 2 commits intokavishdevar:mainfrom
ntsour:feat/teams-mute-sync

Conversation

@ntsour
Copy link
Copy Markdown

@ntsour ntsour commented May 6, 2026

the problem

When LibrePods mutes the OS microphone (via head gesture or stem press), the mute indicator inside the Microsoft Teams app does not update. Teams manages its own mute state and does not register with the Android Telecom framework, so InCallService never fires.

what changed

  • TeamsNotifListener (new NotificationListenerService) watches for Teams' ongoing-call notification (com.microsoft.teams and variant package names)
  • When Teams posts a call notification, the service caches the Mute and Unmute PendingIntents from its notification actions
  • AirPodsService.toggleMicMute() calls TeamsNotifListener.setMuted() after each OS mute toggle, which fires the appropriate cached intent to sync Teams' in-app indicator
  • The listener is non-intrusive: it only reads notification actions from Teams' own ongoing-call notification and fires nothing if Teams is not in a call
  • Notification access permission card added to the initial setup screen (same style as existing permission cards); "ask for all normal permissions" button also opens the notification access settings if not yet granted

why NotificationListenerService and not InCallService

Teams does not use the Android Telecom stack. Logcat confirmed setMutedAll(...): no instance bound on every attempt with InCallService. Teams exposes Mute/Unmute PendingIntents in its ongoing notification, which is the only reliable hook available without modifying Teams.

what this does NOT change

Non-Teams calls are unaffected. The listener holds no wakelock and does nothing when Teams is not posting a call notification.

depends on

Tests

  • Tested on Pixel 10 on Android 16.

This branch is based on #580.

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 force-pushed the feat/teams-mute-sync branch from 24cd3f0 to ede9ff0 Compare May 6, 2026 11:34
Teams on Android does not register VoIP calls with the Telecom framework,
so InCallService.setMuted() cannot drive its in-app mute UI. Instead, watch
Teams' ongoing-call notification and fire the cached Mute/Unmute action
PendingIntent — Teams reacts as if the user had tapped the action in the
notification, which keeps the in-app mute icon in sync with the OS mute set
by hardware controls (stem press, head gesture).

  * New TeamsNotifListener service (BIND_NOTIFICATION_LISTENER_SERVICE) that
    caches the latest Mute and Unmute actions from notifications posted by
    com.microsoft.teams (and a couple of related package variants).
  * AirPodsService.toggleMicMute() and the active-call gesture loop now also
    call TeamsNotifListener.setMuted() alongside AudioManager.setMicrophoneMute,
    so Teams' UI follows the OS state.
  * Notification access permission is requested from the initial setup screen,
    matching the existing permission cards. The "Ask for regular permissions"
    button now also opens the system Notification access settings if not yet
    granted.

The user must enable LibrePods in Settings → Apps → Notification access for
the sync to work; without it the call is a no-op and OS-level mute still
applies, just without Teams' UI updating.
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