{"record":{"id":"63fdf827bbc92341","repo":"apache/cordova-android","slug":"unsupported-keycode-keycode","errorCode":null,"errorMessage":"Unsupported keycode: ${keyCode}","messagePattern":"Unsupported keycode: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"framework/src/org/apache/cordova/CordovaWebViewImpl.java","lineNumber":420,"sourceCode":"        appPlugin.fireJavascriptEvent(event);\n    }\n\n    @Override\n    public void setButtonPlumbedToJs(int keyCode, boolean override) {\n        switch (keyCode) {\n            case KeyEvent.KEYCODE_VOLUME_DOWN:\n            case KeyEvent.KEYCODE_VOLUME_UP:\n            case KeyEvent.KEYCODE_BACK:\n            case KeyEvent.KEYCODE_MENU:\n                // TODO: Why are search and menu buttons handled separately?\n                if (override) {\n                    boundKeyCodes.add(keyCode);\n                } else {\n                    boundKeyCodes.remove(keyCode);\n                }\n                return;\n            default:\n                throw new IllegalArgumentException(\"Unsupported keycode: \" + keyCode);\n        }\n    }\n\n    @Override\n    public boolean isButtonPlumbedToJs(int keyCode) {\n        return boundKeyCodes.contains(keyCode);\n    }\n\n    @Override\n    public Object postMessage(String id, Object data) {\n        return pluginManager.postMessage(id, data);\n    }\n\n    // Engine method proxies:\n    @Override\n    public String getUrl() {\n        return engine.getUrl();\n    }","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/apache/cordova-android/blob/7c1e190064e349ffa4bbc6ac37b77cd773e4dbd3/framework/src/org/apache/cordova/CordovaWebViewImpl.java#L402-L438","documentation":"CordovaWebViewImpl.setButtonPlumbedToJs(int keyCode, boolean override) whitelists exactly four Android keycodes: KEYCODE_VOLUME_UP (24), KEYCODE_VOLUME_DOWN (25), KEYCODE_BACK (4), and KEYCODE_MENU (82). Every other keycode falls into the switch's default branch and throws IllegalArgumentException. The built-in callers — CoreAndroid's back-button override and overrideButton(\"volumeup\"/\"volumedown\"/\"menubutton\") — only ever pass supported codes, so this is reachable only from plugin or app code calling the CordovaWebView API directly with an unlisted keycode.","triggerScenarios":"A plugin calls webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_HOME, true), or passes KEYCODE_SEARCH, KEYCODE_CAMERA, KEYCODE_APP_SWITCH, KEYCODE_DPAD_* (Android TV remotes), KEYCODE_MEDIA_*, or a raw int copied from elsewhere instead of a KeyEvent constant.","commonSituations":"Android TV / set-top-box plugins trying to plumb remote-control keys through Cordova; code ported from older Cordova forks where extra buttons seemed interceptable; keycodes received from JavaScript or config data passed straight into the native API without validation.","solutions":["Pass only KeyEvent.KEYCODE_BACK, KEYCODE_MENU, KEYCODE_VOLUME_UP, or KEYCODE_VOLUME_DOWN.","If you need a different hardware key, intercept it yourself by overriding dispatchKeyEvent/onKeyDown in the Activity or a custom View — Cordova's plumbing API will not carry it.","If the keycode arrives from external data, whitelist-check it (see type guard) before calling setButtonPlumbedToJs."],"exampleFix":"// before\nwebView.setButtonPlumbedToJs(KeyEvent.KEYCODE_HOME, true); // throws IllegalArgumentException\n\n// after\nif (isPlumbableKeyCode(keyCode)) {\n    webView.setButtonPlumbedToJs(keyCode, true);\n} else {\n    // handle this key in your own dispatchKeyEvent/onKeyDown instead\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"private static final Set<Integer> PLUMBABLE_KEY_CODES = new HashSet<>(Arrays.asList(\n        KeyEvent.KEYCODE_BACK,        // 4\n        KeyEvent.KEYCODE_MENU,        // 82\n        KeyEvent.KEYCODE_VOLUME_UP,   // 24\n        KeyEvent.KEYCODE_VOLUME_DOWN // 25\n));\n\nstatic boolean isPlumbableKeyCode(int keyCode) {\n    return PLUMBABLE_KEY_CODES.contains(keyCode);\n}","tryCatchPattern":"// Only when the keycode is dynamic and you cannot pre-filter:\ntry {\n    webView.setButtonPlumbedToJs(keyCode, override);\n} catch (IllegalArgumentException e) {\n    LOG.w(TAG, \"Key not plumbable to JS, ignoring: \" + keyCode);\n}","preventionTips":["Treat setButtonPlumbedToJs as supporting exactly four keys: BACK, MENU, VOLUME_UP, VOLUME_DOWN.","Always use KeyEvent.KEYCODE_* constants, never raw ints or values received from JavaScript.","For TV remotes, media keys, or hardware buttons outside the whitelist, implement your own dispatchKeyEvent/onKeyDown handling."],"tags":["keyevent","android","input","plugin-development","api-misuse"],"backgroundTag":"unsupported-keycode","analyzedSha":"7c1e190064e349ffa4bbc6ac37b77cd773e4dbd3","analyzedAt":"2026-08-22T04:57:58.868Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}