App 2.7.1: voice-key double press works; floating button removed
The car's key report, from the on-screen key log in 2.7.0: android.intent.extra.hardkey.keycode = 287 (Integer), android.intent.extra.hardkey.down (Boolean; one report on press, one on release) and android.intent.extra.hardkey.longpress (Boolean). 2.7.0 only looked for short names like "keyCode", so it never found the code. SteeringKeyReport now matches extras by the last part of their name, case-insensitively, and is Android-free; its tests include the exact double press logged on the car (releases 279 ms apart). Replayed in the car's format on the emulator: double press opens the app, single and long presses do not. The floating button is removed (class, geometry and test, the overlay permission, setting, switch and strings): it did not show over the MG4 launcher. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Reads one steering-wheel key report from the broadcast's extras.
|
||||
*
|
||||
* Seen on the MG4 (2026-10-17, on-screen key log): the head unit sends
|
||||
* android.intent.extra.hardkey.keycode = 287 (Integer)
|
||||
* android.intent.extra.hardkey.down = true (Boolean, then false on release)
|
||||
* android.intent.extra.hardkey.longpress = false (Boolean)
|
||||
* Names are matched on their last dot-separated part, case-insensitively, so this exact form
|
||||
* and simpler ones ("keyCode", "down", …) are both understood.
|
||||
*
|
||||
* Android-free (the extras arrive as a plain map), unit-tested.
|
||||
*/
|
||||
final class SteeringKeyReport {
|
||||
|
||||
final Integer code;
|
||||
final Boolean down;
|
||||
final boolean longPress;
|
||||
|
||||
SteeringKeyReport(Integer code, Boolean down, boolean longPress) {
|
||||
this.code = code;
|
||||
this.down = down;
|
||||
this.longPress = longPress;
|
||||
}
|
||||
|
||||
private static final String[] CODE_NAMES = {"keycode", "key_code", "keyvalue", "key_value", "code", "key"};
|
||||
private static final String[] ACTION_NAMES = {"keyaction", "key_action", "action", "keystate", "key_state",
|
||||
"state", "event", "type"};
|
||||
private static final String[] DOWN_NAMES = {"down", "isdown", "pressed", "ispressed"};
|
||||
private static final String[] LONG_NAMES = {"longpress", "islongpress", "long_press", "islong", "long"};
|
||||
|
||||
static SteeringKeyReport parse(Map<String, Object> extras) {
|
||||
Map<String, Object> byName = new HashMap<>();
|
||||
if (extras != null) {
|
||||
for (Map.Entry<String, Object> e : extras.entrySet()) {
|
||||
String key = e.getKey();
|
||||
String last = key.substring(key.lastIndexOf('.') + 1).toLowerCase(Locale.US);
|
||||
byName.putIfAbsent(last, e.getValue());
|
||||
}
|
||||
}
|
||||
Integer code = firstInt(byName, CODE_NAMES);
|
||||
Boolean down = null;
|
||||
boolean longPress = false;
|
||||
// KeyEvent's own convention for a numeric action: 0 down, 1 up; anything else is long.
|
||||
Integer action = firstInt(byName, ACTION_NAMES);
|
||||
if (action != null) {
|
||||
if (action == 0) down = true;
|
||||
else if (action == 1) down = false;
|
||||
else longPress = true;
|
||||
}
|
||||
Boolean explicitDown = firstBoolean(byName, DOWN_NAMES);
|
||||
if (explicitDown != null) down = explicitDown;
|
||||
Boolean explicitLong = firstBoolean(byName, LONG_NAMES);
|
||||
if (explicitLong != null) longPress = explicitLong;
|
||||
return new SteeringKeyReport(code, down, longPress);
|
||||
}
|
||||
|
||||
private static Integer firstInt(Map<String, Object> byName, String[] names) {
|
||||
for (String n : names) {
|
||||
Object v = byName.get(n);
|
||||
if (v instanceof Integer) return (Integer) v;
|
||||
if (v instanceof Long) return (int) (long) (Long) v;
|
||||
if (v instanceof String) {
|
||||
try { return Integer.parseInt(((String) v).trim()); } catch (NumberFormatException ignored) { }
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Boolean firstBoolean(Map<String, Object> byName, String[] names) {
|
||||
for (String n : names) {
|
||||
Object v = byName.get(n);
|
||||
if (v instanceof Boolean) return (Boolean) v;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user