Tests for misc keyboard focus navigation fixed
Bug: 62911028
Bug: 62943663
Test: TabHostTest#testKeyboardNavigation, tweaked
FocusFinderTest#testFindNextFocus to check touching case.
Change-Id: I625029502ae50edbdebb92a112569f49dee0be3f
diff --git a/tests/tests/view/res/layout/focus_finder_layout.xml b/tests/tests/view/res/layout/focus_finder_layout.xml
index 1dea684..8f4dffb 100644
--- a/tests/tests/view/res/layout/focus_finder_layout.xml
+++ b/tests/tests/view/res/layout/focus_finder_layout.xml
@@ -22,7 +22,8 @@
android:layout_alignParentTop="true">
<TableRow>
<android.view.cts.TestButton android:id="@+id/top_left_button"
- android:layout_width="60dp"
+ android:layout_width="20dp"
+ android:layout_marginRight="40dp"
android:layout_height="match_parent"
android:text="TL" />
<android.view.cts.TestButton android:id="@+id/top_right_button"
diff --git a/tests/tests/view/src/android/view/cts/FocusFinderTest.java b/tests/tests/view/src/android/view/cts/FocusFinderTest.java
index afda5aa..0f2119e 100644
--- a/tests/tests/view/src/android/view/cts/FocusFinderTest.java
+++ b/tests/tests/view/src/android/view/cts/FocusFinderTest.java
@@ -94,6 +94,10 @@
verifyNextFocus(null, View.FOCUS_LEFT, mBottomRight);
verifyNextFocus(null, View.FOCUS_UP, mBottomRight);
+ // Check that left/right traversal works when top/bottom borders are equal.
+ verifyNextFocus(mTopRight, View.FOCUS_LEFT, mTopLeft);
+ verifyNextFocus(mBottomLeft, View.FOCUS_RIGHT, mBottomRight);
+
// Edge-case where root has focus
mActivityRule.runOnUiThread(() -> {
mLayout.setFocusableInTouchMode(true);
diff --git a/tests/tests/widget/res/layout/tabhost_focus.xml b/tests/tests/widget/res/layout/tabhost_focus.xml
new file mode 100644
index 0000000..1229454
--- /dev/null
+++ b/tests/tests/widget/res/layout/tabhost_focus.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:id="@+id/before_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Before Button"/>
+
+ <TabHost
+ android:id="@android:id/tabhost"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TabWidget
+ android:id="@android:id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"></TabWidget>
+
+ <FrameLayout
+ android:id="@android:id/tabcontent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/tab1"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/tab1_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Tab 1 Content" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/tab2"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/tab2_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Tab 2 Content" />
+ </LinearLayout>
+ </FrameLayout>
+ </LinearLayout>
+ </TabHost>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/widget/src/android/widget/cts/TabHostTest.java b/tests/tests/widget/src/android/widget/cts/TabHostTest.java
index 765fd37..203657d 100644
--- a/tests/tests/widget/src/android/widget/cts/TabHostTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TabHostTest.java
@@ -35,6 +35,7 @@
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
+import android.view.KeyEvent;
import android.view.View;
import android.widget.ListView;
import android.widget.TabHost;
@@ -395,6 +396,48 @@
assertEquals(TAG_TAB2, tabHost.getCurrentTabTag());
}
+ @UiThreadTest
+ @Test
+ public void testKeyboardNavigation() throws Throwable {
+ mActivity.setContentView(R.layout.tabhost_focus);
+ TabHost tabHost = (TabHost) mActivity.findViewById(android.R.id.tabhost);
+ tabHost.setup();
+ TabSpec spec = tabHost.newTabSpec("Tab 1");
+ spec.setContent(R.id.tab1);
+ spec.setIndicator("Tab 1");
+ tabHost.addTab(spec);
+ spec = tabHost.newTabSpec("Tab 2");
+ spec.setContent(R.id.tab2);
+ spec.setIndicator("Tab 2");
+ tabHost.addTab(spec);
+ View topBut = mActivity.findViewById(R.id.before_button);
+ topBut.requestFocus();
+ assertTrue(topBut.isFocused());
+ mInstrumentation.waitForIdleSync();
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
+ View tabs = mActivity.findViewById(android.R.id.tabs);
+ assertTrue(tabs.hasFocus());
+ View firstTab = tabs.findFocus();
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
+ assertTrue(tabs.hasFocus());
+ int[] shiftKey = new int[]{KeyEvent.KEYCODE_SHIFT_LEFT};
+ sendKeyComboSync(KeyEvent.KEYCODE_TAB, shiftKey);
+ assertTrue(tabs.hasFocus());
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
+ assertTrue(tabs.hasFocus());
+
+ // non-navigation sends focus to content
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_E);
+ assertTrue(mActivity.findViewById(R.id.tab1_button).isFocused());
+ sendKeyComboSync(KeyEvent.KEYCODE_TAB, shiftKey);
+ assertTrue(tabs.hasFocus());
+
+ mActivityRule.runOnUiThread(() -> firstTab.requestFocus());
+ mInstrumentation.waitForIdleSync();
+ sendKeyComboSync(KeyEvent.KEYCODE_TAB, shiftKey);
+ assertTrue(topBut.isFocused());
+ }
+
private class MyTabContentFactoryText implements TabHost.TabContentFactory {
public View createTabContent(String tag) {
final TextView tv = new TextView(mActivity);
@@ -409,4 +452,48 @@
return lv;
}
}
+
+ private static int metaFromKey(int keyCode) {
+ switch(keyCode) {
+ case KeyEvent.KEYCODE_ALT_LEFT: return KeyEvent.META_ALT_LEFT_ON;
+ case KeyEvent.KEYCODE_ALT_RIGHT: return KeyEvent.META_ALT_RIGHT_ON;
+ case KeyEvent.KEYCODE_SHIFT_LEFT: return KeyEvent.META_SHIFT_LEFT_ON;
+ case KeyEvent.KEYCODE_SHIFT_RIGHT: return KeyEvent.META_SHIFT_RIGHT_ON;
+ case KeyEvent.KEYCODE_CTRL_LEFT: return KeyEvent.META_CTRL_LEFT_ON;
+ case KeyEvent.KEYCODE_CTRL_RIGHT: return KeyEvent.META_CTRL_RIGHT_ON;
+ case KeyEvent.KEYCODE_META_LEFT: return KeyEvent.META_META_LEFT_ON;
+ case KeyEvent.KEYCODE_META_RIGHT: return KeyEvent.META_META_RIGHT_ON;
+ }
+ return 0;
+ }
+
+ /**
+ * High-level method for sending a chorded key-combo (modifiers + key). This will send all the
+ * down and up key events as a user would press them (ie. all the modifiers get their own
+ * down and up events).
+ *
+ * @param keyCode The keycode to send while all meta keys are pressed.
+ * @param metaKeys An array of meta key *keycodes* (not modifiers).
+ */
+ private void sendKeyComboSync(int keyCode, int[] metaKeys) {
+ int metaState = 0;
+ if (metaKeys != null) {
+ for (int mk = 0; mk < metaKeys.length; ++mk) {
+ metaState |= metaFromKey(metaKeys[mk]);
+ mInstrumentation.sendKeySync(new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, metaKeys[mk],
+ 0, KeyEvent.normalizeMetaState(metaState)));
+ }
+ }
+ mInstrumentation.sendKeySync(new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0,
+ KeyEvent.normalizeMetaState(metaState)));
+ mInstrumentation.sendKeySync(new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0,
+ KeyEvent.normalizeMetaState(metaState)));
+ if (metaKeys != null) {
+ for (int mk = 0; mk < metaKeys.length; ++mk) {
+ metaState &= ~metaFromKey(metaKeys[mk]);
+ mInstrumentation.sendKeySync(new KeyEvent(0, 0, KeyEvent.ACTION_UP, metaKeys[mk], 0,
+ KeyEvent.normalizeMetaState(metaState)));
+ }
+ }
+ }
}