Reaction: Fix false auto-pause from Bluetooth interference#559
Merged
Conversation
Classifies the ear-detection source (AAP / BLE_IRK_MATCH / BLE_PROFILE_FALLBACK / BLE_ANONYMOUS / NO_LIVE_BLE) and applies a 3-sample debounce only to unauthenticated BLE paths. AAP and IRK-authenticated BLE pass through unchanged. Also tightens toEarDetectionState() to prefer AAP aggregate over BLE per-side bits whenever AAP EarDetection is present, and suppresses pause on NO_LIVE_BLE (cache-only state) to avoid firing without live evidence.
- Commit pending pause when a trusted source (AAP / BLE_IRK_MATCH) corroborates the not-worn condition mid-debounce, instead of dropping pending silently. - Scope debounceFreshness to not-worn samples only; identical both-in samples no longer pass distinctUntilChangedBy and can't accidentally trigger BLE-only auto-play confirmation. - Add resetTolerance to PendingPauseDebounce so a single corrupt count-up advert no longer kills a legitimate pending pause; reorder reset checks so rawDecision.shouldPlay resets immediately. - Drop bleKeyState from the INFO autoPause log; source already encodes trust without leaking key-configuration state to logcat. - Add flow-level MonitorFlowTests verifying the distinctUntilChangedBy interaction with seenLastAt freshness, plus the #557-direction test (AAP-worn vs corrupt-BLE-not-worn) and rebound-tolerance test. - Clarify in BLE_ANONYMOUS KDoc that the path is unreachable in production via DeviceMonitor.primaryDevice.
…app start - Apply seenLastAt freshness to all unauthenticated BLE samples (worn and not-worn). The earlier scoping to not-worn-only collapsed the second worn sample for BLE-only autoplay confirmation, so the staged play never fired. - Replace distinctUntilChangedBy with a manual filter so worn samples that need to reset an active pause debounce (count went up) can pass through even when the monitor key is otherwise identical. - Skip BLE-only autoplay confirmation for trusted sources. With BLE_IRK_MATCH and AAP, autoplay now fires on the first not-worn -> worn transition, mirroring the pause-debounce skip on the same sources. - Skip the reaction entirely when the previous emission had no live evidence (NO_LIVE_BLE). Prevents app-process-start from synthesising a fake not-worn -> worn transition and firing autoplay while the user is already wearing the pods. Same guard handles mid-session BLE gap recoveries. - Add MonitorFlowTests covering process-start-worn, genuine-insertion-after-startup, mid-session BLE-gap recovery, IRK-matched immediate autoplay, BLE-only autoplay confirmation, 3-sample pause debounce, and rebound-tolerated debounce reset.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Fixed a bug where music could auto-pause even though your AirPods are still in your ears, when you're in a high-interference environment (e.g. crowded city, dense Wi-Fi, lots of nearby Bluetooth devices). A single corrupt Bluetooth scan was enough to flip the wear state and trigger a pause; the app now requires multiple consistent readings before reacting.
Refs #557.
Technical Context
AAP(L2CAP, error-corrected),BLE_IRK_MATCH(RPA verified against the profile's identity key),BLE_PROFILE_FALLBACK(signal-quality fallback assignment),BLE_ANONYMOUS(no profile match),NO_LIVE_BLE(cache-only). Only the bottom two are debounced — identity-authenticated paths can't be misattributed to a different pair, so trusting them is safe and avoids extra latency for paired AirPods.PlayPauseMonitorKeycarriesseenLastAtasdebounceFreshnessfor debounce-eligible sources only, sodistinctUntilChangedBydoesn't collapse identical not-worn BLE samples and the counter can advance.NO_LIVE_BLE(cached state with no live evidence) explicitly suppressesshouldPause. Otherwise a worn → cache-only transition can derive a not-worn state from null fields and fire pause without any real removal evidence.toEarDetectionState()to prefer AAP aggregate whenever AAPEarDetectionis present, even if per-side resolution falls through to BLE bits. Previously a defensive case (resolvedPrimaryPod == nullwhilebleprovides per-side bits) could let BLE bits drive the decision while AAP had the authoritative aggregate state.INFO-level diagnostic line on every pause emission andDEBUG-level lines on every debounce state transition (STARTED/ADVANCED/RESET/COMMITTED), with the source classification, pod-count delta,bleKeyState, AAP connection state, andpauseSentoutcome fromMediaControl. Future user reports become easier to triage.BlePodMonitor.preferCaseContextPodcan keep an existing case-context snapshot in place over an incoming non-case-context snapshot, preserving the oldseenLastAt. This is fail-closed (delays a legitimate unauthenticated-BLE pause rather than firing a false one), documented inline.Review checklist
source=AAP)source=BLE_ANONYMOUS), verify auto-pause now requires ~3 advert cycles before firingEarDetectionSourceclassification doesn't regress any existing reaction tests