blob: 5a225a1beee9f3e606a8517b11864541a81fe36c [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.widget.cts;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.Activity;
import android.app.ActivityGroup;
import android.app.Instrumentation;
import android.content.Intent;
import android.view.KeyEvent;
import android.view.View;
import android.widget.ListView;
import android.widget.TabHost;
import android.widget.TabHost.OnTabChangeListener;
import android.widget.TabHost.TabSpec;
import android.widget.TextView;
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.WidgetTestUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Test {@link TabHost}.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TabHostTest {
private static final String TAG_TAB1 = "tab 1";
private static final String TAG_TAB2 = "tab 2";
private static final int TAB_HOST_ID = android.R.id.tabhost;
private Instrumentation mInstrumentation;
private TabHostCtsActivity mActivity;
@Rule
public ActivityTestRule<TabHostCtsActivity> mActivityRule =
new ActivityTestRule<>(TabHostCtsActivity.class);
@Before
public void setup() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mActivity = mActivityRule.getActivity();
}
@Test
public void testConstructor() {
new TabHost(mActivity);
new TabHost(mActivity, null);
}
@Test
public void testNewTabSpec() {
TabHost tabHost = new TabHost(mActivity);
assertNotNull(tabHost.newTabSpec(TAG_TAB2));
}
@Test(expected=IllegalArgumentException.class)
public void testNewTabSpecWithNullTag() {
TabHost tabHost = new TabHost(mActivity);
tabHost.newTabSpec(null);
}
/*
* Check points:
* 1. the tabWidget view and tabContent view associated with tabHost are created.
* 2. no exception occurs when doing normal operation after setup().
*/
@Test
public void testSetup1() throws Throwable {
final Intent launchIntent = new Intent(Intent.ACTION_MAIN);
launchIntent.setClassName("android.widget.cts", CtsActivity.class.getName());
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Activity activity = mInstrumentation.startActivitySync(launchIntent);
mInstrumentation.waitForIdleSync();
mActivityRule.runOnUiThread(() -> {
activity.setContentView(R.layout.tabhost_layout);
TabHost tabHost = (TabHost) activity.findViewById(TAB_HOST_ID);
assertNull(tabHost.getTabWidget());
assertNull(tabHost.getTabContentView());
tabHost.setup();
assertNotNull(tabHost.getTabWidget());
assertNotNull(tabHost.getTabContentView());
TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB1);
tabSpec.setIndicator(TAG_TAB1);
tabSpec.setContent(new MyTabContentFactoryList());
tabHost.addTab(tabSpec);
tabHost.setCurrentTab(0);
});
mInstrumentation.waitForIdleSync();
activity.finish();
}
/*
* Check points:
* 1. the tabWidget view and tabContent view associated with tabHost are created.
* 2. no exception occurs when uses TabSpec.setContent(android.content.Intent) after setup().
*/
@Test
public void testSetup2() throws Throwable {
final Intent launchIntent = new Intent(Intent.ACTION_MAIN);
launchIntent.setClassName("android.widget.cts", ActivityGroup.class.getName());
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final ActivityGroup activity =
(ActivityGroup) mInstrumentation.startActivitySync(launchIntent);
mInstrumentation.waitForIdleSync();
mActivityRule.runOnUiThread(() -> {
activity.setContentView(R.layout.tabhost_layout);
TabHost tabHost = (TabHost) activity.findViewById(TAB_HOST_ID);
assertNull(tabHost.getTabWidget());
assertNull(tabHost.getTabContentView());
tabHost.setup(activity.getLocalActivityManager());
assertNotNull(tabHost.getTabWidget());
assertNotNull(tabHost.getTabContentView());
TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB1);
tabSpec.setIndicator(TAG_TAB1);
Intent intent = new Intent(Intent.ACTION_VIEW, null,
mActivity, CtsActivity.class);
tabSpec.setContent(intent);
tabHost.addTab(tabSpec);
tabHost.setCurrentTab(0);
});
mInstrumentation.waitForIdleSync();
activity.finish();
}
@UiThreadTest
@Test
public void testAddTab() {
TabHost tabHost = mActivity.getTabHost();
// there is a initial tab
assertEquals(1, tabHost.getTabWidget().getChildCount());
TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
tabSpec.setIndicator(TAG_TAB2);
tabSpec.setContent(new MyTabContentFactoryList());
tabHost.addTab(tabSpec);
assertEquals(2, tabHost.getTabWidget().getChildCount());
tabHost.setCurrentTab(1);
assertTrue(tabHost.getCurrentView() instanceof ListView);
assertEquals(TAG_TAB2, tabHost.getCurrentTabTag());
}
@UiThreadTest
@Test(expected=IllegalArgumentException.class)
public void testAddTabNoIndicatorNoContent() {
TabHost tabHost = mActivity.getTabHost();
tabHost.addTab(tabHost.newTabSpec("tab 3"));
}
@UiThreadTest
@Test(expected=IllegalArgumentException.class)
public void testAddTabNoContent() {
TabHost tabHost = mActivity.getTabHost();
tabHost.addTab(tabHost.newTabSpec("tab 3").setIndicator("tab 3"));
}
@UiThreadTest
@Test(expected=IllegalArgumentException.class)
public void testAddTabNoIndicator() {
TabHost tabHost = mActivity.getTabHost();
tabHost.addTab(tabHost.newTabSpec("tab 3").setContent(new MyTabContentFactoryText()));
}
@UiThreadTest
@Test(expected=NullPointerException.class)
public void testAddTabNull() {
TabHost tabHost = mActivity.getTabHost();
tabHost.addTab(null);
}
@UiThreadTest
@Test
public void testClearAllTabs() {
TabHost tabHost = mActivity.getTabHost();
MyTabContentFactoryText tcf = new MyTabContentFactoryText();
// add two additional tabs
tabHost.addTab(tabHost.newTabSpec(TAG_TAB1).setIndicator(TAG_TAB1).setContent(tcf));
tabHost.addTab(tabHost.newTabSpec(TAG_TAB2).setIndicator(TAG_TAB2).setContent(tcf));
assertEquals(3, tabHost.getTabWidget().getChildCount());
assertEquals(3, tabHost.getTabContentView().getChildCount());
assertEquals(0, tabHost.getCurrentTab());
assertNotNull(tabHost.getCurrentView());
tabHost.clearAllTabs();
assertEquals(0, tabHost.getTabWidget().getChildCount());
assertEquals(0, tabHost.getTabContentView().getChildCount());
assertEquals(-1, tabHost.getCurrentTab());
assertNull(tabHost.getCurrentView());
}
@Test
public void testGetTabWidget() {
TabHost tabHost = mActivity.getTabHost();
// The attributes defined in tabhost_layout.xml
assertEquals(android.R.id.tabs, tabHost.getTabWidget().getId());
WidgetTestUtils.assertScaledPixels(1, tabHost.getTabWidget().getPaddingLeft(), mActivity);
WidgetTestUtils.assertScaledPixels(1, tabHost.getTabWidget().getPaddingRight(), mActivity);
WidgetTestUtils.assertScaledPixels(4, tabHost.getTabWidget().getPaddingTop(), mActivity);
}
@UiThreadTest
@Test
public void testAccessCurrentTab() {
TabHost tabHost = mActivity.getTabHost();
assertEquals(0, tabHost.getCurrentTab());
// normal value
TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
tabSpec.setIndicator(TAG_TAB2);
tabSpec.setContent(new MyTabContentFactoryText());
tabHost.addTab(tabSpec);
tabHost.setCurrentTab(1);
assertEquals(1, tabHost.getCurrentTab());
tabHost.setCurrentTab(0);
assertEquals(0, tabHost.getCurrentTab());
// exceptional value
tabHost.setCurrentTab(tabHost.getTabWidget().getChildCount() + 1);
assertEquals(0, tabHost.getCurrentTab());
tabHost.setCurrentTab(-1);
assertEquals(0, tabHost.getCurrentTab());
}
@UiThreadTest
@Test
public void testGetCurrentTabView() {
TabHost tabHost = mActivity.getTabHost();
// current tab view is the first child of tabWidget.
assertSame(tabHost.getTabWidget().getChildAt(0), tabHost.getCurrentTabView());
TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
tabSpec.setIndicator(TAG_TAB2);
tabSpec.setContent(new MyTabContentFactoryText());
tabHost.addTab(tabSpec);
tabHost.setCurrentTab(1);
// current tab view is the second child of tabWidget.
assertSame(tabHost.getTabWidget().getChildAt(1), tabHost.getCurrentTabView());
}
@UiThreadTest
@Test
public void testGetCurrentView() {
TabHost tabHost = mActivity.getTabHost();
TextView textView = (TextView) tabHost.getCurrentView();
assertEquals(TabHostCtsActivity.INITIAL_VIEW_TEXT, textView.getText().toString());
TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
tabSpec.setIndicator(TAG_TAB2);
tabSpec.setContent(new MyTabContentFactoryList());
tabHost.addTab(tabSpec);
tabHost.setCurrentTab(1);
assertTrue(tabHost.getCurrentView() instanceof ListView);
}
@UiThreadTest
@Test
public void testSetCurrentTabByTag() {
TabHost tabHost = mActivity.getTabHost();
// set CurrentTab
TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
tabSpec.setIndicator(TAG_TAB2);
tabSpec.setContent(new MyTabContentFactoryText());
tabHost.addTab(tabSpec);
tabHost.setCurrentTabByTag(TAG_TAB2);
assertEquals(1, tabHost.getCurrentTab());
tabHost.setCurrentTabByTag(TabHostCtsActivity.INITIAL_TAB_TAG);
assertEquals(0, tabHost.getCurrentTab());
// exceptional value
tabHost.setCurrentTabByTag(null);
assertEquals(0, tabHost.getCurrentTab());
tabHost.setCurrentTabByTag("unknown tag");
assertEquals(0, tabHost.getCurrentTab());
}
@UiThreadTest
@Test
public void testGetTabContentView() {
TabHost tabHost = mActivity.getTabHost();
assertEquals(3, tabHost.getTabContentView().getChildCount());
TextView child0 = (TextView) tabHost.getTabContentView().getChildAt(0);
assertEquals(mActivity.getResources().getString(R.string.hello_world),
child0.getText().toString());
assertTrue(tabHost.getTabContentView().getChildAt(1) instanceof ListView);
TextView child2 = (TextView) tabHost.getTabContentView().getChildAt(2);
tabHost.setCurrentTab(0);
assertEquals(TabHostCtsActivity.INITIAL_VIEW_TEXT, child2.getText().toString());
TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
tabSpec.setIndicator(TAG_TAB2);
tabSpec.setContent(new MyTabContentFactoryList());
tabHost.addTab(tabSpec);
assertEquals(3, tabHost.getTabContentView().getChildCount());
tabHost.setCurrentTab(1);
assertEquals(4, tabHost.getTabContentView().getChildCount());
child0 = (TextView) tabHost.getTabContentView().getChildAt(0);
assertEquals(mActivity.getResources().getString(R.string.hello_world),
child0.getText().toString());
assertTrue(tabHost.getTabContentView().getChildAt(1) instanceof ListView);
child2 = (TextView) tabHost.getTabContentView().getChildAt(2);
tabHost.setCurrentTab(0);
assertEquals(TabHostCtsActivity.INITIAL_VIEW_TEXT, child2.getText().toString());
}
/**
* Check points:
* 1. the specified callback should be invoked when the selected state of any of the items
* in this list changes
*/
@UiThreadTest
@Test
public void testSetOnTabChangedListener() {
TabHost tabHost = mActivity.getTabHost();
// add a tab, and change current tab to the new tab
OnTabChangeListener mockTabChangeListener = mock(OnTabChangeListener.class);
tabHost.setOnTabChangedListener(mockTabChangeListener);
TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
tabSpec.setIndicator(TAG_TAB2);
tabSpec.setContent(new MyTabContentFactoryList());
tabHost.addTab(tabSpec);
tabHost.setCurrentTab(1);
verify(mockTabChangeListener, times(1)).onTabChanged(TAG_TAB2);
// change current tab to the first one
tabHost.setCurrentTab(0);
verify(mockTabChangeListener, times(1)).onTabChanged(TabHostCtsActivity.INITIAL_TAB_TAG);
// set the same tab
tabHost.setCurrentTab(0);
verifyNoMoreInteractions(mockTabChangeListener);
}
@UiThreadTest
@Test
public void testGetCurrentTabTag() {
TabHost tabHost = mActivity.getTabHost();
assertEquals(TabHostCtsActivity.INITIAL_TAB_TAG, tabHost.getCurrentTabTag());
TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
tabSpec.setIndicator(TAG_TAB2);
tabSpec.setContent(new MyTabContentFactoryList());
tabHost.addTab(tabSpec);
tabHost.setCurrentTab(1);
assertEquals(TAG_TAB2, tabHost.getCurrentTabTag());
}
@Test
public void testKeyboardNavigation() throws Throwable {
mActivityRule.runOnUiThread(() -> {
mActivity.setContentView(R.layout.tabhost_focus);
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(mActivity.findViewById(R.id.before_button).isFocused());
}
private class MyTabContentFactoryText implements TabHost.TabContentFactory {
public View createTabContent(String tag) {
final TextView tv = new TextView(mActivity);
tv.setText(tag);
return tv;
}
}
private class MyTabContentFactoryList implements TabHost.TabContentFactory {
public View createTabContent(String tag) {
final ListView lv = new ListView(mActivity);
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)));
}
}
}
}