Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget.cts;
     18 
     19 import static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertNotNull;
     21 import static org.junit.Assert.assertNull;
     22 import static org.junit.Assert.assertSame;
     23 import static org.junit.Assert.assertTrue;
     24 import static org.mockito.Mockito.mock;
     25 import static org.mockito.Mockito.times;
     26 import static org.mockito.Mockito.verify;
     27 import static org.mockito.Mockito.verifyNoMoreInteractions;
     28 
     29 import android.app.Activity;
     30 import android.app.ActivityGroup;
     31 import android.app.Instrumentation;
     32 import android.content.Intent;
     33 import android.view.KeyEvent;
     34 import android.view.View;
     35 import android.widget.ListView;
     36 import android.widget.TabHost;
     37 import android.widget.TabHost.OnTabChangeListener;
     38 import android.widget.TabHost.TabSpec;
     39 import android.widget.TextView;
     40 
     41 import androidx.test.InstrumentationRegistry;
     42 import androidx.test.annotation.UiThreadTest;
     43 import androidx.test.filters.SmallTest;
     44 import androidx.test.rule.ActivityTestRule;
     45 import androidx.test.runner.AndroidJUnit4;
     46 
     47 import com.android.compatibility.common.util.WidgetTestUtils;
     48 
     49 import org.junit.Before;
     50 import org.junit.Rule;
     51 import org.junit.Test;
     52 import org.junit.runner.RunWith;
     53 
     54 /**
     55  * Test {@link TabHost}.
     56  */
     57 @SmallTest
     58 @RunWith(AndroidJUnit4.class)
     59 public class TabHostTest {
     60     private static final String TAG_TAB1 = "tab 1";
     61     private static final String TAG_TAB2 = "tab 2";
     62     private static final int TAB_HOST_ID = android.R.id.tabhost;
     63 
     64     private Instrumentation mInstrumentation;
     65     private TabHostCtsActivity mActivity;
     66 
     67     @Rule
     68     public ActivityTestRule<TabHostCtsActivity> mActivityRule =
     69             new ActivityTestRule<>(TabHostCtsActivity.class);
     70 
     71     @Before
     72     public void setup() {
     73         mInstrumentation = InstrumentationRegistry.getInstrumentation();
     74         mActivity = mActivityRule.getActivity();
     75     }
     76 
     77     @Test
     78     public void testConstructor() {
     79         new TabHost(mActivity);
     80 
     81         new TabHost(mActivity, null);
     82     }
     83 
     84     @Test
     85     public void testNewTabSpec() {
     86         TabHost tabHost = new TabHost(mActivity);
     87 
     88         assertNotNull(tabHost.newTabSpec(TAG_TAB2));
     89     }
     90 
     91     @Test(expected=IllegalArgumentException.class)
     92     public void testNewTabSpecWithNullTag() {
     93         TabHost tabHost = new TabHost(mActivity);
     94 
     95         tabHost.newTabSpec(null);
     96     }
     97 
     98     /*
     99      * Check points:
    100      * 1. the tabWidget view and tabContent view associated with tabHost are created.
    101      * 2. no exception occurs when doing normal operation after setup().
    102      */
    103     @Test
    104     public void testSetup1() throws Throwable {
    105         final Intent launchIntent = new Intent(Intent.ACTION_MAIN);
    106         launchIntent.setClassName("android.widget.cts", CtsActivity.class.getName());
    107         launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    108         final Activity activity = mInstrumentation.startActivitySync(launchIntent);
    109         mInstrumentation.waitForIdleSync();
    110 
    111         mActivityRule.runOnUiThread(() -> {
    112             activity.setContentView(R.layout.tabhost_layout);
    113 
    114             TabHost tabHost = (TabHost) activity.findViewById(TAB_HOST_ID);
    115             assertNull(tabHost.getTabWidget());
    116             assertNull(tabHost.getTabContentView());
    117             tabHost.setup();
    118             assertNotNull(tabHost.getTabWidget());
    119             assertNotNull(tabHost.getTabContentView());
    120 
    121             TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB1);
    122             tabSpec.setIndicator(TAG_TAB1);
    123             tabSpec.setContent(new MyTabContentFactoryList());
    124             tabHost.addTab(tabSpec);
    125             tabHost.setCurrentTab(0);
    126         });
    127         mInstrumentation.waitForIdleSync();
    128 
    129         activity.finish();
    130     }
    131 
    132     /*
    133      * Check points:
    134      * 1. the tabWidget view and tabContent view associated with tabHost are created.
    135      * 2. no exception occurs when uses TabSpec.setContent(android.content.Intent) after setup().
    136      */
    137     @Test
    138     public void testSetup2() throws Throwable {
    139         final Intent launchIntent = new Intent(Intent.ACTION_MAIN);
    140         launchIntent.setClassName("android.widget.cts", ActivityGroup.class.getName());
    141         launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    142         final ActivityGroup activity =
    143                 (ActivityGroup) mInstrumentation.startActivitySync(launchIntent);
    144         mInstrumentation.waitForIdleSync();
    145 
    146         mActivityRule.runOnUiThread(() -> {
    147             activity.setContentView(R.layout.tabhost_layout);
    148 
    149             TabHost tabHost = (TabHost) activity.findViewById(TAB_HOST_ID);
    150             assertNull(tabHost.getTabWidget());
    151             assertNull(tabHost.getTabContentView());
    152             tabHost.setup(activity.getLocalActivityManager());
    153             assertNotNull(tabHost.getTabWidget());
    154             assertNotNull(tabHost.getTabContentView());
    155 
    156             TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB1);
    157             tabSpec.setIndicator(TAG_TAB1);
    158             Intent intent = new Intent(Intent.ACTION_VIEW, null,
    159                     mActivity, CtsActivity.class);
    160             tabSpec.setContent(intent);
    161             tabHost.addTab(tabSpec);
    162             tabHost.setCurrentTab(0);
    163         });
    164         mInstrumentation.waitForIdleSync();
    165 
    166         activity.finish();
    167     }
    168 
    169     @UiThreadTest
    170     @Test
    171     public void testAddTab() {
    172         TabHost tabHost = mActivity.getTabHost();
    173         // there is a initial tab
    174         assertEquals(1, tabHost.getTabWidget().getChildCount());
    175 
    176         TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
    177         tabSpec.setIndicator(TAG_TAB2);
    178         tabSpec.setContent(new MyTabContentFactoryList());
    179         tabHost.addTab(tabSpec);
    180         assertEquals(2, tabHost.getTabWidget().getChildCount());
    181         tabHost.setCurrentTab(1);
    182         assertTrue(tabHost.getCurrentView() instanceof ListView);
    183         assertEquals(TAG_TAB2, tabHost.getCurrentTabTag());
    184     }
    185 
    186     @UiThreadTest
    187     @Test(expected=IllegalArgumentException.class)
    188     public void testAddTabNoIndicatorNoContent() {
    189         TabHost tabHost = mActivity.getTabHost();
    190         tabHost.addTab(tabHost.newTabSpec("tab 3"));
    191     }
    192 
    193     @UiThreadTest
    194     @Test(expected=IllegalArgumentException.class)
    195     public void testAddTabNoContent() {
    196         TabHost tabHost = mActivity.getTabHost();
    197         tabHost.addTab(tabHost.newTabSpec("tab 3").setIndicator("tab 3"));
    198     }
    199 
    200     @UiThreadTest
    201     @Test(expected=IllegalArgumentException.class)
    202     public void testAddTabNoIndicator() {
    203         TabHost tabHost = mActivity.getTabHost();
    204         tabHost.addTab(tabHost.newTabSpec("tab 3").setContent(new MyTabContentFactoryText()));
    205     }
    206 
    207     @UiThreadTest
    208     @Test(expected=NullPointerException.class)
    209     public void testAddTabNull() {
    210         TabHost tabHost = mActivity.getTabHost();
    211         tabHost.addTab(null);
    212     }
    213 
    214     @UiThreadTest
    215     @Test
    216     public void testClearAllTabs() {
    217         TabHost tabHost = mActivity.getTabHost();
    218         MyTabContentFactoryText tcf = new MyTabContentFactoryText();
    219         // add two additional tabs
    220         tabHost.addTab(tabHost.newTabSpec(TAG_TAB1).setIndicator(TAG_TAB1).setContent(tcf));
    221         tabHost.addTab(tabHost.newTabSpec(TAG_TAB2).setIndicator(TAG_TAB2).setContent(tcf));
    222         assertEquals(3, tabHost.getTabWidget().getChildCount());
    223         assertEquals(3, tabHost.getTabContentView().getChildCount());
    224         assertEquals(0, tabHost.getCurrentTab());
    225         assertNotNull(tabHost.getCurrentView());
    226 
    227         tabHost.clearAllTabs();
    228 
    229         assertEquals(0, tabHost.getTabWidget().getChildCount());
    230         assertEquals(0, tabHost.getTabContentView().getChildCount());
    231         assertEquals(-1, tabHost.getCurrentTab());
    232         assertNull(tabHost.getCurrentView());
    233     }
    234 
    235     @Test
    236     public void testGetTabWidget() {
    237         TabHost tabHost = mActivity.getTabHost();
    238 
    239         // The attributes defined in tabhost_layout.xml
    240         assertEquals(android.R.id.tabs, tabHost.getTabWidget().getId());
    241         WidgetTestUtils.assertScaledPixels(1, tabHost.getTabWidget().getPaddingLeft(), mActivity);
    242         WidgetTestUtils.assertScaledPixels(1, tabHost.getTabWidget().getPaddingRight(), mActivity);
    243         WidgetTestUtils.assertScaledPixels(4, tabHost.getTabWidget().getPaddingTop(), mActivity);
    244     }
    245 
    246     @UiThreadTest
    247     @Test
    248     public void testAccessCurrentTab() {
    249         TabHost tabHost = mActivity.getTabHost();
    250         assertEquals(0, tabHost.getCurrentTab());
    251 
    252         // normal value
    253         TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
    254         tabSpec.setIndicator(TAG_TAB2);
    255         tabSpec.setContent(new MyTabContentFactoryText());
    256         tabHost.addTab(tabSpec);
    257         tabHost.setCurrentTab(1);
    258         assertEquals(1, tabHost.getCurrentTab());
    259         tabHost.setCurrentTab(0);
    260         assertEquals(0, tabHost.getCurrentTab());
    261 
    262         // exceptional value
    263         tabHost.setCurrentTab(tabHost.getTabWidget().getChildCount() + 1);
    264         assertEquals(0, tabHost.getCurrentTab());
    265         tabHost.setCurrentTab(-1);
    266         assertEquals(0, tabHost.getCurrentTab());
    267     }
    268 
    269     @UiThreadTest
    270     @Test
    271     public void testGetCurrentTabView() {
    272         TabHost tabHost = mActivity.getTabHost();
    273         // current tab view is the first child of tabWidget.
    274         assertSame(tabHost.getTabWidget().getChildAt(0), tabHost.getCurrentTabView());
    275 
    276         TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
    277         tabSpec.setIndicator(TAG_TAB2);
    278         tabSpec.setContent(new MyTabContentFactoryText());
    279         tabHost.addTab(tabSpec);
    280         tabHost.setCurrentTab(1);
    281         // current tab view is the second child of tabWidget.
    282         assertSame(tabHost.getTabWidget().getChildAt(1), tabHost.getCurrentTabView());
    283     }
    284 
    285     @UiThreadTest
    286     @Test
    287     public void testGetCurrentView() {
    288         TabHost tabHost = mActivity.getTabHost();
    289         TextView textView = (TextView) tabHost.getCurrentView();
    290         assertEquals(TabHostCtsActivity.INITIAL_VIEW_TEXT, textView.getText().toString());
    291 
    292         TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
    293         tabSpec.setIndicator(TAG_TAB2);
    294         tabSpec.setContent(new MyTabContentFactoryList());
    295         tabHost.addTab(tabSpec);
    296         tabHost.setCurrentTab(1);
    297         assertTrue(tabHost.getCurrentView() instanceof ListView);
    298     }
    299 
    300     @UiThreadTest
    301     @Test
    302     public void testSetCurrentTabByTag() {
    303         TabHost tabHost = mActivity.getTabHost();
    304 
    305         // set CurrentTab
    306         TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
    307         tabSpec.setIndicator(TAG_TAB2);
    308         tabSpec.setContent(new MyTabContentFactoryText());
    309         tabHost.addTab(tabSpec);
    310 
    311         tabHost.setCurrentTabByTag(TAG_TAB2);
    312         assertEquals(1, tabHost.getCurrentTab());
    313 
    314         tabHost.setCurrentTabByTag(TabHostCtsActivity.INITIAL_TAB_TAG);
    315         assertEquals(0, tabHost.getCurrentTab());
    316 
    317         // exceptional value
    318         tabHost.setCurrentTabByTag(null);
    319         assertEquals(0, tabHost.getCurrentTab());
    320 
    321         tabHost.setCurrentTabByTag("unknown tag");
    322         assertEquals(0, tabHost.getCurrentTab());
    323     }
    324 
    325     @UiThreadTest
    326     @Test
    327     public void testGetTabContentView() {
    328         TabHost tabHost = mActivity.getTabHost();
    329         assertEquals(3, tabHost.getTabContentView().getChildCount());
    330 
    331         TextView child0 = (TextView) tabHost.getTabContentView().getChildAt(0);
    332         assertEquals(mActivity.getResources().getString(R.string.hello_world),
    333                 child0.getText().toString());
    334         assertTrue(tabHost.getTabContentView().getChildAt(1) instanceof ListView);
    335         TextView child2 = (TextView) tabHost.getTabContentView().getChildAt(2);
    336         tabHost.setCurrentTab(0);
    337         assertEquals(TabHostCtsActivity.INITIAL_VIEW_TEXT, child2.getText().toString());
    338 
    339         TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
    340         tabSpec.setIndicator(TAG_TAB2);
    341         tabSpec.setContent(new MyTabContentFactoryList());
    342         tabHost.addTab(tabSpec);
    343         assertEquals(3, tabHost.getTabContentView().getChildCount());
    344         tabHost.setCurrentTab(1);
    345         assertEquals(4, tabHost.getTabContentView().getChildCount());
    346 
    347         child0 = (TextView) tabHost.getTabContentView().getChildAt(0);
    348         assertEquals(mActivity.getResources().getString(R.string.hello_world),
    349                 child0.getText().toString());
    350         assertTrue(tabHost.getTabContentView().getChildAt(1) instanceof ListView);
    351         child2 = (TextView) tabHost.getTabContentView().getChildAt(2);
    352         tabHost.setCurrentTab(0);
    353         assertEquals(TabHostCtsActivity.INITIAL_VIEW_TEXT, child2.getText().toString());
    354     }
    355 
    356     /**
    357      * Check points:
    358      * 1. the specified callback should be invoked when the selected state of any of the items
    359      * in this list changes
    360      */
    361     @UiThreadTest
    362     @Test
    363     public void testSetOnTabChangedListener() {
    364         TabHost tabHost = mActivity.getTabHost();
    365 
    366         // add a tab, and change current tab to the new tab
    367         OnTabChangeListener mockTabChangeListener = mock(OnTabChangeListener.class);
    368         tabHost.setOnTabChangedListener(mockTabChangeListener);
    369 
    370         TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
    371         tabSpec.setIndicator(TAG_TAB2);
    372         tabSpec.setContent(new MyTabContentFactoryList());
    373         tabHost.addTab(tabSpec);
    374         tabHost.setCurrentTab(1);
    375         verify(mockTabChangeListener, times(1)).onTabChanged(TAG_TAB2);
    376 
    377         // change current tab to the first one
    378         tabHost.setCurrentTab(0);
    379         verify(mockTabChangeListener, times(1)).onTabChanged(TabHostCtsActivity.INITIAL_TAB_TAG);
    380 
    381         // set the same tab
    382         tabHost.setCurrentTab(0);
    383         verifyNoMoreInteractions(mockTabChangeListener);
    384     }
    385 
    386     @UiThreadTest
    387     @Test
    388     public void testGetCurrentTabTag() {
    389         TabHost tabHost = mActivity.getTabHost();
    390         assertEquals(TabHostCtsActivity.INITIAL_TAB_TAG, tabHost.getCurrentTabTag());
    391 
    392         TabSpec tabSpec = tabHost.newTabSpec(TAG_TAB2);
    393         tabSpec.setIndicator(TAG_TAB2);
    394         tabSpec.setContent(new MyTabContentFactoryList());
    395         tabHost.addTab(tabSpec);
    396         tabHost.setCurrentTab(1);
    397         assertEquals(TAG_TAB2, tabHost.getCurrentTabTag());
    398     }
    399 
    400     @Test
    401     public void testKeyboardNavigation() throws Throwable {
    402         mActivityRule.runOnUiThread(() -> {
    403             mActivity.setContentView(R.layout.tabhost_focus);
    404             TabHost tabHost = mActivity.findViewById(android.R.id.tabhost);
    405             tabHost.setup();
    406             TabSpec spec = tabHost.newTabSpec("Tab 1");
    407             spec.setContent(R.id.tab1);
    408             spec.setIndicator("Tab 1");
    409             tabHost.addTab(spec);
    410             spec = tabHost.newTabSpec("Tab 2");
    411             spec.setContent(R.id.tab2);
    412             spec.setIndicator("Tab 2");
    413             tabHost.addTab(spec);
    414             View topBut = mActivity.findViewById(R.id.before_button);
    415             topBut.requestFocus();
    416             assertTrue(topBut.isFocused());
    417         });
    418         mInstrumentation.waitForIdleSync();
    419         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
    420         View tabs = mActivity.findViewById(android.R.id.tabs);
    421         assertTrue(tabs.hasFocus());
    422         View firstTab = tabs.findFocus();
    423         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
    424         assertTrue(tabs.hasFocus());
    425         int[] shiftKey = new int[]{KeyEvent.KEYCODE_SHIFT_LEFT};
    426         sendKeyComboSync(KeyEvent.KEYCODE_TAB, shiftKey);
    427         assertTrue(tabs.hasFocus());
    428         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
    429         assertTrue(tabs.hasFocus());
    430 
    431         // non-navigation sends focus to content
    432         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_E);
    433         assertTrue(mActivity.findViewById(R.id.tab1_button).isFocused());
    434         sendKeyComboSync(KeyEvent.KEYCODE_TAB, shiftKey);
    435         assertTrue(tabs.hasFocus());
    436 
    437         mActivityRule.runOnUiThread(() -> firstTab.requestFocus());
    438         mInstrumentation.waitForIdleSync();
    439         sendKeyComboSync(KeyEvent.KEYCODE_TAB, shiftKey);
    440         assertTrue(mActivity.findViewById(R.id.before_button).isFocused());
    441     }
    442 
    443     private class MyTabContentFactoryText implements TabHost.TabContentFactory {
    444         public View createTabContent(String tag) {
    445             final TextView tv = new TextView(mActivity);
    446             tv.setText(tag);
    447             return tv;
    448         }
    449     }
    450 
    451     private class MyTabContentFactoryList implements TabHost.TabContentFactory {
    452         public View createTabContent(String tag) {
    453             final ListView lv = new ListView(mActivity);
    454             return lv;
    455         }
    456     }
    457 
    458     private static int metaFromKey(int keyCode) {
    459         switch(keyCode) {
    460             case KeyEvent.KEYCODE_ALT_LEFT: return KeyEvent.META_ALT_LEFT_ON;
    461             case KeyEvent.KEYCODE_ALT_RIGHT: return KeyEvent.META_ALT_RIGHT_ON;
    462             case KeyEvent.KEYCODE_SHIFT_LEFT: return KeyEvent.META_SHIFT_LEFT_ON;
    463             case KeyEvent.KEYCODE_SHIFT_RIGHT: return KeyEvent.META_SHIFT_RIGHT_ON;
    464             case KeyEvent.KEYCODE_CTRL_LEFT: return KeyEvent.META_CTRL_LEFT_ON;
    465             case KeyEvent.KEYCODE_CTRL_RIGHT: return KeyEvent.META_CTRL_RIGHT_ON;
    466             case KeyEvent.KEYCODE_META_LEFT: return KeyEvent.META_META_LEFT_ON;
    467             case KeyEvent.KEYCODE_META_RIGHT: return KeyEvent.META_META_RIGHT_ON;
    468         }
    469         return 0;
    470     }
    471 
    472     /**
    473      * High-level method for sending a chorded key-combo (modifiers + key). This will send all the
    474      * down and up key events as a user would press them (ie. all the modifiers get their own
    475      * down and up events).
    476      *
    477      * @param keyCode The keycode to send while all meta keys are pressed.
    478      * @param metaKeys An array of meta key *keycodes* (not modifiers).
    479      */
    480     private void sendKeyComboSync(int keyCode, int[] metaKeys) {
    481         int metaState = 0;
    482         if (metaKeys != null) {
    483             for (int mk = 0; mk < metaKeys.length; ++mk) {
    484                 metaState |= metaFromKey(metaKeys[mk]);
    485                 mInstrumentation.sendKeySync(new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, metaKeys[mk],
    486                         0, KeyEvent.normalizeMetaState(metaState)));
    487             }
    488         }
    489         mInstrumentation.sendKeySync(new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0,
    490                 KeyEvent.normalizeMetaState(metaState)));
    491         mInstrumentation.sendKeySync(new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0,
    492                 KeyEvent.normalizeMetaState(metaState)));
    493         if (metaKeys != null) {
    494             for (int mk = 0; mk < metaKeys.length; ++mk) {
    495                 metaState &= ~metaFromKey(metaKeys[mk]);
    496                 mInstrumentation.sendKeySync(new KeyEvent(0, 0, KeyEvent.ACTION_UP, metaKeys[mk], 0,
    497                         KeyEvent.normalizeMetaState(metaState)));
    498             }
    499         }
    500     }
    501 }
    502