Have you checked if an issue already exists for this feature request?
What feature would you like added?
Hello, and first of all, thank you very much for Key Mapper. It is an excellent, thoughtfully designed app, and the "Key Mapper as the device assistant" feature is exactly what lets people like me give a new life to hardware that otherwise cannot be remapped at all. I really appreciate the time and care you put into this project.
I would like to report a limitation I ran into and, I hope, give you enough information to evaluate it quickly. To note – being an amateur I have done an amount of testing I could, which are summarized below.
Summary
When Key Mapper is set as the default digital assistant and the assistant is invoked by a Bluetooth HFP headset button (the headset sends AT+BVRA=1), the Android Bluetooth stack keeps the voice recognition session pending for ~5 seconds, waiting for the invoked app to call BluetoothHeadset.startVoiceRecognition(). Key Mapper does not do this, so the session is only closed by the stack's timeout (+BVRA: 0).
During these ~5 seconds, all further button presses are lost — they never reach Android (no startVoiceRecognitionByHeadset log entry).
As a result:
- a single press works perfectly;
- a double press, triple press, or "Press in sequence" trigger based on the "Voice assistant" trigger can never be detected, because the second press only arrives ≥ 5 s after the first.
Environment
- Key Mapper version: 4.3.1 259 (Play Store);
- Customer ID: $RCAnonymousID:b1fd7bd0020744b6a275cecc7d962dd3;
- Phone: Galaxy S24 Ultra (SM-S928B/DS);
- Android version 14 / One UI 6.1 / not rooted;
- Headset: 3M Peltor WS ProTac XPI (active hearing protection, Bluetooth HFP + A2DP; the multifunction button sends a voice-recognition request on short press, and redial (
AT+BLDN) on long press);
- Default digital assistant app: Key Mapper;
- "Use text from screen": enabled.
Key map configuration
- Trigger:
Voice assistant (Key Mapper assistant);
- Single press → action "Play/pause media playback": works as expected;
- Trigger
Voice assistant ×2, "Press in sequence" → "Next track": never fires;
- Also tested: a single Voice assistant trigger → action Send intent (Broadcast, action peltor.PROTAC_TOGGLE_MEDIA) → a counting macro in MacroDroid (counts invocations within a time window). With on-screen/test triggers the counting works flawlessly (1/2/3 presses are correctly distinguished), which confirms the key map and action side are fine. With the physical headset button, the second invocation arrives only after ~5–6 s**.
Steps to reproduce
- Set Key Mapper as the default digital assistant;
- Pair a Bluetooth headset that triggers voice recognition via HFP (
AT+BVRA=1) on a button press;
- Create a key map with the
Voice assistant trigger and any action (a toast or media action is enough).
- Press the headset button twice within ~1 s.
- Observe: the action fires once. The next press is only recognised ~5 s after the first.
Logs (adb logcat, filtered)
Each press produces startVoiceRecognitionByHeadset; the stack closes the session with BVRA : 0 almost exactly 5 s later. Presses made in between do not appear at all.
09-28 21:01:02.550 19044 19352 I HeadsetService: startVoiceRecognitionByHeadset: from XX:XX:XX:XX:0C:3E
09-28 21:01:02.550 19044 19352 D HeadsetSystemInterface: isInCall : false
09-28 21:01:02.550 19044 19352 I HeadsetService: setActiveDevice: device=XX:XX:XX:XX:0C:3E, uid/pid=1002/19044, persist connect audio true, call from internal true
09-28 21:01:02.550 19044 19352 I HeadsetService: setActiveDevice: device XX:XX:XX:XX:0C:3E is already active
09-28 21:01:07.589 19044 19085 W bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:110 LogMsg: exit sniff for faster SCO close when BVRA : 0
09-28 21:01:09.708 19044 19352 I HeadsetService: startVoiceRecognitionByHeadset: from XX:XX:XX:XX:0C:3E
09-28 21:01:14.741 19044 19085 W bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:110 LogMsg: exit sniff for faster SCO close when BVRA : 0
09-28 21:01:38.719 19044 19352 I HeadsetService: startVoiceRecognitionByHeadset: from XX:XX:XX:XX:0C:3E
09-28 21:01:43.757 19044 19085 W bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:110 LogMsg: exit sniff for faster SCO close when BVRA : 0
09-28 21:02:40.930 19044 19352 I HeadsetService: startVoiceRecognitionByHeadset: from XX:XX:XX:XX:0C:3E
09-28 21:02:45.951 19044 19352 I HeadsetService: startVoiceRecognitionByHeadset: from XX:XX:XX:XX:0C:3E
09-28 21:02:45.987 19044 19085 W bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:110 LogMsg: exit sniff for faster SCO close when BVRA : 0
09-28 21:02:50.992 19044 19085 W bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:110 LogMsg: exit sniff for faster SCO close when BVRA : 0
Intervals startVoiceRecognitionByHeadset to BVRA : 0: 5.04 s, 5.03 s, 5.04 s, 5.06 s. In the 21:02:40 series I pressed the button several times quickly; the only additional press registered is the one at 21:02:45.951 — right after the previous session timed out.
(Full unfiltered log available on request).
Analysis
As far as I understand the AOSP HFP implementation (HeadsetService / HeadsetStateMachine / HeadsetSystemInterface.activateVoiceRecognition()):
- On
AT+BVRA=1 from the headset, the stack launches the voice command handler (Intent.ACTION_VOICE_COMMAND) — in this case Key Mapper's assistant.
- The stack then waits for the launched app to call
BluetoothHeadset.startVoiceRecognition(device) (the Javadoc of activateVoiceRecognition() says: "caller should wait for BluetoothHeadset#startVoiceRecognition(BluetoothDevice) callback").
- If no such call arrives, a timeout (~5 s) fires, the stack answers the headset with
+BVRA: 0, and only then the headset/stack accept a new AT+BVRA=1.
Since Key Mapper only needs the invocation event (not actual voice input), it never acknowledges the session, so every press "costs" the full timeout.
Things I tried that did not help:
• Tasker "Bluetooth Voice → Off" immediately after each invocation (triggered via the same broadcast) — no change, the session still times out after ~5 s.
• Tasker as the default assistant ("Assistance Request" event) — not triggered at all by the headset, presumably because Tasker handles ACTION_ASSIST but not ACTION_VOICE_COMMAND. This also suggests Key Mapper is one of the very few apps that can solve this.
Proposed fix
When the assistant is invoked and a connected HFP headset is the origin (or simply whenever a HFP headset is connected and the invocation came via ACTION_VOICE_COMMAND), immediately acknowledge and terminate the Bluetooth voice recognition session, e.g.:
// Requires BLUETOOTH_CONNECT on Android 12+
val adapter = context.getSystemService(BluetoothManager::class.java).adapter
adapter.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
val headset = proxy as BluetoothHeadset
headset.connectedDevices.forEach { device ->
headset.startVoiceRecognition(device) // acknowledge the pending BVRA session
headset.stopVoiceRecognition(device) // close it right away -> +BVRA: 0 sent to headset
}
adapter.closeProfileProxy(BluetoothProfile.HEADSET, headset)
}
override fun onServiceDisconnected(profile: Int) {}
}, BluetoothProfile.HEADSET)
Possible refinements, entirely at your discretion:
- Keep the
BluetoothHeadset proxy open while the accessibility service runs, to avoid the async connection delay on every press.
- Make it an option, e.g. "Close Bluetooth headset voice session immediately (enables double press from BT headsets)", in case some users actually want the SCO/voice channel to open.
- If only
stopVoiceRecognition() turns out to be sufficient on some devices, the start call could be skipped.
I have not verified this code on a device myself, so please treat it only as a pointer. I am, however, happy to test any debug/beta build on my setup and send back adb logcat output (I have ADB set up and know the relevant log tags now, I hope).
Related / nice to have
- Long press on this headset sends AT+BLDN (redial last number), which is handled by the telephony stack and is not visible to Key Mapper. I understand this is probably out of scope, but if there were ever a way to intercept it, it would be very welcome.
- The documentation could mention that double press / sequences with the "Voice assistant" trigger may not work with Bluetooth headsets for this reason — it would save other users some debugging time.
Expected behaviour
Double press, triple press and sequence triggers on the Voice assistant trigger work with Bluetooth HFP headsets the same way they work with on-screen or other assistant invocations.
Thank you again for your work on Key Mapper and for taking the time to read this. I fully understand that this is a niche use case and that your time is limited, so any feedback, even "not planned", would be appreciated. If there is anything else I can provide or test, please just let me know.
Kind regards,
MstrVolt.
App version
4.3.1 259
Device model and manufacturer
Galaxy S24 Ultra (SM-S928B/DS)
Extra info
Have you checked if an issue already exists for this feature request?
What feature would you like added?
Hello, and first of all, thank you very much for Key Mapper. It is an excellent, thoughtfully designed app, and the "Key Mapper as the device assistant" feature is exactly what lets people like me give a new life to hardware that otherwise cannot be remapped at all. I really appreciate the time and care you put into this project.
I would like to report a limitation I ran into and, I hope, give you enough information to evaluate it quickly. To note – being an amateur I have done an amount of testing I could, which are summarized below.
Summary
When Key Mapper is set as the default digital assistant and the assistant is invoked by a Bluetooth HFP headset button (the headset sends
AT+BVRA=1), the Android Bluetooth stack keeps the voice recognition session pending for ~5 seconds, waiting for the invoked app to callBluetoothHeadset.startVoiceRecognition(). Key Mapper does not do this, so the session is only closed by the stack's timeout (+BVRA: 0).During these ~5 seconds, all further button presses are lost — they never reach Android (no startVoiceRecognitionByHeadset log entry).
As a result:
Environment
AT+BLDN) on long press);Key map configuration
Voice assistant(Key Mapper assistant);Voice assistant×2, "Press in sequence" → "Next track": never fires;Steps to reproduce
AT+BVRA=1) on a button press;Voice assistanttrigger and any action (a toast or media action is enough).Logs (adb logcat, filtered)
Each press produces
startVoiceRecognitionByHeadset;the stack closes the session withBVRA : 0almost exactly 5 s later. Presses made in between do not appear at all.Intervals
startVoiceRecognitionByHeadsettoBVRA : 0: 5.04 s, 5.03 s, 5.04 s, 5.06 s. In the 21:02:40 series I pressed the button several times quickly; the only additional press registered is the one at 21:02:45.951 — right after the previous session timed out.(Full unfiltered log available on request).
Analysis
As far as I understand the AOSP HFP implementation (
HeadsetService/HeadsetStateMachine/HeadsetSystemInterface.activateVoiceRecognition()):AT+BVRA=1from the headset, the stack launches the voice command handler (Intent.ACTION_VOICE_COMMAND) — in this case Key Mapper's assistant.BluetoothHeadset.startVoiceRecognition(device)(the Javadoc ofactivateVoiceRecognition()says: "caller should wait forBluetoothHeadset#startVoiceRecognition(BluetoothDevice)callback").+BVRA: 0, and only then the headset/stack accept a newAT+BVRA=1.Since Key Mapper only needs the invocation event (not actual voice input), it never acknowledges the session, so every press "costs" the full timeout.
Things I tried that did not help:
• Tasker "Bluetooth Voice → Off" immediately after each invocation (triggered via the same broadcast) — no change, the session still times out after ~5 s.
• Tasker as the default assistant ("Assistance Request" event) — not triggered at all by the headset, presumably because Tasker handles ACTION_ASSIST but not ACTION_VOICE_COMMAND. This also suggests Key Mapper is one of the very few apps that can solve this.
Proposed fix
When the assistant is invoked and a connected HFP headset is the origin (or simply whenever a HFP headset is connected and the invocation came via
ACTION_VOICE_COMMAND), immediately acknowledge and terminate the Bluetooth voice recognition session, e.g.:Possible refinements, entirely at your discretion:
BluetoothHeadsetproxy open while the accessibility service runs, to avoid the async connection delay on every press.stopVoiceRecognition()turns out to be sufficient on some devices, the start call could be skipped.I have not verified this code on a device myself, so please treat it only as a pointer. I am, however, happy to test any debug/beta build on my setup and send back
adb logcatoutput (I have ADB set up and know the relevant log tags now, I hope).Related / nice to have
Expected behaviour
Double press, triple press and sequence triggers on the Voice assistant trigger work with Bluetooth HFP headsets the same way they work with on-screen or other assistant invocations.
Thank you again for your work on Key Mapper and for taking the time to read this. I fully understand that this is a niche use case and that your time is limited, so any feedback, even "not planned", would be appreciated. If there is anything else I can provide or test, please just let me know.
Kind regards,
MstrVolt.
App version
4.3.1 259
Device model and manufacturer
Galaxy S24 Ultra (SM-S928B/DS)
Extra info