Fix back button not working with empty menu

Happens on ICS+. Caused by AppCompat letting dispatchKeyEvent()
to bubble up to PhoneWindow when the menu is empty. PhoneWindow's
KEYCODE_BACK handling then steals all events, not allowing the
Activity to onBackPressed().

Fixed by making AppCompat handle all KEYCODE_MENU events.

BUG: 23379766
Change-Id: I1d9bd25cb117cd19d1af9967964c0c1da67ddec4
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
index bc6af17..a438cd7 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
@@ -892,8 +892,10 @@
         switch (keyCode) {
             case KeyEvent.KEYCODE_MENU:
                 onKeyDownPanel(Window.FEATURE_OPTIONS_PANEL, event);
-                // Break, and let this fall through to the original callback
-                break;
+                // We need to return true here and not let it bubble up to the Window.
+                // For empty menus, PhoneWindow's KEYCODE_BACK handling will steals all events,
+                // not allowing the Activity to call onBackPressed().
+                return true;
         }
 
         // On API v7-10 we need to manually call onKeyShortcut() as this is not called
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
index 8adc940..44f9207 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
@@ -106,10 +106,30 @@
         getInstrumentation().waitForIdleSync();
         assertTrue("onMenuOpened called", getActivity().wasOnMenuOpenedCalled());
 
-        // TODO Re-enable this in v23
-        //getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
-        //getInstrumentation().waitForIdleSync();
-        //assertTrue("onPanelClosed called", getActivity().wasOnPanelClosedCalled());
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
+        getInstrumentation().waitForIdleSync();
+        assertTrue("onPanelClosed called", getActivity().wasOnPanelClosedCalled());
+    }
+
+    @Test
+    public void testBackPressWithMenuInvokesOnPanelClosed() throws InterruptedException {
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
+        getInstrumentation().waitForIdleSync();
+
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+        getInstrumentation().waitForIdleSync();
+        assertTrue("onPanelClosed called", getActivity().wasOnPanelClosedCalled());
+    }
+
+    @Test
+    public void testBackPressWithEmptyMenuDestroysActivity() throws InterruptedException {
+        repopulateWithEmptyMenu();
+
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
+        getInstrumentation().waitForIdleSync();
+
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+        waitAssertDestroyed();
     }
 
     @Test
@@ -141,4 +161,28 @@
         assertEquals("onKeyDown event matches", KeyEvent.KEYCODE_MENU, upEvent.getKeyCode());
     }
 
+    private void waitAssertDestroyed() throws InterruptedException {
+        int count = 0;
+        while (count++ < 10) {
+            if (!getActivity().isDestroyed()) {
+                Thread.sleep(50);
+            } else {
+                break;
+            }
+        }
+        assertTrue("Activity destroyed", getActivity().isDestroyed());
+    }
+
+    private void repopulateWithEmptyMenu() throws InterruptedException {
+        int count = 0;
+        getActivity().setShouldPopulateOptionsMenu(false);
+        while (count++ < 10) {
+            Menu menu = getActivity().getMenu();
+            if (menu == null || menu.size() != 0) {
+                Thread.sleep(50);
+            } else {
+                return;
+            }
+        }
+    }
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseTestActivity.java b/v7/appcompat/tests/src/android/support/v7/app/BaseTestActivity.java
index f336e91..ebbd0d5 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseTestActivity.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseTestActivity.java
@@ -37,6 +37,11 @@
     private boolean mOnMenuOpenedCalled;
     private boolean mOnPanelClosedCalled;
 
+    private boolean mShouldPopulateOptionsMenu = true;
+
+    private boolean mOnBackPressedCalled;
+    private boolean mDestroyed;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -113,8 +118,13 @@
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         mMenu = menu;
-        getMenuInflater().inflate(R.menu.sample_actions, menu);
-        return true;
+        if (mShouldPopulateOptionsMenu) {
+            getMenuInflater().inflate(R.menu.sample_actions, menu);
+            return true;
+        } else {
+            menu.clear();
+            return super.onCreateOptionsMenu(menu);
+        }
     }
 
     public boolean expandSearchView() {
@@ -140,4 +150,36 @@
         mOnMenuOpenedCalled = false;
         mOnPanelClosedCalled = false;
     }
+
+    public void setShouldPopulateOptionsMenu(boolean populate) {
+        mShouldPopulateOptionsMenu = populate;
+        if (mMenu != null) {
+            supportInvalidateOptionsMenu();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mDestroyed = true;
+    }
+
+    @Override
+    public void onBackPressed() {
+        super.onBackPressed();
+        mOnBackPressedCalled = true;
+    }
+
+    public boolean wasOnBackPressedCalled() {
+        return mOnBackPressedCalled;
+    }
+
+    public Menu getMenu() {
+        return mMenu;
+    }
+
+    @Override
+    public boolean isDestroyed() {
+        return mDestroyed;
+    }
 }