Add Alt + dpad hotkeys to ViewPager

Alt + right/left on a view within a ViewPager should now
switch pages.

Bug: 32673955
Test: v4.view.BaseViewPagerTest#testKeyboardNavigation
Change-Id: I811659be7acca4a6e1990844cfaeb95cb403e0cc
diff --git a/core-ui/java/android/support/v4/view/ViewPager.java b/core-ui/java/android/support/v4/view/ViewPager.java
index 60b1ee5..8cd973c 100644
--- a/core-ui/java/android/support/v4/view/ViewPager.java
+++ b/core-ui/java/android/support/v4/view/ViewPager.java
@@ -2749,10 +2749,18 @@
         if (event.getAction() == KeyEvent.ACTION_DOWN) {
             switch (event.getKeyCode()) {
                 case KeyEvent.KEYCODE_DPAD_LEFT:
-                    handled = arrowScroll(FOCUS_LEFT);
+                    if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
+                        handled = pageLeft();
+                    } else {
+                        handled = arrowScroll(FOCUS_LEFT);
+                    }
                     break;
                 case KeyEvent.KEYCODE_DPAD_RIGHT:
-                    handled = arrowScroll(FOCUS_RIGHT);
+                    if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
+                        handled = pageRight();
+                    } else {
+                        handled = arrowScroll(FOCUS_RIGHT);
+                    }
                     break;
                 case KeyEvent.KEYCODE_TAB:
                     if (event.hasNoModifiers()) {
diff --git a/core-ui/tests/java/android/support/v4/view/BaseViewPagerTest.java b/core-ui/tests/java/android/support/v4/view/BaseViewPagerTest.java
index a83a976..1544e76 100644
--- a/core-ui/tests/java/android/support/v4/view/BaseViewPagerTest.java
+++ b/core-ui/tests/java/android/support/v4/view/BaseViewPagerTest.java
@@ -16,6 +16,7 @@
 package android.support.v4.view;
 
 import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.pressKey;
 import static android.support.test.espresso.action.ViewActions.swipeLeft;
 import static android.support.test.espresso.action.ViewActions.swipeRight;
 import static android.support.test.espresso.assertion.PositionAssertions.isBelow;
@@ -46,6 +47,7 @@
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.is;
 import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -61,14 +63,18 @@
 import android.graphics.Color;
 import android.support.coreui.test.R;
 import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.action.EspressoKey;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.MediumTest;
 import android.support.v4.BaseInstrumentationTestCase;
 import android.support.v4.testutils.TestUtilsMatchers;
 import android.text.TextUtils;
 import android.util.Pair;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import org.junit.After;
@@ -202,6 +208,43 @@
         }
     }
 
+    protected static class ButtonPagerAdapter extends BasePagerAdapter<Integer> {
+        private ArrayList<Button[]> mButtons = new ArrayList<>();
+
+        @Override
+        public void add(String title, Integer content) {
+            super.add(title, content);
+            mButtons.add(new Button[3]);
+        }
+
+        @Override
+        public Object instantiateItem(ViewGroup container, int position) {
+            final LinearLayout view = new LinearLayout(container.getContext());
+            view.setBackgroundColor(mEntries.get(position).second);
+            view.setOrientation(LinearLayout.HORIZONTAL);
+            configureInstantiatedItem(view, position);
+
+            for (int i = 0; i < 3; ++i) {
+                Button but = new Button(container.getContext());
+                but.setText("" + i);
+                but.setFocusableInTouchMode(true);
+                view.addView(but, ViewGroup.LayoutParams.WRAP_CONTENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT);
+                mButtons.get(position)[i] = but;
+            }
+
+            // Unlike ListView adapters, the ViewPager adapter is responsible
+            // for adding the view to the container.
+            container.addView(view);
+
+            return new ViewHolder(view, position);
+        }
+
+        public View getButton(int page, int idx) {
+            return mButtons.get(page)[idx];
+        }
+    }
+
     public BaseViewPagerTest(Class<T> activityClass) {
         super(activityClass);
     }
@@ -1031,4 +1074,33 @@
         // Swipe one more page to the right
         verifyScrollCallbacksToLowerPage(wrap(swipeRight()), 0);
     }
+
+    @Test
+    @MediumTest
+    public void testKeyboardNavigation() {
+        ButtonPagerAdapter adapter = new ButtonPagerAdapter();
+        adapter.add("Red", Color.RED);
+        adapter.add("Green", Color.GREEN);
+        adapter.add("Blue", Color.BLUE);
+        onView(withId(R.id.pager)).perform(setAdapter(adapter), scrollToPage(0, false));
+        View firstButton = adapter.getButton(0, 0);
+        firstButton.requestFocus();
+        assertTrue(firstButton.isFocused());
+        assertEquals(0, mViewPager.getCurrentItem());
+
+        // Normal arrows should traverse contents first
+        onView(is(firstButton)).perform(pressKey(KeyEvent.KEYCODE_DPAD_RIGHT));
+        assertEquals(0, mViewPager.getCurrentItem());
+        assertTrue(adapter.getButton(0, 1).isFocused());
+
+        // Alt arrows should change page even if there are more focusables in that direction
+        onView(is(adapter.getButton(0, 1))).perform(pressKey(new EspressoKey.Builder()
+                .withAltPressed(true).withKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT).build()));
+        assertEquals(1, mViewPager.getCurrentItem());
+        assertTrue(adapter.getButton(1, 0).isFocused());
+
+        // Normal arrows should change page if there are no more focusables in that direction
+        onView(is(adapter.getButton(1, 0))).perform(pressKey(KeyEvent.KEYCODE_DPAD_LEFT));
+        assertEquals(0, mViewPager.getCurrentItem());
+    }
 }