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