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