Home » Android » menubutton not working anymore after upgrade to cordova 5+cordova android 4.0.0

menubutton not working anymore after upgrade to cordova 5+cordova android 4.0.0

Posted by: admin May 14, 2020 Leave a comment

Questions:

I’ve recently upgraded to cordova 5 and removed/recreated android platform in version 4.0.0 and uninstalled/reinstalled all plugins.

I also had to upgrade android sdk to sdk 22 instead of 21.

Since the update, I’m no more able to catch the menubutton event as described in the cordova documentation.

As it’s still referenced in the edge docs, I assume it should still be working and I’ve seen nothing about this in the release notes.

back button is still working.

I tried to set the target-sdk to 19, it did not solve anything about the issue.

Edit:
I’ve dug into cordova source code and found in CordovaWebViewImpl.java I found a suspicious TODO comment :

   public void setButtonPlumbedToJs(int keyCode, boolean override) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_VOLUME_DOWN:
            case KeyEvent.KEYCODE_VOLUME_UP:
            case KeyEvent.KEYCODE_BACK:
                // TODO: Why are search and menu buttons handled separately?
                if (override) {
                    boundKeyCodes.add(keyCode);
                } else {
                    boundKeyCodes.remove(keyCode);
                }
                return;
            default:
                throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
        }
    }

Well my answer would be “IT SHOULDN’T!!!!”

Cordova makes a list of keycode to handle but does not add the menu button and later on the keycode is compared to KeyEvent.KEYCODE_MENU only after the keycode has been skipped because it’s not in the list.

I’ve tried to add a case for the menu button, but it turns out the function is only called with the code of the back button.

So now I know why it doesn’t work but still not how to fix it.

Edit 02/2016:
As per latest Jira, the support of the menubutton is now fixed in java part in Cordova Android 5.1.0 but still not initialized from the javascript.
For the moment, as indicated by Jira user Keith Wong, you need to add a javascript call before you add your event listener :

document.addEventListener("deviceready", function() {
    ...
    navigator.app.overrideButton("menubutton", true);  // <-- Add this line
    document.addEventListener("menubutton", yourCallbackFunction, false);
    ...
}, false);
How to&Answers:

clarent’s answer didn’t do it for me, the menu button still didn’t respond.

I tried several patches, one other suggestion to disable the boundKeyCodes check completely didn’t do it either, because then the backbutton behaviour would be compromised.

The clean way to get the old behaviour back should be as follows.
The boundKeyCodes check ensures, that custom behaviour is only executed when there actually is a custom event handler bound to the event. But binding an event handler to “menubutton” in your app’s JS code no longer triggers the menubutton key code to be added to the boundKeyCodes list.
This is because the setButtonPlumbedToJs method is never executed for the “menubutton” handler in the first place AND even if it would, the switch statement in this method doesn’t handle KEYCODE_MENU.

You can get that behaviour back quite easily, first you will have to apply the change suggested by clarent:

  1. Handle KEYCODE_MENU

in CordovaLib/src/org/apache/cordova/CoreAndroid.java (around line 357, setButtonPlumbedToJs) add a case statement after the KEYCODE_BACK entry like this:

public void setButtonPlumbedToJs(int keyCode, boolean override) {
  switch (keyCode) {
    case KeyEvent.KEYCODE_VOLUME_DOWN:
    case KeyEvent.KEYCODE_VOLUME_UP:
    case KeyEvent.KEYCODE_BACK:
    case KeyEvent.KEYCODE_MENU:
    // TODO: Why are search and menu buttons handled separately?
      if (override) {
        boundKeyCodes.add(keyCode);
      } else {
        boundKeyCodes.remove(keyCode);
      }
      return;
    default:
      throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
  }
}

Then ensure that setButtonPlumbedToJs actually gets executed. You need two more changes for that.

  1. Add framework handler

In CordovaLib/src/org/apache/cordova/CoreAndroid.java (around line 243, overrideButton) make the method look like this (add the last else-if clause):

