Wednesday, August 29, 2012

Manually Adding the Legacy Overflow Button when targetSdkVersion >= 14

My company is in the process of converting our apps to use action bars, but we're not quite there yet.  In the meantime, we've depended on the legacy overflow button on devices without a dedicated menu button:



A problem has arisen because we've bumped our targetSdkVersion up to 16 (Jelly Bean).  It turns out there is a specific set of rules for when the legacy overflow button appears.  Our app was depending on the fact that our minSdkVersion was less than 10 and that our targetSdkVersion was between 11 and 13.  By bumping our targetSdkVersion up to 16, we no longer got the legacy overflow button.

This situation wouldn't be a problem if we showed an action bar, since the overflow menu would just appear there.  However, we're using a theme that disables the title, so our menu items disappeared completely.

I want to emphasize that the correct solution is to use an action bar with an overflow button.  But we're not done with the conversion to action bars yet and during development we'll want that menu to be accessible (all of our debug options go in there).  So I came up with a small hack that lets us enable the legacy overflow button manually.

It turns out there is a hidden Window flag - WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY - that determines whether the legacy overflow button is shown or not.  By using reflection, we can retrieve this field and add it to the Window's flags:

public static void addLegacyOverflowButton(Window window) {
  if (window.peekDecorView() == null) {
    throw new RuntimeException("Must call addLegacyOverflowButton() after setContentView()");
  }

  try {
    window.addFlags(WindowManager.LayoutParams.class.getField("FLAG_NEEDS_MENU_KEY").getInt(null));
  }
  catch (NoSuchFieldException e) {
    // Ignore since this field won't exist in most versions of Android
  }
  catch (IllegalAccessException e) {
    Log.w(TAG, "Could not access FLAG_NEEDS_MENU_KEY in addLegacyOverflowButton()", e);
  }
}

I added the RuntimeException above because you need to call this after the decor View is set (or else it does nothing).