feat(android): Auto-disable CA volume ducking when only one AirPod is worn (#616)#675
feat(android): Auto-disable CA volume ducking when only one AirPod is worn (#616)#675QuantumBreakz wants to merge 2 commits into
Conversation
- Add conversationalAwarenessBothPodsOnly field to ServiceConfig (default false) - Seed 'conversational_awareness_both_pods_only' pref on first run (default false) - Load pref in initializeConfig() and handle live changes in onSharedPreferenceChanged() - Add conversationalAwarenessBothPodsOnlyEnabled to AppSettingsUiState - Add setConversationalAwarenessBothPodsOnlyEnabled() setter to AppSettingsViewModel - Add StyledToggle in AppSettingsScreen inside the Conversational Awareness list - Add string resources: conversational_awareness_both_pods_only + _description Default is off — no behavior change for existing users. Fixes librepods-org#616
|
Hey @kavishdevar 👋 This PR implements the feature requested in #616 by @tabjy using the app-side suppression approach you approved. Since this is submitted from a fork, I can't set reviewers or assignees programmatically. Could you please:
Build is verified: Happy to make any changes based on your review! 🙏 |
tabjy
left a comment
There was a problem hiding this comment.
Thank you for the PR! Sorry I never got around doing this. I really appreciate you taking this issue. I haven't actually tested your code yet. Here are some pre-flight thoughts. I hope they're helpful.
I'll get back to you after I have a chance to test your branch. :)
| <string name="conversational_awareness_pause_music">Pause Music</string> | ||
| <string name="conversational_awareness_pause_music_description">When you start speaking, music will be paused.</string> | ||
| <string name="conversational_awareness_both_pods_only">Only when both AirPods are worn</string> | ||
| <string name="conversational_awareness_both_pods_only_description">Only lowers the volume when you speak if both AirPods are in your ears.</string> |
There was a problem hiding this comment.
Something along the line of:
Only with Both AirPods: Enables conversational awareness only if both AirPods are in ears.
There was a problem hiding this comment.
Done in the latest commit (5d4d36a). Went with "Both AirPods in Ear" as the label to keep it short and consistent with Apple's in-ear detection phrasing elsewhere in the app. Happy to adjust wording as I went with the version you suggested.
| var deviceName: String = "AirPods", | ||
| var earDetectionEnabled: Boolean = true, | ||
| var conversationalAwarenessPauseMusic: Boolean = false, | ||
| var conversationalAwarenessBothPodsOnly: Boolean = false, |
There was a problem hiding this comment.
In the original issue, two approaches were discussed. Your PR seems to be approach 2 (app-side suppression). @kavishdevar pointed out this approach will still let one bud to switch to transparency mode. To me this is perfectly acceptable, but @kavishdevar what's your thought?
There was a problem hiding this comment.
To add context: the transparency-mode switch is firmware-driven, it happens the moment the bud detects you're speaking, independently of what the app does. App-side suppression (Approach 2) only prevents startSpeaking() from lowering the volume; it can't intercept the firmware-level transparency switch.
Approach 1 (sending a firmware command to disable CA entirely) would suppress both, but it means toggling a firmware feature on/off reactively based on ear state, which is more invasive and has different tradeoffs (latency, firmware round-trips, potential edge cases on reconnect).
I'm happy to implement either, but leaving this for @kavishdevar to call, don't want to pick the approach and have it reopened at merge time.
| MediaController.startSpeaking() | ||
| } else if (conversationAwarenessNotification.status == 6.toByte() ||conversationAwarenessNotification.status == 8.toByte() || conversationAwarenessNotification.status == 9.toByte()) { | ||
| MediaController.stopSpeaking() | ||
| if (!blockedForSinglePod) MediaController.startSpeaking() |
There was a problem hiding this comment.
Your toggle also gates music pausing. By suppressing startSpeaking(), if user also has "Pause Music" enabled, this toggle would prevent that too. Again, this is perfectly fine to me, but I'd recommend updating UI string to reflect that.
There was a problem hiding this comment.
Also, a simple debug log on both paths printing startSpeaking() is suppressed would be helpful.
There was a problem hiding this comment.
Your toggle also gates music pausing. By suppressing
startSpeaking(), if user also has "Pause Music" enabled, this toggle would prevent that too. Again, this is perfectly fine to me, but I'd recommend updating UI string to reflect that.
Done in the same commit. The description now reads:
"Conversational Awareness only activates when both AirPods are in your ears. This also suppresses the Pause Music action when wearing a single AirPod."
There was a problem hiding this comment.
Also, a simple debug log on both paths printing
startSpeaking()is suppressed would be helpful.
Added in 5d4d36a. Both branches now carry a Log.d("AirPodsParser", ...), the suppressed path prints the bud count, the live path prints bud count + flag state. stopSpeaking() is left without a log since it's always unconditional.
…only - Rename label to 'Both AirPods in Ear' (shorter, Apple-style) - Expand description to note that Pause Music is also suppressed when wearing a single AirPod, since the gate sits above startSpeaking() - Add Log.d on both branches of the startSpeaking() gate so suppression is visible in logcat during testing; stopSpeaking() remains ungated
Summary
Closes #616
Implements the app-side suppression approach suggested by @tabjy and accepted by @kavishdevar.
When the new "Only when both AirPods are worn" toggle is ON, the Conversational Awareness volume-ducking event (
startSpeaking()) is silently ignored if fewer than two buds are currently in-ear. ThestopSpeaking()(volume restore) path is never gated, so volume can never get stuck low.Changes
AirPodsService.ktconversationalAwarenessBothPodsOnly: Boolean = falsetoServiceConfigconversational_awareness_both_pods_onlyin first-run SharedPreferences defaults (false)initializeConfig()onSharedPreferenceChanged()onConversationAwarenessReceived():AppSettingsViewModel.ktconversationalAwarenessBothPodsOnlyEnabled: Boolean = falsetoAppSettingsUiStateloadSettings()setConversationalAwarenessBothPodsOnlyEnabled(enabled: Boolean)setterAppSettingsScreen.ktStyledToggleinside the existing Conversational AwarenessStyledList, between "Pause Music" and "Relative volume"strings.xmlBehaviour
The
stopSpeaking()path always runs regardless of toggle state — volume is always restored.Testing
Build verified:
./gradlew assembleFossDebug→ BUILD SUCCESSFUL (51 tasks, 0 errors).Manual test steps:
Notes
falsein all five required places — zero behaviour change for existing usersearDetectionNotification.statusarray (0x00= in-ear)