2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package android.widget;
19 import static android.support.test.espresso.Espresso.onView;
20 import static android.support.test.espresso.action.ViewActions.click;
21 import static android.support.test.espresso.action.ViewActions.longClick;
22 import static android.support.test.espresso.action.ViewActions.pressKey;
23 import static android.support.test.espresso.action.ViewActions.replaceText;
24 import static android.support.test.espresso.assertion.ViewAssertions.matches;
25 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
26 import static android.support.test.espresso.matcher.ViewMatchers.withId;
27 import static android.support.test.espresso.matcher.ViewMatchers.withText;
28 import static android.widget.espresso.CustomViewActions.longPressAtRelativeCoordinates;
29 import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
30 import static android.widget.espresso.DragHandleUtils.onHandleView;
31 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
32 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarDoesNotContainItem;
33 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
34 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsNotDisplayed;
35 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarItemIndex;
36 import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem;
37 import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
38 import static android.widget.espresso.TextViewActions.Handle;
39 import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
40 import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex;
41 import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText;
42 import static android.widget.espresso.TextViewActions.dragHandle;
43 import static android.widget.espresso.TextViewActions.longPressAndDragOnText;
44 import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex;
45 import static android.widget.espresso.TextViewAssertions.doesNotHaveStyledText;
46 import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
47 import static android.widget.espresso.TextViewAssertions.hasSelection;
49 import static junit.framework.Assert.assertFalse;
50 import static junit.framework.Assert.assertTrue;
52 import static org.hamcrest.Matchers.anyOf;
53 import static org.hamcrest.Matchers.is;
55 import android.app.Activity;
56 import android.app.Instrumentation;
57 import android.content.ClipData;
58 import android.content.ClipboardManager;
59 import android.support.test.InstrumentationRegistry;
60 import android.support.test.espresso.action.EspressoKey;
61 import android.support.test.filters.MediumTest;
62 import android.support.test.rule.ActivityTestRule;
63 import android.support.test.runner.AndroidJUnit4;
64 import android.text.InputType;
65 import android.text.Selection;
66 import android.text.Spannable;
67 import android.view.ActionMode;
68 import android.view.KeyEvent;
69 import android.view.Menu;
70 import android.view.MenuItem;
71 import android.view.textclassifier.TextClassificationManager;
72 import android.view.textclassifier.TextClassifier;
73 import android.widget.espresso.CustomViewActions.RelativeCoordinatesProvider;
75 import com.android.frameworks.coretests.R;
77 import org.junit.Before;
78 import org.junit.Rule;
79 import org.junit.Test;
80 import org.junit.runner.RunWith;
83 * Tests the TextView widget from an Activity
85 @RunWith(AndroidJUnit4.class)
87 public class TextViewActivityTest {
90 public ActivityTestRule<TextViewActivity> mActivityRule = new ActivityTestRule<>(
91 TextViewActivity.class);
93 private Activity mActivity;
94 private Instrumentation mInstrumentation;
98 mActivity = mActivityRule.getActivity();
99 mInstrumentation = InstrumentationRegistry.getInstrumentation();
100 mActivity.getSystemService(TextClassificationManager.class)
101 .setTextClassifier(TextClassifier.NO_OP);
105 public void testTypedTextIsOnScreen() {
106 final String helloWorld = "Hello world!";
107 // We use replaceText instead of typeTextIntoFocusedView to input text to avoid
108 // unintentional interactions with software keyboard.
109 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
111 onView(withId(R.id.textview)).check(matches(withText(helloWorld)));
114 public void testPositionCursorAtTextAtIndex() {
115 final String helloWorld = "Hello world!";
116 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
117 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("world")));
119 // Delete text at specified index and see if we got the right one.
120 onView(withId(R.id.textview)).perform(pressKey(KeyEvent.KEYCODE_FORWARD_DEL));
121 onView(withId(R.id.textview)).check(matches(withText("Hello orld!")));
125 public void testPositionCursorAtTextAtIndex_arabic() {
126 // Arabic text. The expected cursorable boundary is
127 // | \u0623 \u064F | \u067A | \u0633 \u0652 |
128 final String text = "\u0623\u064F\u067A\u0633\u0652";
129 onView(withId(R.id.textview)).perform(replaceText(text));
131 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
132 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
133 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1));
134 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2))));
135 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2));
136 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
137 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(3));
138 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(3));
139 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(4));
140 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(3), is(5))));
141 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(5));
142 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(5));
146 public void testPositionCursorAtTextAtIndex_devanagari() {
147 // Devanagari text. The expected cursorable boundary is | \u0915 \u093E |
148 final String text = "\u0915\u093E";
149 onView(withId(R.id.textview)).perform(replaceText(text));
151 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
152 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
153 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1));
154 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2))));
155 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2));
156 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
160 public void testLongPressToSelect() {
161 final String helloWorld = "Hello Kirk!";
162 onView(withId(R.id.textview)).perform(click());
163 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
164 onView(withId(R.id.textview)).perform(
165 longPressOnTextAtIndex(helloWorld.indexOf("Kirk")));
167 onView(withId(R.id.textview)).check(hasSelection("Kirk"));
171 public void testLongPressEmptySpace() {
172 final String helloWorld = "Hello big round sun!";
173 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
174 // Move cursor somewhere else
175 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("big")));
176 // Long-press at end of line.
177 onView(withId(R.id.textview)).perform(longPressAtRelativeCoordinates(
178 RelativeCoordinatesProvider.HorizontalReference.RIGHT, -5,
179 RelativeCoordinatesProvider.VerticalReference.CENTER, 0));
181 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(helloWorld.length()));
185 public void testLongPressAndDragToSelect() {
186 final String helloWorld = "Hello little handsome boy!";
187 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
188 onView(withId(R.id.textview)).perform(
189 longPressAndDragOnText(helloWorld.indexOf("little"), helloWorld.indexOf(" boy!")));
191 onView(withId(R.id.textview)).check(hasSelection("little handsome"));
195 public void testLongPressAndDragToSelect_emoji() {
196 final String text = "\uD83D\uDE00\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03";
197 onView(withId(R.id.textview)).perform(replaceText(text));
199 onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 6));
200 onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE02"));
202 onView(withId(R.id.textview)).perform(click());
204 onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 2));
205 onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE01"));
209 public void testDragAndDrop() {
210 final String text = "abc def ghi.";
211 onView(withId(R.id.textview)).perform(replaceText(text));
212 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("e")));
214 onView(withId(R.id.textview)).perform(
215 longPressAndDragOnText(text.indexOf("e"), text.length()));
217 onView(withId(R.id.textview)).check(matches(withText("abc ghi.def")));
218 onView(withId(R.id.textview)).check(hasSelection(""));
219 assertNoSelectionHandles();
220 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length()));
222 // Test undo returns to the original state.
223 onView(withId(R.id.textview)).perform(pressKey(
224 (new EspressoKey.Builder()).withCtrlPressed(true).withKeyCode(KeyEvent.KEYCODE_Z)
226 onView(withId(R.id.textview)).check(matches(withText(text)));
230 public void testDoubleTapToSelect() {
231 final String helloWorld = "Hello SuetYi!";
232 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
234 onView(withId(R.id.textview)).perform(
235 doubleClickOnTextAtIndex(helloWorld.indexOf("SuetYi")));
237 onView(withId(R.id.textview)).check(hasSelection("SuetYi"));
241 public void testDoubleTapAndDragToSelect() {
242 final String helloWorld = "Hello young beautiful person!";
243 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
244 onView(withId(R.id.textview)).perform(doubleTapAndDragOnText(helloWorld.indexOf("young"),
245 helloWorld.indexOf(" person!")));
247 onView(withId(R.id.textview)).check(hasSelection("young beautiful"));
251 public void testDoubleTapAndDragToSelect_multiLine() {
252 final String helloWorld = "abcd\n" + "efg\n" + "hijklm\n" + "nop";
253 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
254 onView(withId(R.id.textview)).perform(
255 doubleTapAndDragOnText(helloWorld.indexOf("m"), helloWorld.indexOf("a")));
256 onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijklm"));
260 public void testSelectBackwordsByTouch() {
261 final String helloWorld = "Hello king of the Jungle!";
262 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
263 onView(withId(R.id.textview)).perform(
264 doubleTapAndDragOnText(helloWorld.indexOf(" Jungle!"), helloWorld.indexOf("king")));
266 onView(withId(R.id.textview)).check(hasSelection("king of the"));
270 public void testToolbarAppearsAfterSelection() {
271 final String text = "Toolbar appears after selection.";
272 assertFloatingToolbarIsNotDisplayed();
273 onView(withId(R.id.textview)).perform(replaceText(text));
274 onView(withId(R.id.textview)).perform(
275 longPressOnTextAtIndex(text.indexOf("appears")));
277 sleepForFloatingToolbarPopup();
278 assertFloatingToolbarIsDisplayed();
280 final String text2 = "Toolbar disappears after typing text.";
281 onView(withId(R.id.textview)).perform(replaceText(text2));
282 sleepForFloatingToolbarPopup();
283 assertFloatingToolbarIsNotDisplayed();
287 public void testToolbarAppearsAfterSelection_withFirstStringLtrAlgorithmAndRtlHint()
289 // after the hint layout change, the floating toolbar was not visible in the case below
290 // this test tests that the floating toolbar is displayed on the screen and is visible to
292 mActivityRule.runOnUiThread(() -> {
293 final TextView textView = mActivity.findViewById(R.id.textview);
294 textView.setTextDirection(TextView.TEXT_DIRECTION_FIRST_STRONG_LTR);
295 textView.setInputType(InputType.TYPE_CLASS_TEXT);
296 textView.setSingleLine(true);
297 textView.setHint("الروبوت");
299 mInstrumentation.waitForIdleSync();
301 onView(withId(R.id.textview)).perform(replaceText("test"));
302 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(1));
303 clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.cut));
304 onView(withId(R.id.textview)).perform(longClick());
305 sleepForFloatingToolbarPopup();
307 assertFloatingToolbarIsDisplayed();
311 public void testToolbarAndInsertionHandle() {
312 final String text = "text";
313 onView(withId(R.id.textview)).perform(replaceText(text));
314 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
315 assertFloatingToolbarIsNotDisplayed();
317 onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
318 sleepForFloatingToolbarPopup();
319 assertFloatingToolbarIsDisplayed();
321 assertFloatingToolbarContainsItem(
322 mActivity.getString(com.android.internal.R.string.selectAll));
323 assertFloatingToolbarDoesNotContainItem(
324 mActivity.getString(com.android.internal.R.string.copy));
325 assertFloatingToolbarDoesNotContainItem(
326 mActivity.getString(com.android.internal.R.string.cut));
330 public void testToolbarAndSelectionHandle() {
331 final String text = "abcd efg hijk";
332 onView(withId(R.id.textview)).perform(replaceText(text));
334 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("f")));
335 sleepForFloatingToolbarPopup();
336 assertFloatingToolbarIsDisplayed();
338 assertFloatingToolbarContainsItem(
339 mActivity.getString(com.android.internal.R.string.selectAll));
340 assertFloatingToolbarContainsItem(
341 mActivity.getString(com.android.internal.R.string.copy));
342 assertFloatingToolbarContainsItem(
343 mActivity.getString(com.android.internal.R.string.cut));
345 final TextView textView = mActivity.findViewById(R.id.textview);
346 onHandleView(com.android.internal.R.id.selection_start_handle)
347 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
348 sleepForFloatingToolbarPopup();
349 assertFloatingToolbarIsDisplayed();
351 onHandleView(com.android.internal.R.id.selection_end_handle)
352 .perform(dragHandle(textView, Handle.SELECTION_END, text.length()));
353 sleepForFloatingToolbarPopup();
354 assertFloatingToolbarIsDisplayed();
356 assertFloatingToolbarDoesNotContainItem(
357 mActivity.getString(com.android.internal.R.string.selectAll));
358 assertFloatingToolbarContainsItem(
359 mActivity.getString(com.android.internal.R.string.copy));
360 assertFloatingToolbarContainsItem(
361 mActivity.getString(com.android.internal.R.string.cut));
365 public void testInsertionHandle() {
366 final String text = "abcd efg hijk ";
367 onView(withId(R.id.textview)).perform(replaceText(text));
369 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
370 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
372 final TextView textView = mActivity.findViewById(R.id.textview);
374 onHandleView(com.android.internal.R.id.insertion_handle)
375 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a')));
376 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a")));
378 onHandleView(com.android.internal.R.id.insertion_handle)
379 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f')));
380 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
384 public void testInsertionHandle_multiLine() {
385 final String text = "abcd\n" + "efg\n" + "hijk\n";
386 onView(withId(R.id.textview)).perform(replaceText(text));
388 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
389 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
391 final TextView textView = mActivity.findViewById(R.id.textview);
393 onHandleView(com.android.internal.R.id.insertion_handle)
394 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a')));
395 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a")));
397 onHandleView(com.android.internal.R.id.insertion_handle)
398 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f')));
399 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
403 public void testSelectionHandles() {
404 final String text = "abcd efg hijk lmn";
405 onView(withId(R.id.textview)).perform(replaceText(text));
407 assertNoSelectionHandles();
409 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
411 onHandleView(com.android.internal.R.id.selection_start_handle)
412 .check(matches(isDisplayed()));
413 onHandleView(com.android.internal.R.id.selection_end_handle)
414 .check(matches(isDisplayed()));
416 final TextView textView = mActivity.findViewById(R.id.textview);
417 onHandleView(com.android.internal.R.id.selection_start_handle)
418 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
419 onView(withId(R.id.textview)).check(hasSelection("abcd efg"));
421 onHandleView(com.android.internal.R.id.selection_end_handle)
422 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('k') + 1));
423 onView(withId(R.id.textview)).check(hasSelection("abcd efg hijk"));
427 public void testSelectionHandles_bidi() {
428 final String text = "abc \u0621\u0622\u0623 def";
429 onView(withId(R.id.textview)).perform(replaceText(text));
431 assertNoSelectionHandles();
433 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0622')));
435 onHandleView(com.android.internal.R.id.selection_start_handle)
436 .check(matches(isDisplayed()));
437 onHandleView(com.android.internal.R.id.selection_end_handle)
438 .check(matches(isDisplayed()));
440 onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
442 final TextView textView = mActivity.findViewById(R.id.textview);
443 onHandleView(com.android.internal.R.id.selection_start_handle)
444 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('f')));
445 onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
447 onHandleView(com.android.internal.R.id.selection_end_handle)
448 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a')));
449 onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
451 onHandleView(com.android.internal.R.id.selection_start_handle)
452 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u0623'),
454 onView(withId(R.id.textview)).check(hasSelection("\u0623"));
456 onHandleView(com.android.internal.R.id.selection_start_handle)
457 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u0621'),
459 onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
461 onHandleView(com.android.internal.R.id.selection_start_handle)
462 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
463 onView(withId(R.id.textview)).check(hasSelection("abc \u0621\u0622\u0623"));
465 onHandleView(com.android.internal.R.id.selection_end_handle)
466 .perform(dragHandle(textView, Handle.SELECTION_END, text.length()));
467 onView(withId(R.id.textview)).check(hasSelection("abc \u0621\u0622\u0623 def"));
471 public void testSelectionHandles_multiLine() {
472 final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr";
473 onView(withId(R.id.textview)).perform(replaceText(text));
474 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
476 final TextView textView = mActivity.findViewById(R.id.textview);
477 onHandleView(com.android.internal.R.id.selection_start_handle)
478 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('e')));
479 onView(withId(R.id.textview)).check(hasSelection("efg\nhijk"));
481 onHandleView(com.android.internal.R.id.selection_start_handle)
482 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
483 onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk"));
485 onHandleView(com.android.internal.R.id.selection_end_handle)
486 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('n') + 1));
487 onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn"));
489 onHandleView(com.android.internal.R.id.selection_end_handle)
490 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('r') + 1));
491 onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn\nopqr"));
495 public void testSelectionHandles_multiLine_rtl() {
497 final String text = "\u062A\u062B\u062C\n" + "\u062D\u062E\u062F\n"
498 + "\u0630\u0631\u0632\n" + "\u0633\u0634\u0635\n" + "\u0636\u0637\u0638\n"
499 + "\u0639\u063A\u063B";
500 onView(withId(R.id.textview)).perform(replaceText(text));
501 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0634')));
503 final TextView textView = mActivity.findViewById(R.id.textview);
504 onHandleView(com.android.internal.R.id.selection_start_handle)
505 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062E')));
506 onView(withId(R.id.textview)).check(hasSelection(
507 text.substring(text.indexOf('\u062D'), text.indexOf('\u0635') + 1)));
509 onHandleView(com.android.internal.R.id.selection_start_handle)
510 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062A')));
511 onView(withId(R.id.textview)).check(hasSelection(
512 text.substring(text.indexOf('\u062A'), text.indexOf('\u0635') + 1)));
514 onHandleView(com.android.internal.R.id.selection_end_handle)
515 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u0638')));
516 onView(withId(R.id.textview)).check(hasSelection(
517 text.substring(text.indexOf('\u062A'), text.indexOf('\u0638') + 1)));
519 onHandleView(com.android.internal.R.id.selection_end_handle)
520 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u063B')));
521 onView(withId(R.id.textview)).check(hasSelection(text));
525 public void testSelectionHandles_doesNotPassAnotherHandle() {
526 final String text = "abcd efg hijk lmn";
527 onView(withId(R.id.textview)).perform(replaceText(text));
528 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
530 final TextView textView = mActivity.findViewById(R.id.textview);
531 onHandleView(com.android.internal.R.id.selection_start_handle)
532 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('l')));
533 onView(withId(R.id.textview)).check(hasSelection("g"));
535 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
536 onHandleView(com.android.internal.R.id.selection_end_handle)
537 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a')));
538 onView(withId(R.id.textview)).check(hasSelection("e"));
542 public void testSelectionHandles_doesNotPassAnotherHandle_multiLine() {
543 final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr";
544 onView(withId(R.id.textview)).perform(replaceText(text));
545 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
547 final TextView textView = mActivity.findViewById(R.id.textview);
548 onHandleView(com.android.internal.R.id.selection_start_handle)
549 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('r') + 1));
550 onView(withId(R.id.textview)).check(hasSelection("k"));
552 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
553 onHandleView(com.android.internal.R.id.selection_end_handle)
554 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a')));
555 onView(withId(R.id.textview)).check(hasSelection("h"));
559 public void testSelectionHandles_snapToWordBoundary() {
560 final String text = "abcd efg hijk lmn opqr";
561 onView(withId(R.id.textview)).perform(replaceText(text));
562 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
564 final TextView textView = mActivity.findViewById(R.id.textview);
566 onHandleView(com.android.internal.R.id.selection_start_handle)
567 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('f')));
568 onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
570 onHandleView(com.android.internal.R.id.selection_start_handle)
571 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('d') + 1));
572 onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
575 onHandleView(com.android.internal.R.id.selection_start_handle)
576 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('c')));
577 onView(withId(R.id.textview)).check(hasSelection("abcd efg hijk"));
579 onHandleView(com.android.internal.R.id.selection_start_handle)
580 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('d')));
581 onView(withId(R.id.textview)).check(hasSelection("d efg hijk"));
583 onHandleView(com.android.internal.R.id.selection_start_handle)
584 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('b')));
585 onView(withId(R.id.textview)).check(hasSelection("bcd efg hijk"));
587 onView(withId(R.id.textview)).perform(click());
588 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
590 onHandleView(com.android.internal.R.id.selection_end_handle)
591 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('n')));
592 onView(withId(R.id.textview)).check(hasSelection("hijk lmn"));
594 onHandleView(com.android.internal.R.id.selection_end_handle)
595 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('o')));
596 onView(withId(R.id.textview)).check(hasSelection("hijk lmn"));
598 onHandleView(com.android.internal.R.id.selection_end_handle)
599 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('q')));
600 onView(withId(R.id.textview)).check(hasSelection("hijk lmn opqr"));
602 onHandleView(com.android.internal.R.id.selection_end_handle)
603 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('p')));
604 onView(withId(R.id.textview)).check(hasSelection("hijk lmn o"));
606 onHandleView(com.android.internal.R.id.selection_end_handle)
607 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('r')));
608 onView(withId(R.id.textview)).check(hasSelection("hijk lmn opq"));
612 public void testSelectionHandles_snapToWordBoundary_multiLine() {
613 final String text = "abcd efg\n" + "hijk lmn\n" + "opqr stu";
614 onView(withId(R.id.textview)).perform(replaceText(text));
615 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('m')));
617 final TextView textView = mActivity.findViewById(R.id.textview);
619 onHandleView(com.android.internal.R.id.selection_start_handle)
620 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('c')));
621 onView(withId(R.id.textview)).check(hasSelection("abcd efg\nhijk lmn"));
623 onHandleView(com.android.internal.R.id.selection_start_handle)
624 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('g')));
625 onView(withId(R.id.textview)).check(hasSelection("g\nhijk lmn"));
627 onHandleView(com.android.internal.R.id.selection_start_handle)
628 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('m')));
629 onView(withId(R.id.textview)).check(hasSelection("lmn"));
631 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
633 onHandleView(com.android.internal.R.id.selection_end_handle)
634 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('u')));
635 onView(withId(R.id.textview)).check(hasSelection("hijk lmn\nopqr stu"));
637 onHandleView(com.android.internal.R.id.selection_end_handle)
638 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('p')));
639 onView(withId(R.id.textview)).check(hasSelection("hijk lmn\no"));
641 onHandleView(com.android.internal.R.id.selection_end_handle)
642 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('i')));
643 onView(withId(R.id.textview)).check(hasSelection("hijk"));
647 public void testSetSelectionAndActionMode() throws Throwable {
648 final String text = "abc def";
649 onView(withId(R.id.textview)).perform(replaceText(text));
651 final TextView textView = mActivity.findViewById(R.id.textview);
652 assertFloatingToolbarIsNotDisplayed();
653 mActivityRule.runOnUiThread(
654 () -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
655 mInstrumentation.waitForIdleSync();
656 sleepForFloatingToolbarPopup();
657 // Don't automatically start action mode.
658 assertFloatingToolbarIsNotDisplayed();
659 // Make sure that "Select All" is included in the selection action mode when the entire text
661 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
662 sleepForFloatingToolbarPopup();
663 assertFloatingToolbarIsDisplayed();
664 // Changing the selection range by API should not interrupt the selection action mode.
665 mActivityRule.runOnUiThread(
666 () -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
667 mInstrumentation.waitForIdleSync();
668 sleepForFloatingToolbarPopup();
669 assertFloatingToolbarIsDisplayed();
670 assertFloatingToolbarContainsItem(
671 mActivity.getString(com.android.internal.R.string.selectAll));
672 // Make sure that "Select All" is no longer included when the entire text is selected by
674 mActivityRule.runOnUiThread(
675 () -> Selection.setSelection((Spannable) textView.getText(), 0, text.length()));
676 mInstrumentation.waitForIdleSync();
678 sleepForFloatingToolbarPopup();
679 assertFloatingToolbarIsDisplayed();
680 assertFloatingToolbarDoesNotContainItem(
681 mActivity.getString(com.android.internal.R.string.selectAll));
682 // Make sure that shrinking the selection range to cursor (an empty range) by API
683 // terminates selection action mode and does not trigger the insertion action mode.
684 mActivityRule.runOnUiThread(
685 () -> Selection.setSelection((Spannable) textView.getText(), 0));
686 mInstrumentation.waitForIdleSync();
688 sleepForFloatingToolbarPopup();
689 assertFloatingToolbarIsNotDisplayed();
690 // Make sure that user click can trigger the insertion action mode.
691 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
692 onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
693 sleepForFloatingToolbarPopup();
694 assertFloatingToolbarIsDisplayed();
695 // Make sure that an existing insertion action mode keeps alive after the insertion point is
697 mActivityRule.runOnUiThread(
698 () -> Selection.setSelection((Spannable) textView.getText(), 0));
699 mInstrumentation.waitForIdleSync();
701 sleepForFloatingToolbarPopup();
702 assertFloatingToolbarIsDisplayed();
703 assertFloatingToolbarDoesNotContainItem(
704 mActivity.getString(com.android.internal.R.string.copy));
705 // Make sure that selection action mode is started after selection is created by API when
706 // insertion action mode is active.
707 mActivityRule.runOnUiThread(
708 () -> Selection.setSelection((Spannable) textView.getText(), 1, text.length()));
709 mInstrumentation.waitForIdleSync();
711 sleepForFloatingToolbarPopup();
712 assertFloatingToolbarIsDisplayed();
713 assertFloatingToolbarContainsItem(
714 mActivity.getString(com.android.internal.R.string.copy));
718 public void testTransientState() throws Throwable {
719 final String text = "abc def";
720 onView(withId(R.id.textview)).perform(replaceText(text));
722 final TextView textView = mActivity.findViewById(R.id.textview);
723 assertFalse(textView.hasTransientState());
725 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('b')));
726 // hasTransientState should return true when user generated selection is active.
727 assertTrue(textView.hasTransientState());
728 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('d')));
729 // hasTransientState should return false as the selection has been cleared.
730 assertFalse(textView.hasTransientState());
731 mActivityRule.runOnUiThread(
732 () -> Selection.setSelection((Spannable) textView.getText(), 0, text.length()));
733 mInstrumentation.waitForIdleSync();
735 // hasTransientState should return false when selection is created by API.
736 assertFalse(textView.hasTransientState());
740 public void testResetMenuItemTitle() throws Throwable {
741 mActivity.getSystemService(TextClassificationManager.class).setTextClassifier(null);
742 final TextView textView = mActivity.findViewById(R.id.textview);
743 final int itemId = 1;
744 final String title1 = " AFIGBO";
745 final int index = title1.indexOf('I');
746 final String title2 = title1.substring(index);
747 final String[] title = new String[]{title1};
748 mActivityRule.runOnUiThread(() -> textView.setCustomSelectionActionModeCallback(
749 new ActionMode.Callback() {
751 public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
756 public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
757 menu.removeItem(itemId);
758 menu.add(Menu.NONE /* group */, itemId, 0 /* order */, title[0]);
763 public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
768 public void onDestroyActionMode(ActionMode actionMode) {
771 mInstrumentation.waitForIdleSync();
773 onView(withId(R.id.textview)).perform(replaceText(title1));
774 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(index));
775 sleepForFloatingToolbarPopup();
776 assertFloatingToolbarContainsItem(title1);
778 // Change the menu item title.
780 // Change the selection to invalidate the action mode without restarting it.
781 onHandleView(com.android.internal.R.id.selection_start_handle)
782 .perform(dragHandle(textView, Handle.SELECTION_START, index));
783 sleepForFloatingToolbarPopup();
784 assertFloatingToolbarContainsItem(title2);
788 public void testAssistItemIsAtIndexZero() throws Throwable {
789 useSystemDefaultTextClassifier();
790 final TextView textView = mActivity.findViewById(R.id.textview);
791 mActivityRule.runOnUiThread(() -> textView.setCustomSelectionActionModeCallback(
792 new ActionMode.Callback() {
794 public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
795 // Create another item at order position 0 to confirm that it will never be
796 // placed before the textAssist item.
797 menu.add(Menu.NONE, 0 /* id */, 0 /* order */, "Test");
802 public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
807 public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
812 public void onDestroyActionMode(ActionMode actionMode) {
815 mInstrumentation.waitForIdleSync();
816 final String text = "droid@android.com";
818 onView(withId(R.id.textview)).perform(replaceText(text));
819 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('@')));
820 sleepForFloatingToolbarPopup();
821 assertFloatingToolbarItemIndex(android.R.id.textAssist, 0);
825 public void testNoAssistItemForPasswordField() throws Throwable {
826 useSystemDefaultTextClassifier();
827 final TextView textView = mActivity.findViewById(R.id.textview);
828 mActivityRule.runOnUiThread(() -> {
829 textView.setInputType(
830 InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
832 mInstrumentation.waitForIdleSync();
833 final String password = "afigbo@android.com";
835 onView(withId(R.id.textview)).perform(replaceText(password));
836 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(password.indexOf('@')));
837 sleepForFloatingToolbarPopup();
838 assertFloatingToolbarDoesNotContainItem(android.R.id.textAssist);
842 public void testNoAssistItemForTextFieldWithUnsupportedCharacters() throws Throwable {
843 useSystemDefaultTextClassifier();
844 final String text = "\u202Emoc.diordna.com";
845 final TextView textView = mActivity.findViewById(R.id.textview);
846 mActivityRule.runOnUiThread(() -> textView.setText(text));
847 mInstrumentation.waitForIdleSync();
849 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('.')));
850 sleepForFloatingToolbarPopup();
851 assertFloatingToolbarDoesNotContainItem(android.R.id.textAssist);
855 public void testPastePlainText_menuAction() {
856 initializeClipboardWithText(TextStyle.STYLED);
858 onView(withId(R.id.textview)).perform(replaceText(""));
859 onView(withId(R.id.textview)).perform(longClick());
860 sleepForFloatingToolbarPopup();
861 clickFloatingToolbarItem(
862 mActivity.getString(com.android.internal.R.string.paste_as_plain_text));
863 mInstrumentation.waitForIdleSync();
865 onView(withId(R.id.textview)).check(matches(withText("styledtext")));
866 onView(withId(R.id.textview)).check(doesNotHaveStyledText());
870 public void testPastePlainText_noMenuItemForPlainText() {
871 initializeClipboardWithText(TextStyle.PLAIN);
873 onView(withId(R.id.textview)).perform(replaceText(""));
874 onView(withId(R.id.textview)).perform(longClick());
875 sleepForFloatingToolbarPopup();
877 assertFloatingToolbarDoesNotContainItem(
878 mActivity.getString(com.android.internal.R.string.paste_as_plain_text));
881 private void useSystemDefaultTextClassifier() {
882 mActivity.getSystemService(TextClassificationManager.class).setTextClassifier(null);
885 private void initializeClipboardWithText(TextStyle textStyle) {
889 clip = ClipData.newHtmlText("html", "styledtext", "<b>styledtext</b>");
892 clip = ClipData.newPlainText("plain", "plaintext");
895 throw new IllegalArgumentException("Invalid text style");
897 mActivity.getWindow().getDecorView().post(() ->
898 mActivity.getSystemService(ClipboardManager.class).setPrimaryClip(clip));
899 mInstrumentation.waitForIdleSync();
902 private enum TextStyle {