public void overrideButton(String button, boolean override) {

  if (button.equals("volumeup")) {
    webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
  }
  else if (button.equals("volumedown")) {
    webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
  }
  else if (button.equals("menubutton")) {
    webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_MENU, override);
  }
}
  1. Add javascript handler call

In platform_www/cordova.js (around line 1532, bootstrap) change this line:

cordova.addDocumentEventHandler('menubutton');

to this:

var menuButtonChannel = cordova.addDocumentEventHandler('menubutton');
menuButtonChannel.onHasSubscribersChange = function() {
  exec(null, null, APP_PLUGIN_NAME, "overrideButton", ['menubutton', this.numHandlers == 1]);
};

This will trigger the frameworks overrideButton method as soon as an event handler is added to “menubutton”.

That should do it. I also added this solution as a comment to
https://issues.apache.org/jira/browse/CB-8921
and might be filing a pull request shortly.

Answer:

Just add to function setButtonPlumbedToJs one line : case KeyEvent.KEYCODE_MENU:

public void setButtonPlumbedToJs(int keyCode, boolean override) {
    switch (keyCode) {
        case KeyEvent.KEYCODE_VOLUME_DOWN:
        case KeyEvent.KEYCODE_VOLUME_UP:
        case KeyEvent.KEYCODE_BACK:
        case KeyEvent.KEYCODE_MENU:

So in onDispatchKeyEvent switch will work:

      } else if (boundKeyCodes.contains(keyCode)) {
                String eventName = null;
                switch (keyCode) {
                    case KeyEvent.KEYCODE_VOLUME_DOWN:
                        eventName = "volumedownbutton";
                        break;
                    case KeyEvent.KEYCODE_VOLUME_UP:
                        eventName = "volumeupbutton";
                        break;
                    case KeyEvent.KEYCODE_SEARCH:
                        eventName = "searchbutton";
                        break;
                    case KeyEvent.KEYCODE_MENU:
                        eventName = "menubutton";
                        break;
                    case KeyEvent.KEYCODE_BACK:
                        eventName = "backbutton";
                        break;
                }

Answer:

Now with cordova-android 5.1, the code has changed and my patch didn’t work any-more (and sadly, with no patch the menu button is still not working in this version).

As I wanted to be able to upgrade the platform without having to review the code each time, I searched for a new way to get the menu button working again.

In cordova android 5.1 it turns out that everything is here in the java code for the button to be working, except that the menu button key is never added to the boundKeyCoded array.

It turns out that this array needs to be filled by a call from javascript (which is done for the back button and volume button, but neither for the search button or the menu button).

The code that is missing is something like that :

exec(null, null, APP_PLUGIN_NAME, 'overrideButton', ['menubutton' , true]);

(a js call to the java function overrideButton from CoreAndroid.java to tell to add the menu button key to the boundKeyCodes array.

I think this call should be added to platform.js, but since platform.js is used to build cordova.js during the platform add, I decided to make a after_platform_add hook that patchs the cordova.js file.

The advantage of this hook is that there’s no java change and it should work even if you use a different webview like crosswalk.

So, first, in config.xml, in the android section add the hook :

<platform name="android">
    ....
    ....
    <hook type="after_platform_add" src="scripts/android/patch_menubutton.js" />
    ....
    ....
</platform>

Then, in the scripts folder add the hook file patch_menubutton.js :

#!/usr/bin/env node
module.exports = function(ctx) {
    var fs = ctx.requireCordovaModule('fs'),
        path = ctx.requireCordovaModule('path');
    var CordovaJSPath = path.join(ctx.opts.projectRoot, 'platforms/android/platform_www/cordova.js');
    var data = fs.readFileSync(CordovaJSPath, 'utf8');
    var result = data.replace(new RegExp("cordova\.addDocumentEventHandler\('menubutton'\);", "g"), "cordova.addDocumentEventHandler('menubutton'); exec(null, null, APP_PLUGIN_NAME, 'overrideButton', ['menubutton' , true]);");
    fs.writeFileSync(CordovaJSPath, result, 'utf8');
}

(it looks for the initialisation of the event handler for the menu button and appends the call to the overrideButton function, like described in the last part of FewKinG’s answer)