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 android.widget; 18 19 import static android.widget.espresso.DragHandleUtils.onHandleView; 20 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem; 21 import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem; 22 import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup; 23 import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupContainsItem; 24 import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupIsDisplayed; 25 import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupIsNotDisplayed; 26 import static android.widget.espresso.SuggestionsPopupwindowUtils.clickSuggestionsPopupItem; 27 import static android.widget.espresso.SuggestionsPopupwindowUtils.onSuggestionsPopup; 28 import static android.widget.espresso.TextViewActions.clickOnTextAtIndex; 29 import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex; 30 31 import static androidx.test.espresso.Espresso.onView; 32 import static androidx.test.espresso.Espresso.pressBack; 33 import static androidx.test.espresso.action.ViewActions.clearText; 34 import static androidx.test.espresso.action.ViewActions.click; 35 import static androidx.test.espresso.action.ViewActions.replaceText; 36 import static androidx.test.espresso.assertion.ViewAssertions.matches; 37 import static androidx.test.espresso.matcher.RootMatchers.withDecorView; 38 import static androidx.test.espresso.matcher.ViewMatchers.withId; 39 import static androidx.test.espresso.matcher.ViewMatchers.withText; 40 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 41 42 import static org.hamcrest.Matchers.is; 43 import static org.junit.Assert.assertEquals; 44 import static org.junit.Assert.assertFalse; 45 import static org.junit.Assert.assertNotNull; 46 import static org.junit.Assert.assertTrue; 47 48 import android.content.res.TypedArray; 49 import android.text.Selection; 50 import android.text.Spannable; 51 import android.text.Spanned; 52 import android.text.TextPaint; 53 import android.text.style.SuggestionSpan; 54 import android.text.style.TextAppearanceSpan; 55 56 import androidx.test.filters.SmallTest; 57 import androidx.test.rule.ActivityTestRule; 58 59 import com.android.frameworks.coretests.R; 60 61 import org.junit.Rule; 62 import org.junit.Test; 63 64 /** 65 * SuggestionsPopupWindowTest tests. 66 * 67 * TODO: Add tests for when there are no suggestions 68 */ 69 @SmallTest 70 public class SuggestionsPopupWindowTest { 71 72 @Rule 73 public final ActivityTestRule<TextViewActivity> mActivityRule = 74 new ActivityTestRule<>(TextViewActivity.class); 75 getActivity()76 private TextViewActivity getActivity() { 77 return mActivityRule.getActivity(); 78 } 79 setSuggestionSpan(SuggestionSpan span, int start, int end)80 private void setSuggestionSpan(SuggestionSpan span, int start, int end) { 81 final TextView textView = getActivity().findViewById(R.id.textview); 82 textView.post( 83 () -> { 84 final Spannable text = (Spannable) textView.getText(); 85 text.setSpan(span, start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 86 Selection.setSelection(text, (start + end) / 2); 87 }); 88 getInstrumentation().waitForIdleSync(); 89 } 90 91 @Test testOnTextContextMenuItem()92 public void testOnTextContextMenuItem() { 93 final String text = "abc def ghi"; 94 95 onView(withId(R.id.textview)).perform(click()); 96 onView(withId(R.id.textview)).perform(replaceText(text)); 97 98 final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(), 99 new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION); 100 setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1); 101 102 final TextView textView = getActivity().findViewById(R.id.textview); 103 textView.post(() -> textView.onTextContextMenuItem(TextView.ID_REPLACE)); 104 getInstrumentation().waitForIdleSync(); 105 106 assertSuggestionsPopupIsDisplayed(); 107 } 108 109 @Test testSelectionActionMode()110 public void testSelectionActionMode() { 111 final String text = "abc def ghi"; 112 113 onView(withId(R.id.textview)).perform(click()); 114 onView(withId(R.id.textview)).perform(replaceText(text)); 115 116 final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(), 117 new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION); 118 setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1); 119 120 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e'))); 121 sleepForFloatingToolbarPopup(); 122 assertFloatingToolbarContainsItem( 123 getActivity().getString(com.android.internal.R.string.replace)); 124 sleepForFloatingToolbarPopup(); 125 clickFloatingToolbarItem( 126 getActivity().getString(com.android.internal.R.string.replace)); 127 128 assertSuggestionsPopupIsDisplayed(); 129 } 130 131 @Test testInsertionActionMode()132 public void testInsertionActionMode() { 133 final String text = "abc def ghi"; 134 135 onView(withId(R.id.textview)).perform(click()); 136 onView(withId(R.id.textview)).perform(replaceText(text)); 137 138 final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(), 139 new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION); 140 setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1); 141 142 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e'))); 143 onHandleView(com.android.internal.R.id.insertion_handle).perform(click()); 144 sleepForFloatingToolbarPopup(); 145 assertFloatingToolbarContainsItem( 146 getActivity().getString(com.android.internal.R.string.replace)); 147 clickFloatingToolbarItem( 148 getActivity().getString(com.android.internal.R.string.replace)); 149 150 assertSuggestionsPopupIsDisplayed(); 151 } 152 showSuggestionsPopup()153 private void showSuggestionsPopup() { 154 final TextView textView = getActivity().findViewById(R.id.textview); 155 textView.post(() -> textView.onTextContextMenuItem(TextView.ID_REPLACE)); 156 getInstrumentation().waitForIdleSync(); 157 assertSuggestionsPopupIsDisplayed(); 158 } 159 160 @Test testSuggestionItems()161 public void testSuggestionItems() { 162 final String text = "abc def ghi"; 163 164 onView(withId(R.id.textview)).perform(click()); 165 onView(withId(R.id.textview)).perform(replaceText(text)); 166 167 final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(), 168 new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION); 169 setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1); 170 171 showSuggestionsPopup(); 172 173 assertSuggestionsPopupIsDisplayed(); 174 assertSuggestionsPopupContainsItem("DEF"); 175 assertSuggestionsPopupContainsItem("Def"); 176 assertSuggestionsPopupContainsItem( 177 getActivity().getString(com.android.internal.R.string.delete)); 178 179 // Select an item. 180 clickSuggestionsPopupItem("DEF"); 181 assertSuggestionsPopupIsNotDisplayed(); 182 onView(withId(R.id.textview)).check(matches(withText("abc DEF ghi"))); 183 184 showSuggestionsPopup(); 185 assertSuggestionsPopupIsDisplayed(); 186 assertSuggestionsPopupContainsItem("def"); 187 assertSuggestionsPopupContainsItem("Def"); 188 assertSuggestionsPopupContainsItem( 189 getActivity().getString(com.android.internal.R.string.delete)); 190 191 // Delete 192 clickSuggestionsPopupItem( 193 getActivity().getString(com.android.internal.R.string.delete)); 194 assertSuggestionsPopupIsNotDisplayed(); 195 onView(withId(R.id.textview)).check(matches(withText("abc ghi"))); 196 } 197 198 @Test testMisspelled()199 public void testMisspelled() { 200 final String text = "abc def ghi"; 201 202 onView(withId(R.id.textview)).perform(click()); 203 onView(withId(R.id.textview)).perform(replaceText(text)); 204 205 final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(), 206 new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_MISSPELLED); 207 setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1); 208 209 showSuggestionsPopup(); 210 211 assertSuggestionsPopupIsDisplayed(); 212 assertSuggestionsPopupContainsItem("DEF"); 213 assertSuggestionsPopupContainsItem("Def"); 214 assertSuggestionsPopupContainsItem( 215 getActivity().getString(com.android.internal.R.string.addToDictionary)); 216 assertSuggestionsPopupContainsItem( 217 getActivity().getString(com.android.internal.R.string.delete)); 218 219 // Click "Add to dictionary". 220 clickSuggestionsPopupItem( 221 getActivity().getString(com.android.internal.R.string.addToDictionary)); 222 // TODO: Check if add to dictionary dialog is displayed. 223 } 224 225 @Test testEasyCorrect()226 public void testEasyCorrect() { 227 final String text = "abc def ghi"; 228 229 onView(withId(R.id.textview)).perform(click()); 230 onView(withId(R.id.textview)).perform(replaceText(text)); 231 232 final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(), 233 new String[]{"DEF", "Def"}, 234 SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED); 235 setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1); 236 237 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e'))); 238 239 assertSuggestionsPopupIsDisplayed(); 240 assertSuggestionsPopupContainsItem("DEF"); 241 assertSuggestionsPopupContainsItem("Def"); 242 assertSuggestionsPopupContainsItem( 243 getActivity().getString(com.android.internal.R.string.delete)); 244 245 // Select an item. 246 clickSuggestionsPopupItem("DEF"); 247 assertSuggestionsPopupIsNotDisplayed(); 248 onView(withId(R.id.textview)).check(matches(withText("abc DEF ghi"))); 249 250 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e'))); 251 assertSuggestionsPopupIsNotDisplayed(); 252 253 showSuggestionsPopup(); 254 assertSuggestionsPopupIsDisplayed(); 255 assertSuggestionsPopupContainsItem("def"); 256 assertSuggestionsPopupContainsItem("Def"); 257 assertSuggestionsPopupContainsItem( 258 getActivity().getString(com.android.internal.R.string.delete)); 259 } 260 261 @Test testTextAppearanceInSuggestionsPopup()262 public void testTextAppearanceInSuggestionsPopup() { 263 final String text = "abc def ghi"; 264 265 final String[] singleWordCandidates = {"DEF", "Def"}; 266 final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(), 267 singleWordCandidates, SuggestionSpan.FLAG_MISSPELLED); 268 final String[] multiWordCandidates = {"ABC DEF GHI", "Abc Def Ghi"}; 269 final SuggestionSpan multiWordSuggestionSpan = new SuggestionSpan(getActivity(), 270 multiWordCandidates, SuggestionSpan.FLAG_MISSPELLED); 271 272 final TypedArray array = 273 getActivity().obtainStyledAttributes(com.android.internal.R.styleable.Theme); 274 final int id = array.getResourceId( 275 com.android.internal.R.styleable.Theme_textEditSuggestionHighlightStyle, 0); 276 array.recycle(); 277 final TextAppearanceSpan expectedSpan = new TextAppearanceSpan(getActivity(), id); 278 final TextPaint tmpTp = new TextPaint(); 279 expectedSpan.updateDrawState(tmpTp); 280 final int expectedHighlightTextColor = tmpTp.getColor(); 281 final float expectedHighlightTextSize = tmpTp.getTextSize(); 282 final TextView textView = (TextView) getActivity().findViewById(R.id.textview); 283 284 // In this test, the SuggestionsPopupWindow looks like 285 // abc def ghi 286 // ----------------- 287 // | abc *DEF* ghi | 288 // | abc *Def* ghi | 289 // | *ABC DEF GHI* | 290 // | *Abc Def Ghi* | 291 // ----------------- 292 // | DELETE | 293 // ----------------- 294 // *XX* means that XX is highlighted. 295 for (int i = 0; i < 2; i++) { 296 onView(withId(R.id.textview)).perform(click()); 297 onView(withId(R.id.textview)).perform(replaceText(text)); 298 setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1); 299 setSuggestionSpan(multiWordSuggestionSpan, 0, text.length()); 300 301 showSuggestionsPopup(); 302 assertSuggestionsPopupIsDisplayed(); 303 assertSuggestionsPopupContainsItem("abc DEF ghi"); 304 assertSuggestionsPopupContainsItem("abc Def ghi"); 305 assertSuggestionsPopupContainsItem("ABC DEF GHI"); 306 assertSuggestionsPopupContainsItem("Abc Def Ghi"); 307 assertSuggestionsPopupContainsItem( 308 getActivity().getString(com.android.internal.R.string.delete)); 309 310 onSuggestionsPopup().check((view, e) -> { 311 final ListView listView = view.findViewById( 312 com.android.internal.R.id.suggestionContainer); 313 assertNotNull(listView); 314 final int childNum = listView.getChildCount(); 315 assertEquals(singleWordCandidates.length + multiWordCandidates.length, childNum); 316 317 for (int j = 0; j < childNum; j++) { 318 final TextView suggestion = (TextView) listView.getChildAt(j); 319 assertNotNull(suggestion); 320 final Spanned spanned = (Spanned) suggestion.getText(); 321 assertNotNull(spanned); 322 323 // Check that the suggestion item order is kept. 324 final String expectedText; 325 if (j < singleWordCandidates.length) { 326 expectedText = "abc " + singleWordCandidates[j] + " ghi"; 327 } else { 328 expectedText = multiWordCandidates[j - singleWordCandidates.length]; 329 } 330 assertEquals(expectedText, spanned.toString()); 331 332 // Check that the text is highlighted with correct color and text size. 333 final TextAppearanceSpan[] taSpan = spanned.getSpans( 334 text.indexOf('d'), text.indexOf('f') + 1, TextAppearanceSpan.class); 335 assertEquals(1, taSpan.length); 336 TextPaint tp = new TextPaint(); 337 taSpan[0].updateDrawState(tp); 338 assertEquals(expectedHighlightTextColor, tp.getColor()); 339 assertEquals(expectedHighlightTextSize, tp.getTextSize(), 0f); 340 341 // Check the correct part of the text is highlighted. 342 final int expectedStart; 343 final int expectedEnd; 344 if (j < singleWordCandidates.length) { 345 expectedStart = text.indexOf('d'); 346 expectedEnd = text.indexOf('f') + 1; 347 } else { 348 expectedStart = 0; 349 expectedEnd = text.length(); 350 } 351 assertEquals(expectedStart, spanned.getSpanStart(taSpan[0])); 352 assertEquals(expectedEnd, spanned.getSpanEnd(taSpan[0])); 353 } 354 }); 355 pressBack(); 356 onView(withId(R.id.textview)) 357 .inRoot(withDecorView(is(getActivity().getWindow().getDecorView()))) 358 .perform(clearText()); 359 } 360 } 361 362 @Test testCursorVisibility()363 public void testCursorVisibility() { 364 final TextView textView = getActivity().findViewById(R.id.textview); 365 final String text = "abc"; 366 367 assertTrue(textView.isCursorVisible()); 368 369 onView(withId(R.id.textview)).perform(click()); 370 onView(withId(R.id.textview)).perform(replaceText(text)); 371 final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(), 372 new String[]{"ABC"}, SuggestionSpan.FLAG_AUTO_CORRECTION); 373 setSuggestionSpan(suggestionSpan, text.indexOf('a'), text.indexOf('c') + 1); 374 showSuggestionsPopup(); 375 376 assertSuggestionsPopupIsDisplayed(); 377 assertSuggestionsPopupContainsItem("ABC"); 378 assertFalse(textView.isCursorVisible()); 379 380 // Delete an item. 381 clickSuggestionsPopupItem( 382 getActivity().getString(com.android.internal.R.string.delete)); 383 assertSuggestionsPopupIsNotDisplayed(); 384 assertTrue(textView.isCursorVisible()); 385 } 386 387 @Test testCursorVisibilityWhenImeConsumesInput()388 public void testCursorVisibilityWhenImeConsumesInput() { 389 final TextView textView = getActivity().findViewById(R.id.textview); 390 final String text = "abc"; 391 392 assertTrue(textView.isCursorVisible()); 393 394 onView(withId(R.id.textview)).perform(click()); 395 onView(withId(R.id.textview)).perform(replaceText(text)); 396 setImeConsumesInputWithExpect(textView, true /* imeConsumesInput */, 397 false /* expectedCursorVisibility */); 398 final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(), 399 new String[]{"ABC"}, SuggestionSpan.FLAG_AUTO_CORRECTION); 400 setSuggestionSpan(suggestionSpan, text.indexOf('a'), text.indexOf('c') + 1); 401 showSuggestionsPopup(); 402 403 assertSuggestionsPopupIsDisplayed(); 404 assertSuggestionsPopupContainsItem("ABC"); 405 assertFalse(textView.isCursorVisible()); 406 407 // Delete an item. 408 clickSuggestionsPopupItem( 409 getActivity().getString(com.android.internal.R.string.delete)); 410 assertSuggestionsPopupIsNotDisplayed(); 411 assertFalse(textView.isCursorVisible()); 412 413 // Set IME not consumes input, cursor should be back to visible. 414 setImeConsumesInputWithExpect(textView, false /* imeConsumesInput */, 415 true /* expectedCursorVisibility */); 416 } 417 setImeConsumesInputWithExpect( final TextView textView, boolean imeConsumesInput, boolean expectedCursorVisibility)418 private void setImeConsumesInputWithExpect( 419 final TextView textView, boolean imeConsumesInput, boolean expectedCursorVisibility) { 420 textView.post(() -> textView.setImeConsumesInput(imeConsumesInput)); 421 getInstrumentation().waitForIdleSync(); 422 if (expectedCursorVisibility) { 423 assertTrue(textView.isCursorVisible()); 424 } else { 425 assertFalse(textView.isCursorVisible()); 426 } 427 } 428 } 429