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