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

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.

  • How to change programmatically the primary color in Android L
  • Android: Use icon as back button without reloading previous activity
  • emulator-5554 disconnected! Cancelling 'com.example.merhaba.Main activity launch'!
  • NDK build error
  • Changing EditText height and width on Runtime with some animation
  • How do I discover memory usage of my application in Android?
  • 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);
    

    Related posts:

    Kitkat, error while trying to load an image
    How well supported is pressure sensitivity across Android devices?
    RecyclerView - Where should I handle its click events?
    Effects of Negative padding
    android.util.Log when publishing - what can I do / not do
    add image to twitter share intent android
  • Android SDK Build Tool Multiple Versions
  • get bitmap from visible zoomed image android
  • Android ViewPager setCurrentItem not working after onResume
  • static variables, what is their life span?
  • What is the best practice for naming the id of XML Element in Android?
  • Add local javadoc to local aar in Android-Studio
  • 3 Solutions collect form web for “menubutton not working anymore after upgrade to cordova 5+cordova android 4.0.0”

    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.

    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;
                    }
    

    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)

    Android Babe is a Google Android Fan, All about Android Phones, Android Wear, Android Dev and Android Games Apps and so on.