TextViewActivityTest.java revision 724eff9a3da8cea2d0356186982caa8dd8da33fd
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.widget; 18 19import static android.support.test.espresso.Espresso.onView; 20import static android.support.test.espresso.action.ViewActions.click; 21import static android.support.test.espresso.action.ViewActions.longClick; 22import static android.support.test.espresso.action.ViewActions.pressKey; 23import static android.support.test.espresso.action.ViewActions.replaceText; 24import static android.support.test.espresso.assertion.ViewAssertions.matches; 25import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 26import static android.support.test.espresso.matcher.ViewMatchers.withId; 27import static android.support.test.espresso.matcher.ViewMatchers.withText; 28import static android.widget.espresso.CustomViewActions.longPressAtRelativeCoordinates; 29import static android.widget.espresso.DragHandleUtils.onHandleView; 30import static android.widget.espresso.FloatingToolbarEspressoUtils 31 .assertFloatingToolbarContainsItem; 32import static android.widget.espresso.FloatingToolbarEspressoUtils 33 .assertFloatingToolbarDoesNotContainItem; 34import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed; 35import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarItemIndex; 36import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem; 37import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup; 38import static android.widget.espresso.TextViewActions.Handle; 39import static android.widget.espresso.TextViewActions.clickOnTextAtIndex; 40import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex; 41import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText; 42import static android.widget.espresso.TextViewActions.dragHandle; 43import static android.widget.espresso.TextViewActions.longPressAndDragOnText; 44import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex; 45import static android.widget.espresso.TextViewAssertions.doesNotHaveStyledText; 46import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex; 47import static android.widget.espresso.TextViewAssertions.hasSelection; 48 49import static junit.framework.Assert.assertFalse; 50import static junit.framework.Assert.assertTrue; 51 52import static org.hamcrest.Matchers.anyOf; 53import static org.hamcrest.Matchers.is; 54import static org.mockito.Matchers.any; 55import static org.mockito.Mockito.mock; 56import static org.mockito.Mockito.never; 57import static org.mockito.Mockito.verify; 58import static org.mockito.Mockito.when; 59 60import android.app.Activity; 61import android.app.Instrumentation; 62import android.content.ClipData; 63import android.content.ClipboardManager; 64import android.support.test.InstrumentationRegistry; 65import android.support.test.espresso.action.EspressoKey; 66import android.support.test.filters.MediumTest; 67import android.support.test.rule.ActivityTestRule; 68import android.support.test.runner.AndroidJUnit4; 69import android.test.suitebuilder.annotation.Suppress; 70import android.text.InputType; 71import android.text.Selection; 72import android.text.Spannable; 73import android.text.SpannableString; 74import android.text.method.LinkMovementMethod; 75import android.view.ActionMode; 76import android.view.KeyEvent; 77import android.view.Menu; 78import android.view.MenuItem; 79import android.view.textclassifier.TextClassificationManager; 80import android.view.textclassifier.TextClassifier; 81import android.view.textclassifier.TextLinks; 82import android.widget.espresso.CustomViewActions.RelativeCoordinatesProvider; 83 84import com.android.frameworks.coretests.R; 85 86import org.junit.Before; 87import org.junit.Rule; 88import org.junit.Test; 89import org.junit.runner.RunWith; 90 91/** 92 * Tests the TextView widget from an Activity 93 */ 94@RunWith(AndroidJUnit4.class) 95@MediumTest 96public class TextViewActivityTest { 97 98 @Rule 99 public ActivityTestRule<TextViewActivity> mActivityRule = new ActivityTestRule<>( 100 TextViewActivity.class); 101 102 private Activity mActivity; 103 private Instrumentation mInstrumentation; 104 105 @Before 106 public void setUp() { 107 mActivity = mActivityRule.getActivity(); 108 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 109 mActivity.getSystemService(TextClassificationManager.class) 110 .setTextClassifier(TextClassifier.NO_OP); 111 } 112 113 @Test 114 public void testTypedTextIsOnScreen() { 115 final String helloWorld = "Hello world!"; 116 // We use replaceText instead of typeTextIntoFocusedView to input text to avoid 117 // unintentional interactions with software keyboard. 118 onView(withId(R.id.textview)).perform(replaceText(helloWorld)); 119 120 onView(withId(R.id.textview)).check(matches(withText(helloWorld))); 121 } 122 @Test 123 public void testPositionCursorAtTextAtIndex() { 124 final String helloWorld = "Hello world!"; 125 onView(withId(R.id.textview)).perform(replaceText(helloWorld)); 126 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("world"))); 127 128 // Delete text at specified index and see if we got the right one. 129 onView(withId(R.id.textview)).perform(pressKey(KeyEvent.KEYCODE_FORWARD_DEL)); 130 onView(withId(R.id.textview)).check(matches(withText("Hello orld!"))); 131 } 132 133 @Test 134 public void testPositionCursorAtTextAtIndex_arabic() { 135 // Arabic text. The expected cursorable boundary is 136 // | \u0623 \u064F | \u067A | \u0633 \u0652 | 137 final String text = "\u0623\u064F\u067A\u0633\u0652"; 138 onView(withId(R.id.textview)).perform(replaceText(text)); 139 140 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0)); 141 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0)); 142 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1)); 143 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2)))); 144 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2)); 145 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2)); 146 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(3)); 147 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(3)); 148 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(4)); 149 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(3), is(5)))); 150 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(5)); 151 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(5)); 152 } 153 154 @Test 155 public void testPositionCursorAtTextAtIndex_devanagari() { 156 // Devanagari text. The expected cursorable boundary is | \u0915 \u093E | 157 final String text = "\u0915\u093E"; 158 onView(withId(R.id.textview)).perform(replaceText(text)); 159 160 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0)); 161 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0)); 162 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1)); 163 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2)))); 164 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2)); 165 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2)); 166 } 167 168 @Test 169 public void testLongPressToSelect() { 170 final String helloWorld = "Hello Kirk!"; 171 onView(withId(R.id.textview)).perform(click()); 172 onView(withId(R.id.textview)).perform(replaceText(helloWorld)); 173 onView(withId(R.id.textview)).perform( 174 longPressOnTextAtIndex(helloWorld.indexOf("Kirk"))); 175 176 onView(withId(R.id.textview)).check(hasSelection("Kirk")); 177 } 178 179 @Test 180 public void testLongPressEmptySpace() { 181 final String helloWorld = "Hello big round sun!"; 182 onView(withId(R.id.textview)).perform(replaceText(helloWorld)); 183 // Move cursor somewhere else 184 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("big"))); 185 // Long-press at end of line. 186 onView(withId(R.id.textview)).perform(longPressAtRelativeCoordinates( 187 RelativeCoordinatesProvider.HorizontalReference.RIGHT, -5, 188 RelativeCoordinatesProvider.VerticalReference.CENTER, 0)); 189 190 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(helloWorld.length())); 191 } 192 193 @Test 194 public void testLongPressAndDragToSelect() { 195 final String helloWorld = "Hello little handsome boy!"; 196 onView(withId(R.id.textview)).perform(replaceText(helloWorld)); 197 onView(withId(R.id.textview)).perform( 198 longPressAndDragOnText(helloWorld.indexOf("little"), helloWorld.indexOf(" boy!"))); 199 200 onView(withId(R.id.textview)).check(hasSelection("little handsome")); 201 } 202 203 @Test 204 public void testLongPressAndDragToSelect_emoji() { 205 final String text = "\uD83D\uDE00\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03"; 206 onView(withId(R.id.textview)).perform(replaceText(text)); 207 208 onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 6)); 209 onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE02")); 210 211 onView(withId(R.id.textview)).perform(click()); 212 213 onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 2)); 214 onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE01")); 215 } 216 217 @Test 218 public void testDragAndDrop() { 219 final String text = "abc def ghi."; 220 onView(withId(R.id.textview)).perform(replaceText(text)); 221 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("e"))); 222 223 onView(withId(R.id.textview)).perform( 224 longPressAndDragOnText(text.indexOf("e"), text.length())); 225 226 onView(withId(R.id.textview)).check(matches(withText("abc ghi.def"))); 227 onView(withId(R.id.textview)).check(hasSelection("")); 228 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length())); 229 230 // Test undo returns to the original state. 231 onView(withId(R.id.textview)).perform(pressKey( 232 (new EspressoKey.Builder()).withCtrlPressed(true).withKeyCode(KeyEvent.KEYCODE_Z) 233 .build())); 234 onView(withId(R.id.textview)).check(matches(withText(text))); 235 } 236 237 @Test 238 public void testDoubleTapToSelect() { 239 final String helloWorld = "Hello SuetYi!"; 240 onView(withId(R.id.textview)).perform(replaceText(helloWorld)); 241 242 onView(withId(R.id.textview)).perform( 243 doubleClickOnTextAtIndex(helloWorld.indexOf("SuetYi"))); 244 245 onView(withId(R.id.textview)).check(hasSelection("SuetYi")); 246 } 247 248 @Test 249 public void testDoubleTapAndDragToSelect() { 250 final String helloWorld = "Hello young beautiful person!"; 251 onView(withId(R.id.textview)).perform(replaceText(helloWorld)); 252 onView(withId(R.id.textview)).perform(doubleTapAndDragOnText(helloWorld.indexOf("young"), 253 helloWorld.indexOf(" person!"))); 254 255 onView(withId(R.id.textview)).check(hasSelection("young beautiful")); 256 } 257 258 @Test 259 public void testDoubleTapAndDragToSelect_multiLine() { 260 final String helloWorld = "abcd\n" + "efg\n" + "hijklm\n" + "nop"; 261 onView(withId(R.id.textview)).perform(replaceText(helloWorld)); 262 onView(withId(R.id.textview)).perform( 263 doubleTapAndDragOnText(helloWorld.indexOf("m"), helloWorld.indexOf("a"))); 264 onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijklm")); 265 } 266 267 @Test 268 public void testSelectBackwordsByTouch() { 269 final String helloWorld = "Hello king of the Jungle!"; 270 onView(withId(R.id.textview)).perform(replaceText(helloWorld)); 271 onView(withId(R.id.textview)).perform( 272 doubleTapAndDragOnText(helloWorld.indexOf(" Jungle!"), helloWorld.indexOf("king"))); 273 274 onView(withId(R.id.textview)).check(hasSelection("king of the")); 275 } 276 277 @Test 278 public void testToolbarAppearsAfterSelection() { 279 final String text = "Toolbar appears after selection."; 280 onView(withId(R.id.textview)).perform(replaceText(text)); 281 onView(withId(R.id.textview)).perform( 282 longPressOnTextAtIndex(text.indexOf("appears"))); 283 284 sleepForFloatingToolbarPopup(); 285 assertFloatingToolbarIsDisplayed(); 286 } 287 288 @Test 289 public void testToolbarAppearsAfterSelection_withFirstStringLtrAlgorithmAndRtlHint() 290 throws Throwable { 291 // after the hint layout change, the floating toolbar was not visible in the case below 292 // this test tests that the floating toolbar is displayed on the screen and is visible to 293 // user. 294 mActivityRule.runOnUiThread(() -> { 295 final TextView textView = mActivity.findViewById(R.id.textview); 296 textView.setTextDirection(TextView.TEXT_DIRECTION_FIRST_STRONG_LTR); 297 textView.setInputType(InputType.TYPE_CLASS_TEXT); 298 textView.setSingleLine(true); 299 textView.setHint("الروبوت"); 300 }); 301 mInstrumentation.waitForIdleSync(); 302 303 onView(withId(R.id.textview)).perform(replaceText("test")); 304 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(1)); 305 clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.cut)); 306 onView(withId(R.id.textview)).perform(longClick()); 307 sleepForFloatingToolbarPopup(); 308 309 assertFloatingToolbarIsDisplayed(); 310 } 311 312 @Test 313 public void testToolbarAppearsAfterLinkClicked() throws Throwable { 314 runToolbarAppearsAfterLinkClickedTest(R.id.textview); 315 } 316 317 @Test 318 public void testToolbarAppearsAfterLinkClickedNonselectable() throws Throwable { 319 runToolbarAppearsAfterLinkClickedTest(R.id.nonselectable_textview); 320 } 321 322 private void runToolbarAppearsAfterLinkClickedTest(int id) throws Throwable { 323 TextView textView = mActivity.findViewById(id); 324 useSystemDefaultTextClassifier(); 325 TextClassificationManager textClassificationManager = 326 mActivity.getSystemService(TextClassificationManager.class); 327 TextClassifier textClassifier = textClassificationManager.getTextClassifier(); 328 SpannableString content = new SpannableString("Call me at +19148277737"); 329 TextLinks links = textClassifier.generateLinks(content); 330 links.apply(content, null); 331 332 mActivityRule.runOnUiThread(() -> { 333 textView.setText(content); 334 textView.setMovementMethod(LinkMovementMethod.getInstance()); 335 }); 336 mInstrumentation.waitForIdleSync(); 337 338 // Wait for the UI thread to refresh 339 Thread.sleep(1000); 340 341 TextLinks.TextLink textLink = links.getLinks().iterator().next(); 342 int position = (textLink.getStart() + textLink.getEnd()) / 2; 343 onView(withId(id)).perform(clickOnTextAtIndex(position)); 344 sleepForFloatingToolbarPopup(); 345 assertFloatingToolbarIsDisplayed(); 346 } 347 348 @Test 349 public void testToolbarAndInsertionHandle() { 350 final String text = "text"; 351 onView(withId(R.id.textview)).perform(replaceText(text)); 352 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); 353 354 onHandleView(com.android.internal.R.id.insertion_handle).perform(click()); 355 sleepForFloatingToolbarPopup(); 356 assertFloatingToolbarIsDisplayed(); 357 358 assertFloatingToolbarContainsItem( 359 mActivity.getString(com.android.internal.R.string.selectAll)); 360 assertFloatingToolbarDoesNotContainItem( 361 mActivity.getString(com.android.internal.R.string.copy)); 362 assertFloatingToolbarDoesNotContainItem( 363 mActivity.getString(com.android.internal.R.string.cut)); 364 } 365 366 @Test 367 public void testToolbarAndSelectionHandle() { 368 final String text = "abcd efg hijk"; 369 onView(withId(R.id.textview)).perform(replaceText(text)); 370 371 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("f"))); 372 sleepForFloatingToolbarPopup(); 373 assertFloatingToolbarIsDisplayed(); 374 375 assertFloatingToolbarContainsItem( 376 mActivity.getString(com.android.internal.R.string.selectAll)); 377 assertFloatingToolbarContainsItem( 378 mActivity.getString(com.android.internal.R.string.copy)); 379 assertFloatingToolbarContainsItem( 380 mActivity.getString(com.android.internal.R.string.cut)); 381 382 final TextView textView = mActivity.findViewById(R.id.textview); 383 onHandleView(com.android.internal.R.id.selection_start_handle) 384 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a'))); 385 sleepForFloatingToolbarPopup(); 386 assertFloatingToolbarIsDisplayed(); 387 388 onHandleView(com.android.internal.R.id.selection_end_handle) 389 .perform(dragHandle(textView, Handle.SELECTION_END, text.length())); 390 sleepForFloatingToolbarPopup(); 391 assertFloatingToolbarIsDisplayed(); 392 393 assertFloatingToolbarDoesNotContainItem( 394 mActivity.getString(com.android.internal.R.string.selectAll)); 395 assertFloatingToolbarContainsItem( 396 mActivity.getString(com.android.internal.R.string.copy)); 397 assertFloatingToolbarContainsItem( 398 mActivity.getString(com.android.internal.R.string.cut)); 399 } 400 401 @Test 402 public void testInsertionHandle() { 403 final String text = "abcd efg hijk "; 404 onView(withId(R.id.textview)).perform(replaceText(text)); 405 406 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); 407 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); 408 409 final TextView textView = mActivity.findViewById(R.id.textview); 410 411 onHandleView(com.android.internal.R.id.insertion_handle) 412 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a'))); 413 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a"))); 414 415 onHandleView(com.android.internal.R.id.insertion_handle) 416 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f'))); 417 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f"))); 418 } 419 420 @Test 421 public void testInsertionHandle_multiLine() { 422 final String text = "abcd\n" + "efg\n" + "hijk\n"; 423 onView(withId(R.id.textview)).perform(replaceText(text)); 424 425 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); 426 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); 427 428 final TextView textView = mActivity.findViewById(R.id.textview); 429 430 onHandleView(com.android.internal.R.id.insertion_handle) 431 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a'))); 432 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a"))); 433 434 onHandleView(com.android.internal.R.id.insertion_handle) 435 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f'))); 436 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f"))); 437 } 438 439 @Test 440 public void testSelectionHandles() { 441 final String text = "abcd efg hijk lmn"; 442 onView(withId(R.id.textview)).perform(replaceText(text)); 443 444 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f'))); 445 446 onHandleView(com.android.internal.R.id.selection_start_handle) 447 .check(matches(isDisplayed())); 448 onHandleView(com.android.internal.R.id.selection_end_handle) 449 .check(matches(isDisplayed())); 450 451 final TextView textView = mActivity.findViewById(R.id.textview); 452 onHandleView(com.android.internal.R.id.selection_start_handle) 453 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a'))); 454 onView(withId(R.id.textview)).check(hasSelection("abcd efg")); 455 456 onHandleView(com.android.internal.R.id.selection_end_handle) 457 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('k') + 1)); 458 onView(withId(R.id.textview)).check(hasSelection("abcd efg hijk")); 459 } 460 461 @Test 462 public void testSelectionHandles_bidi() { 463 final String text = "abc \u0621\u0622\u0623 def"; 464 onView(withId(R.id.textview)).perform(replaceText(text)); 465 466 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0622'))); 467 468 onHandleView(com.android.internal.R.id.selection_start_handle) 469 .check(matches(isDisplayed())); 470 onHandleView(com.android.internal.R.id.selection_end_handle) 471 .check(matches(isDisplayed())); 472 473 onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623")); 474 475 final TextView textView = mActivity.findViewById(R.id.textview); 476 onHandleView(com.android.internal.R.id.selection_start_handle) 477 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('f'))); 478 onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623")); 479 480 onHandleView(com.android.internal.R.id.selection_end_handle) 481 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a'))); 482 onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623")); 483 484 onHandleView(com.android.internal.R.id.selection_start_handle) 485 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u0623'), 486 false)); 487 onView(withId(R.id.textview)).check(hasSelection("\u0623")); 488 489 onHandleView(com.android.internal.R.id.selection_start_handle) 490 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u0621'), 491 false)); 492 onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623")); 493 494 onHandleView(com.android.internal.R.id.selection_start_handle) 495 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a'))); 496 onView(withId(R.id.textview)).check(hasSelection("abc \u0621\u0622\u0623")); 497 498 onHandleView(com.android.internal.R.id.selection_end_handle) 499 .perform(dragHandle(textView, Handle.SELECTION_END, text.length())); 500 onView(withId(R.id.textview)).check(hasSelection("abc \u0621\u0622\u0623 def")); 501 } 502 503 @Test 504 public void testSelectionHandles_multiLine() { 505 final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr"; 506 onView(withId(R.id.textview)).perform(replaceText(text)); 507 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i'))); 508 509 final TextView textView = mActivity.findViewById(R.id.textview); 510 onHandleView(com.android.internal.R.id.selection_start_handle) 511 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('e'))); 512 onView(withId(R.id.textview)).check(hasSelection("efg\nhijk")); 513 514 onHandleView(com.android.internal.R.id.selection_start_handle) 515 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a'))); 516 onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk")); 517 518 onHandleView(com.android.internal.R.id.selection_end_handle) 519 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('n') + 1)); 520 onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn")); 521 522 onHandleView(com.android.internal.R.id.selection_end_handle) 523 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('r') + 1)); 524 onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn\nopqr")); 525 } 526 527 @Suppress // Consistently failing. 528 @Test 529 public void testSelectionHandles_multiLine_rtl() { 530 // Arabic text. 531 final String text = "\u062A\u062B\u062C\n" + "\u062D\u062E\u062F\n" 532 + "\u0630\u0631\u0632\n" + "\u0633\u0634\u0635\n" + "\u0636\u0637\u0638\n" 533 + "\u0639\u063A\u063B"; 534 onView(withId(R.id.textview)).perform(replaceText(text)); 535 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0634'))); 536 537 final TextView textView = mActivity.findViewById(R.id.textview); 538 onHandleView(com.android.internal.R.id.selection_start_handle) 539 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062E'))); 540 onView(withId(R.id.textview)).check(hasSelection( 541 text.substring(text.indexOf('\u062D'), text.indexOf('\u0635') + 1))); 542 543 onHandleView(com.android.internal.R.id.selection_start_handle) 544 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062A'))); 545 onView(withId(R.id.textview)).check(hasSelection( 546 text.substring(text.indexOf('\u062A'), text.indexOf('\u0635') + 1))); 547 548 onHandleView(com.android.internal.R.id.selection_end_handle) 549 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u0638'))); 550 onView(withId(R.id.textview)).check(hasSelection( 551 text.substring(text.indexOf('\u062A'), text.indexOf('\u0638') + 1))); 552 553 onHandleView(com.android.internal.R.id.selection_end_handle) 554 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u063B'))); 555 onView(withId(R.id.textview)).check(hasSelection(text)); 556 } 557 558 @Test 559 public void testSelectionHandles_doesNotPassAnotherHandle() { 560 final String text = "abcd efg hijk lmn"; 561 onView(withId(R.id.textview)).perform(replaceText(text)); 562 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f'))); 563 564 final TextView textView = mActivity.findViewById(R.id.textview); 565 onHandleView(com.android.internal.R.id.selection_start_handle) 566 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('l'))); 567 onView(withId(R.id.textview)).check(hasSelection("g")); 568 569 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f'))); 570 onHandleView(com.android.internal.R.id.selection_end_handle) 571 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a'))); 572 onView(withId(R.id.textview)).check(hasSelection("e")); 573 } 574 575 @Test 576 public void testSelectionHandles_doesNotPassAnotherHandle_multiLine() { 577 final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr"; 578 onView(withId(R.id.textview)).perform(replaceText(text)); 579 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i'))); 580 581 final TextView textView = mActivity.findViewById(R.id.textview); 582 onHandleView(com.android.internal.R.id.selection_start_handle) 583 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('r') + 1)); 584 onView(withId(R.id.textview)).check(hasSelection("k")); 585 586 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i'))); 587 onHandleView(com.android.internal.R.id.selection_end_handle) 588 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a'))); 589 onView(withId(R.id.textview)).check(hasSelection("h")); 590 } 591 592 @Test 593 public void testSelectionHandles_snapToWordBoundary() { 594 final String text = "abcd efg hijk lmn opqr"; 595 onView(withId(R.id.textview)).perform(replaceText(text)); 596 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i'))); 597 598 final TextView textView = mActivity.findViewById(R.id.textview); 599 600 onHandleView(com.android.internal.R.id.selection_start_handle) 601 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('f'))); 602 onView(withId(R.id.textview)).check(hasSelection("efg hijk")); 603 604 onHandleView(com.android.internal.R.id.selection_start_handle) 605 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('d') + 1)); 606 onView(withId(R.id.textview)).check(hasSelection("efg hijk")); 607 608 609 onHandleView(com.android.internal.R.id.selection_start_handle) 610 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('c'))); 611 onView(withId(R.id.textview)).check(hasSelection("abcd efg hijk")); 612 613 onHandleView(com.android.internal.R.id.selection_start_handle) 614 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('d'))); 615 onView(withId(R.id.textview)).check(hasSelection("d efg hijk")); 616 617 onHandleView(com.android.internal.R.id.selection_start_handle) 618 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('b'))); 619 onView(withId(R.id.textview)).check(hasSelection("bcd efg hijk")); 620 621 onView(withId(R.id.textview)).perform(click()); 622 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i'))); 623 624 onHandleView(com.android.internal.R.id.selection_end_handle) 625 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('n'))); 626 onView(withId(R.id.textview)).check(hasSelection("hijk lmn")); 627 628 onHandleView(com.android.internal.R.id.selection_end_handle) 629 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('o'))); 630 onView(withId(R.id.textview)).check(hasSelection("hijk lmn")); 631 632 onHandleView(com.android.internal.R.id.selection_end_handle) 633 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('q'))); 634 onView(withId(R.id.textview)).check(hasSelection("hijk lmn opqr")); 635 636 onHandleView(com.android.internal.R.id.selection_end_handle) 637 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('p'))); 638 onView(withId(R.id.textview)).check(hasSelection("hijk lmn o")); 639 640 onHandleView(com.android.internal.R.id.selection_end_handle) 641 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('r'))); 642 onView(withId(R.id.textview)).check(hasSelection("hijk lmn opq")); 643 } 644 645 @Test 646 public void testSelectionHandles_snapToWordBoundary_multiLine() { 647 final String text = "abcd efg\n" + "hijk lmn\n" + "opqr stu"; 648 onView(withId(R.id.textview)).perform(replaceText(text)); 649 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('m'))); 650 651 final TextView textView = mActivity.findViewById(R.id.textview); 652 653 onHandleView(com.android.internal.R.id.selection_start_handle) 654 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('c'))); 655 onView(withId(R.id.textview)).check(hasSelection("abcd efg\nhijk lmn")); 656 657 onHandleView(com.android.internal.R.id.selection_start_handle) 658 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('g'))); 659 onView(withId(R.id.textview)).check(hasSelection("g\nhijk lmn")); 660 661 onHandleView(com.android.internal.R.id.selection_start_handle) 662 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('m'))); 663 onView(withId(R.id.textview)).check(hasSelection("lmn")); 664 665 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i'))); 666 667 onHandleView(com.android.internal.R.id.selection_end_handle) 668 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('u'))); 669 onView(withId(R.id.textview)).check(hasSelection("hijk lmn\nopqr stu")); 670 671 onHandleView(com.android.internal.R.id.selection_end_handle) 672 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('p'))); 673 onView(withId(R.id.textview)).check(hasSelection("hijk lmn\no")); 674 675 onHandleView(com.android.internal.R.id.selection_end_handle) 676 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('i'))); 677 onView(withId(R.id.textview)).check(hasSelection("hijk")); 678 } 679 680 @Test 681 public void testSelectionHandles_visibleEvenWithEmptyMenu() { 682 ((TextView) mActivity.findViewById(R.id.textview)).setCustomSelectionActionModeCallback( 683 new ActionMode.Callback() { 684 @Override 685 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 686 menu.clear(); 687 return true; 688 } 689 690 @Override 691 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 692 menu.clear(); 693 return true; 694 } 695 696 @Override 697 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 698 return false; 699 } 700 701 @Override 702 public void onDestroyActionMode(ActionMode mode) {} 703 }); 704 final String text = "abcd efg hijk lmn"; 705 onView(withId(R.id.textview)).perform(replaceText(text)); 706 707 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f'))); 708 709 onHandleView(com.android.internal.R.id.selection_start_handle) 710 .check(matches(isDisplayed())); 711 onHandleView(com.android.internal.R.id.selection_end_handle) 712 .check(matches(isDisplayed())); 713 } 714 715 @Test 716 public void testSetSelectionAndActionMode() throws Throwable { 717 final TextView textView = mActivity.findViewById(R.id.textview); 718 final ActionMode.Callback amCallback = mock(ActionMode.Callback.class); 719 when(amCallback.onCreateActionMode(any(ActionMode.class), any(Menu.class))) 720 .thenReturn(true); 721 when(amCallback.onPrepareActionMode(any(ActionMode.class), any(Menu.class))) 722 .thenReturn(true); 723 textView.setCustomSelectionActionModeCallback(amCallback); 724 725 final String text = "abc def"; 726 onView(withId(R.id.textview)).perform(replaceText(text)); 727 mActivityRule.runOnUiThread( 728 () -> Selection.setSelection((Spannable) textView.getText(), 0, 3)); 729 mInstrumentation.waitForIdleSync(); 730 // Don't automatically start action mode. 731 verify(amCallback, never()).onCreateActionMode(any(ActionMode.class), any(Menu.class)); 732 // Make sure that "Select All" is included in the selection action mode when the entire text 733 // is not selected. 734 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e'))); 735 sleepForFloatingToolbarPopup(); 736 assertFloatingToolbarIsDisplayed(); 737 // Changing the selection range by API should not interrupt the selection action mode. 738 mActivityRule.runOnUiThread( 739 () -> Selection.setSelection((Spannable) textView.getText(), 0, 3)); 740 mInstrumentation.waitForIdleSync(); 741 sleepForFloatingToolbarPopup(); 742 assertFloatingToolbarIsDisplayed(); 743 assertFloatingToolbarContainsItem( 744 mActivity.getString(com.android.internal.R.string.selectAll)); 745 // Make sure that "Select All" is no longer included when the entire text is selected by 746 // API. 747 mActivityRule.runOnUiThread( 748 () -> Selection.setSelection((Spannable) textView.getText(), 0, text.length())); 749 mInstrumentation.waitForIdleSync(); 750 751 sleepForFloatingToolbarPopup(); 752 assertFloatingToolbarIsDisplayed(); 753 assertFloatingToolbarDoesNotContainItem( 754 mActivity.getString(com.android.internal.R.string.selectAll)); 755 // Make sure that shrinking the selection range to cursor (an empty range) by API 756 // terminates selection action mode and does not trigger the insertion action mode. 757 mActivityRule.runOnUiThread( 758 () -> Selection.setSelection((Spannable) textView.getText(), 0)); 759 mInstrumentation.waitForIdleSync(); 760 761 // Make sure that user click can trigger the insertion action mode. 762 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); 763 onHandleView(com.android.internal.R.id.insertion_handle).perform(click()); 764 sleepForFloatingToolbarPopup(); 765 assertFloatingToolbarIsDisplayed(); 766 // Make sure that an existing insertion action mode keeps alive after the insertion point is 767 // moved by API. 768 mActivityRule.runOnUiThread( 769 () -> Selection.setSelection((Spannable) textView.getText(), 0)); 770 mInstrumentation.waitForIdleSync(); 771 772 sleepForFloatingToolbarPopup(); 773 assertFloatingToolbarIsDisplayed(); 774 assertFloatingToolbarDoesNotContainItem( 775 mActivity.getString(com.android.internal.R.string.copy)); 776 // Make sure that selection action mode is started after selection is created by API when 777 // insertion action mode is active. 778 mActivityRule.runOnUiThread( 779 () -> Selection.setSelection((Spannable) textView.getText(), 1, text.length())); 780 mInstrumentation.waitForIdleSync(); 781 782 sleepForFloatingToolbarPopup(); 783 assertFloatingToolbarIsDisplayed(); 784 assertFloatingToolbarContainsItem( 785 mActivity.getString(com.android.internal.R.string.copy)); 786 } 787 788 @Test 789 public void testTransientState() throws Throwable { 790 final String text = "abc def"; 791 onView(withId(R.id.textview)).perform(replaceText(text)); 792 793 final TextView textView = mActivity.findViewById(R.id.textview); 794 assertFalse(textView.hasTransientState()); 795 796 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('b'))); 797 // hasTransientState should return true when user generated selection is active. 798 assertTrue(textView.hasTransientState()); 799 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('d'))); 800 // hasTransientState should return false as the selection has been cleared. 801 assertFalse(textView.hasTransientState()); 802 mActivityRule.runOnUiThread( 803 () -> Selection.setSelection((Spannable) textView.getText(), 0, text.length())); 804 mInstrumentation.waitForIdleSync(); 805 806 // hasTransientState should return false when selection is created by API. 807 assertFalse(textView.hasTransientState()); 808 } 809 810 @Test 811 public void testResetMenuItemTitle() throws Throwable { 812 mActivity.getSystemService(TextClassificationManager.class).setTextClassifier(null); 813 final TextView textView = mActivity.findViewById(R.id.textview); 814 final int itemId = 1; 815 final String title1 = " AFIGBO"; 816 final int index = title1.indexOf('I'); 817 final String title2 = title1.substring(index); 818 final String[] title = new String[]{title1}; 819 mActivityRule.runOnUiThread(() -> textView.setCustomSelectionActionModeCallback( 820 new ActionMode.Callback() { 821 @Override 822 public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { 823 return true; 824 } 825 826 @Override 827 public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { 828 menu.removeItem(itemId); 829 menu.add(Menu.NONE /* group */, itemId, 0 /* order */, title[0]); 830 return true; 831 } 832 833 @Override 834 public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { 835 return false; 836 } 837 838 @Override 839 public void onDestroyActionMode(ActionMode actionMode) { 840 } 841 })); 842 mInstrumentation.waitForIdleSync(); 843 844 onView(withId(R.id.textview)).perform(replaceText(title1)); 845 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(index)); 846 sleepForFloatingToolbarPopup(); 847 assertFloatingToolbarContainsItem(title1); 848 849 // Change the menu item title. 850 title[0] = title2; 851 // Change the selection to invalidate the action mode without restarting it. 852 onHandleView(com.android.internal.R.id.selection_start_handle) 853 .perform(dragHandle(textView, Handle.SELECTION_START, index)); 854 sleepForFloatingToolbarPopup(); 855 assertFloatingToolbarContainsItem(title2); 856 } 857 858 @Test 859 public void testAssistItemIsAtIndexZero() throws Throwable { 860 useSystemDefaultTextClassifier(); 861 final TextView textView = mActivity.findViewById(R.id.textview); 862 mActivityRule.runOnUiThread(() -> textView.setCustomSelectionActionModeCallback( 863 new ActionMode.Callback() { 864 @Override 865 public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { 866 // Create another item at order position 0 to confirm that it will never be 867 // placed before the textAssist item. 868 menu.add(Menu.NONE, 0 /* id */, 0 /* order */, "Test"); 869 return true; 870 } 871 872 @Override 873 public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { 874 return true; 875 } 876 877 @Override 878 public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { 879 return false; 880 } 881 882 @Override 883 public void onDestroyActionMode(ActionMode actionMode) { 884 } 885 })); 886 mInstrumentation.waitForIdleSync(); 887 final String text = "droid@android.com"; 888 889 onView(withId(R.id.textview)).perform(replaceText(text)); 890 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('@'))); 891 sleepForFloatingToolbarPopup(); 892 assertFloatingToolbarItemIndex(android.R.id.textAssist, 0); 893 } 894 895 @Test 896 public void testNoAssistItemForPasswordField() throws Throwable { 897 useSystemDefaultTextClassifier(); 898 final TextView textView = mActivity.findViewById(R.id.textview); 899 mActivityRule.runOnUiThread(() -> { 900 textView.setInputType( 901 InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); 902 }); 903 mInstrumentation.waitForIdleSync(); 904 final String password = "afigbo@android.com"; 905 906 onView(withId(R.id.textview)).perform(replaceText(password)); 907 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(password.indexOf('@'))); 908 sleepForFloatingToolbarPopup(); 909 assertFloatingToolbarDoesNotContainItem(android.R.id.textAssist); 910 } 911 912 @Test 913 public void testPastePlainText_menuAction() { 914 initializeClipboardWithText(TextStyle.STYLED); 915 916 onView(withId(R.id.textview)).perform(replaceText("")); 917 onView(withId(R.id.textview)).perform(longClick()); 918 sleepForFloatingToolbarPopup(); 919 clickFloatingToolbarItem( 920 mActivity.getString(com.android.internal.R.string.paste_as_plain_text)); 921 mInstrumentation.waitForIdleSync(); 922 923 onView(withId(R.id.textview)).check(matches(withText("styledtext"))); 924 onView(withId(R.id.textview)).check(doesNotHaveStyledText()); 925 } 926 927 @Test 928 public void testPastePlainText_noMenuItemForPlainText() { 929 initializeClipboardWithText(TextStyle.PLAIN); 930 931 onView(withId(R.id.textview)).perform(replaceText("")); 932 onView(withId(R.id.textview)).perform(longClick()); 933 sleepForFloatingToolbarPopup(); 934 935 assertFloatingToolbarDoesNotContainItem( 936 mActivity.getString(com.android.internal.R.string.paste_as_plain_text)); 937 } 938 939 private void useSystemDefaultTextClassifier() { 940 mActivity.getSystemService(TextClassificationManager.class).setTextClassifier(null); 941 } 942 943 private void initializeClipboardWithText(TextStyle textStyle) { 944 final ClipData clip; 945 switch (textStyle) { 946 case STYLED: 947 clip = ClipData.newHtmlText("html", "styledtext", "<b>styledtext</b>"); 948 break; 949 case PLAIN: 950 clip = ClipData.newPlainText("plain", "plaintext"); 951 break; 952 default: 953 throw new IllegalArgumentException("Invalid text style"); 954 } 955 mActivity.getWindow().getDecorView().post(() -> 956 mActivity.getSystemService(ClipboardManager.class).setPrimaryClip(clip)); 957 mInstrumentation.waitForIdleSync(); 958 } 959 960 private enum TextStyle { 961 PLAIN, STYLED 962 } 963} 964