Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2015 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 androidx.appcompat.app;
     18 
     19 import static android.support.test.espresso.Espresso.onView;
     20 import static android.support.test.espresso.action.ViewActions.click;
     21 import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
     22 import static android.support.test.espresso.assertion.ViewAssertions.matches;
     23 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
     24 import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
     25 import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
     26 import static android.support.test.espresso.matcher.ViewMatchers.withId;
     27 
     28 import static org.junit.Assert.assertEquals;
     29 import static org.junit.Assert.assertFalse;
     30 import static org.junit.Assert.assertNotNull;
     31 import static org.junit.Assert.assertTrue;
     32 
     33 import android.app.Instrumentation;
     34 import android.support.test.InstrumentationRegistry;
     35 import android.support.test.filters.LargeTest;
     36 import android.support.test.filters.SmallTest;
     37 import android.support.test.rule.ActivityTestRule;
     38 import android.support.test.runner.AndroidJUnit4;
     39 import android.view.KeyEvent;
     40 import android.view.Menu;
     41 import android.view.MenuItem;
     42 
     43 import androidx.appcompat.test.R;
     44 import androidx.appcompat.testutils.BaseTestActivity;
     45 import androidx.appcompat.view.ActionMode;
     46 import androidx.core.view.MenuItemCompat;
     47 
     48 import org.hamcrest.Matchers;
     49 import org.junit.Before;
     50 import org.junit.Rule;
     51 import org.junit.Test;
     52 import org.junit.runner.RunWith;
     53 
     54 import java.util.concurrent.atomic.AtomicBoolean;
     55 
     56 @RunWith(AndroidJUnit4.class)
     57 public abstract class BaseKeyEventsTestCase<A extends BaseTestActivity> {
     58     @Rule
     59     public final ActivityTestRule<A> mActivityTestRule;
     60 
     61     private Instrumentation mInstrumentation;
     62     private A mActivity;
     63 
     64     protected BaseKeyEventsTestCase(Class<A> activityClass) {
     65         mActivityTestRule = new ActivityTestRule<>(activityClass);
     66     }
     67 
     68     @Before
     69     public void setup() {
     70         mInstrumentation = InstrumentationRegistry.getInstrumentation();
     71         mActivity = mActivityTestRule.getActivity();
     72     }
     73 
     74     @Test
     75     @SmallTest
     76     public void testBackDismissesActionMode() {
     77         final AtomicBoolean destroyed = new AtomicBoolean();
     78 
     79         mActivity.runOnUiThread(new Runnable() {
     80             @Override
     81             public void run() {
     82                 mActivity.startSupportActionMode(new ActionMode.Callback() {
     83                     @Override
     84                     public boolean onCreateActionMode(ActionMode mode, Menu menu) {
     85                         mode.getMenuInflater().inflate(R.menu.sample_actions, menu);
     86                         return true;
     87                     }
     88 
     89                     @Override
     90                     public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
     91                         return false;
     92                     }
     93 
     94                     @Override
     95                     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
     96                         return false;
     97                     }
     98 
     99                     @Override
    100                     public void onDestroyActionMode(ActionMode mode) {
    101                         destroyed.set(true);
    102                     }
    103                 });
    104             }
    105         });
    106 
    107         mInstrumentation.waitForIdleSync();
    108         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
    109         mInstrumentation.waitForIdleSync();
    110 
    111         assertFalse("Activity was not finished", mActivity.isFinishing());
    112         assertTrue("ActionMode was destroyed", destroyed.get());
    113     }
    114 
    115     @Test
    116     @LargeTest
    117     public void testBackCollapsesActionView() throws InterruptedException {
    118         // Click on the Search menu item
    119         onView(withId(R.id.action_search)).perform(click());
    120         // Check that the action view is displayed (expanded)
    121         onView(withClassName(Matchers.is(CustomCollapsibleView.class.getName())))
    122                 .check(matches(isDisplayed()));
    123 
    124         // Let things settle
    125         mInstrumentation.waitForIdleSync();
    126         // Now send a back event to collapse the custom action view
    127         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
    128         mInstrumentation.waitForIdleSync();
    129 
    130         // Check that the Activity is still running
    131         assertFalse(mActivity.isFinishing());
    132         assertFalse(mActivity.isDestroyed());
    133         // ... and that our action view is not attached
    134         onView(withClassName(Matchers.is(CustomCollapsibleView.class.getName())))
    135                 .check(doesNotExist());
    136     }
    137 
    138     @Test
    139     @SmallTest
    140     public void testMenuPressInvokesPanelCallbacks() throws InterruptedException {
    141         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
    142         mInstrumentation.waitForIdleSync();
    143         assertTrue("onMenuOpened called", mActivity.wasOnMenuOpenedCalled());
    144 
    145         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
    146         mInstrumentation.waitForIdleSync();
    147         assertTrue("onPanelClosed called", mActivity.wasOnPanelClosedCalled());
    148     }
    149 
    150     @Test
    151     @SmallTest
    152     public void testBackPressWithMenuInvokesOnPanelClosed() throws InterruptedException {
    153         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
    154         mInstrumentation.waitForIdleSync();
    155 
    156         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
    157         mInstrumentation.waitForIdleSync();
    158         assertTrue("onPanelClosed called", mActivity.wasOnPanelClosedCalled());
    159     }
    160 
    161     @Test
    162     @SmallTest
    163     public void testBackPressWithEmptyMenuFinishesActivity() throws InterruptedException {
    164         repopulateWithEmptyMenu();
    165 
    166         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
    167         mInstrumentation.waitForIdleSync();
    168 
    169         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
    170         assertTrue(mActivity.isFinishing());
    171     }
    172 
    173     @Test
    174     @SmallTest
    175     public void testDelKeyEventReachesActivity() {
    176         // First send the event
    177         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DEL);
    178         mInstrumentation.waitForIdleSync();
    179 
    180         KeyEvent downEvent = mActivity.getInvokedKeyDownEvent();
    181         assertNotNull("onKeyDown called", downEvent);
    182         assertEquals("onKeyDown event matches", KeyEvent.KEYCODE_DEL, downEvent.getKeyCode());
    183 
    184         KeyEvent upEvent = mActivity.getInvokedKeyUpEvent();
    185         assertNotNull("onKeyUp called", upEvent);
    186         assertEquals("onKeyUp event matches", KeyEvent.KEYCODE_DEL, upEvent.getKeyCode());
    187     }
    188 
    189     @Test
    190     @SmallTest
    191     public void testMenuKeyEventReachesActivity() throws InterruptedException {
    192         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
    193         mInstrumentation.waitForIdleSync();
    194 
    195         KeyEvent downEvent = mActivity.getInvokedKeyDownEvent();
    196         assertNotNull("onKeyDown called", downEvent);
    197         assertEquals("onKeyDown event matches", KeyEvent.KEYCODE_MENU, downEvent.getKeyCode());
    198 
    199         KeyEvent upEvent = mActivity.getInvokedKeyUpEvent();
    200         assertNotNull("onKeyUp called", upEvent);
    201         assertEquals("onKeyDown event matches", KeyEvent.KEYCODE_MENU, upEvent.getKeyCode());
    202     }
    203 
    204     @Test
    205     @SmallTest
    206     public void testActionMenuContent() throws Throwable {
    207         onView(withId(R.id.action_search))
    208                 .check(matches(isDisplayed()))
    209                 .check(matches(withContentDescription(R.string.search_menu_description)));
    210 
    211         onView(withId(R.id.action_alpha_shortcut))
    212                 .check(matches(isDisplayed()))
    213                 .check(matches(withContentDescription((String) null)));
    214 
    215         Menu menu = mActivity.getMenu();
    216         final MenuItem alphaItem = menu.findItem(R.id.action_alpha_shortcut);
    217         assertNotNull(alphaItem);
    218 
    219         mActivityTestRule.runOnUiThread(new Runnable() {
    220             @Override
    221             public void run() {
    222                 MenuItemCompat.setContentDescription(alphaItem,
    223                         mActivity.getString(R.string.alpha_menu_description));
    224                 MenuItemCompat.setTooltipText(alphaItem,
    225                         mActivity.getString(R.string.alpha_menu_tooltip));
    226             }
    227         });
    228 
    229         onView(withId(R.id.action_alpha_shortcut))
    230                 .check(matches(isDisplayed()))
    231                 .check(matches(withContentDescription(R.string.alpha_menu_description)));
    232     }
    233 
    234     private void repopulateWithEmptyMenu() throws InterruptedException {
    235         int count = 0;
    236         mActivity.setShouldPopulateOptionsMenu(false);
    237         while (count++ < 10) {
    238             Menu menu = mActivity.getMenu();
    239             if (menu == null || menu.size() != 0) {
    240                 Thread.sleep(100);
    241             } else {
    242                 return;
    243             }
    244         }
    245     }
    246 }
    247