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