TextViewActivityTest.java revision 6eecdc934c6050d67a639116cde600c09e5b76bd
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package android.widget;
18
19import static android.widget.espresso.CustomViewActions.longPressAtRelativeCoordinates;
20import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
21import static android.widget.espresso.DragHandleUtils.onHandleView;
22import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
23import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText;
24import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex;
25import static android.widget.espresso.TextViewActions.dragHandle;
26import static android.widget.espresso.TextViewActions.Handle;
27import static android.widget.espresso.TextViewActions.longPressAndDragOnText;
28import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex;
29import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
30import static android.widget.espresso.TextViewAssertions.hasSelection;
31import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarItemIndex;
32import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
33import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsNotDisplayed;
34import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
35import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarDoesNotContainItem;
36import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem;
37import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
38import static android.support.test.espresso.Espresso.onView;
39import static android.support.test.espresso.action.ViewActions.click;
40import static android.support.test.espresso.action.ViewActions.longClick;
41import static android.support.test.espresso.action.ViewActions.pressKey;
42import static android.support.test.espresso.action.ViewActions.replaceText;
43import static android.support.test.espresso.assertion.ViewAssertions.matches;
44import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
45import static android.support.test.espresso.matcher.ViewMatchers.withId;
46import static android.support.test.espresso.matcher.ViewMatchers.withText;
47import static org.hamcrest.Matchers.anyOf;
48import static org.hamcrest.Matchers.is;
49
50import android.view.ActionMode;
51import android.view.Menu;
52import android.view.MenuItem;
53import android.view.textclassifier.TextClassificationManager;
54import android.view.textclassifier.TextClassifier;
55import android.widget.espresso.CustomViewActions.RelativeCoordinatesProvider;
56
57import android.support.test.espresso.action.EspressoKey;
58import android.test.ActivityInstrumentationTestCase2;
59import android.test.suitebuilder.annotation.MediumTest;
60import android.text.Selection;
61import android.text.Spannable;
62import android.text.InputType;
63import android.view.KeyEvent;
64
65import com.android.frameworks.coretests.R;
66
67/**
68 * Tests the TextView widget from an Activity
69 */
70@MediumTest
71public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextViewActivity>{
72
73    public TextViewActivityTest() {
74        super(TextViewActivity.class);
75    }
76
77    @Override
78    public void setUp() throws Exception {
79        super.setUp();
80        getActivity().getSystemService(TextClassificationManager.class)
81                .setTextClassifier(TextClassifier.NO_OP);
82    }
83
84    public void testTypedTextIsOnScreen() throws Exception {
85        final String helloWorld = "Hello world!";
86        // We use replaceText instead of typeTextIntoFocusedView to input text to avoid
87        // unintentional interactions with software keyboard.
88        onView(withId(R.id.textview)).perform(replaceText(helloWorld));
89
90        onView(withId(R.id.textview)).check(matches(withText(helloWorld)));
91    }
92
93    public void testPositionCursorAtTextAtIndex() throws Exception {
94        final String helloWorld = "Hello world!";
95        onView(withId(R.id.textview)).perform(replaceText(helloWorld));
96        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("world")));
97
98        // Delete text at specified index and see if we got the right one.
99        onView(withId(R.id.textview)).perform(pressKey(KeyEvent.KEYCODE_FORWARD_DEL));
100        onView(withId(R.id.textview)).check(matches(withText("Hello orld!")));
101    }
102
103    public void testPositionCursorAtTextAtIndex_arabic() throws Exception {
104        // Arabic text. The expected cursorable boundary is
105        // | \u0623 \u064F | \u067A | \u0633 \u0652 |
106        final String text = "\u0623\u064F\u067A\u0633\u0652";
107        onView(withId(R.id.textview)).perform(replaceText(text));
108
109        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
110        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
111        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1));
112        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2))));
113        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2));
114        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
115        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(3));
116        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(3));
117        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(4));
118        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(3), is(5))));
119        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(5));
120        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(5));
121    }
122
123    public void testPositionCursorAtTextAtIndex_devanagari() throws Exception {
124        // Devanagari text. The expected cursorable boundary is | \u0915 \u093E |
125        final String text = "\u0915\u093E";
126        onView(withId(R.id.textview)).perform(replaceText(text));
127
128        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
129        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
130        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1));
131        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2))));
132        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2));
133        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
134    }
135
136    public void testLongPressToSelect() throws Exception {
137        final String helloWorld = "Hello Kirk!";
138        onView(withId(R.id.textview)).perform(click());
139        onView(withId(R.id.textview)).perform(replaceText(helloWorld));
140        onView(withId(R.id.textview)).perform(
141                longPressOnTextAtIndex(helloWorld.indexOf("Kirk")));
142
143        onView(withId(R.id.textview)).check(hasSelection("Kirk"));
144    }
145
146    public void testLongPressEmptySpace() throws Exception {
147        final String helloWorld = "Hello big round sun!";
148        onView(withId(R.id.textview)).perform(replaceText(helloWorld));
149        // Move cursor somewhere else
150        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("big")));
151        // Long-press at end of line.
152        onView(withId(R.id.textview)).perform(longPressAtRelativeCoordinates(
153                RelativeCoordinatesProvider.HorizontalReference.RIGHT, -5,
154                RelativeCoordinatesProvider.VerticalReference.CENTER, 0));
155
156        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(helloWorld.length()));
157    }
158
159    public void testLongPressAndDragToSelect() throws Exception {
160        final String helloWorld = "Hello little handsome boy!";
161        onView(withId(R.id.textview)).perform(replaceText(helloWorld));
162        onView(withId(R.id.textview)).perform(
163                longPressAndDragOnText(helloWorld.indexOf("little"), helloWorld.indexOf(" boy!")));
164
165        onView(withId(R.id.textview)).check(hasSelection("little handsome"));
166    }
167
168    public void testLongPressAndDragToSelect_emoji() throws Exception {
169        final String text = "\uD83D\uDE00\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03";
170        onView(withId(R.id.textview)).perform(replaceText(text));
171
172        onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 6));
173        onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE02"));
174
175        onView(withId(R.id.textview)).perform(click());
176
177        onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 2));
178        onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE01"));
179    }
180
181    public void testDragAndDrop() throws Exception {
182        final String text = "abc def ghi.";
183        onView(withId(R.id.textview)).perform(replaceText(text));
184        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("e")));
185
186        onView(withId(R.id.textview)).perform(
187                longPressAndDragOnText(text.indexOf("e"), text.length()));
188
189        onView(withId(R.id.textview)).check(matches(withText("abc ghi.def")));
190        onView(withId(R.id.textview)).check(hasSelection(""));
191        assertNoSelectionHandles();
192        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length()));
193
194        // Test undo returns to the original state.
195        onView(withId(R.id.textview)).perform(pressKey(
196                (new EspressoKey.Builder()).withCtrlPressed(true).withKeyCode(KeyEvent.KEYCODE_Z)
197                        .build()));
198        onView(withId(R.id.textview)).check(matches(withText(text)));
199    }
200
201    public void testDoubleTapToSelect() throws Exception {
202        final String helloWorld = "Hello SuetYi!";
203        onView(withId(R.id.textview)).perform(replaceText(helloWorld));
204
205        onView(withId(R.id.textview)).perform(
206                doubleClickOnTextAtIndex(helloWorld.indexOf("SuetYi")));
207
208        onView(withId(R.id.textview)).check(hasSelection("SuetYi"));
209    }
210
211    public void testDoubleTapAndDragToSelect() throws Exception {
212        final String helloWorld = "Hello young beautiful girl!";
213        onView(withId(R.id.textview)).perform(replaceText(helloWorld));
214        onView(withId(R.id.textview)).perform(
215                doubleTapAndDragOnText(helloWorld.indexOf("young"), helloWorld.indexOf(" girl!")));
216
217        onView(withId(R.id.textview)).check(hasSelection("young beautiful"));
218    }
219
220    public void testDoubleTapAndDragToSelect_multiLine() throws Exception {
221        final String helloWorld = "abcd\n" + "efg\n" + "hijklm\n" + "nop";
222        onView(withId(R.id.textview)).perform(replaceText(helloWorld));
223        onView(withId(R.id.textview)).perform(
224                doubleTapAndDragOnText(helloWorld.indexOf("m"), helloWorld.indexOf("a")));
225        onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijklm"));
226    }
227
228    public void testSelectBackwordsByTouch() throws Exception {
229        final String helloWorld = "Hello king of the Jungle!";
230        onView(withId(R.id.textview)).perform(replaceText(helloWorld));
231        onView(withId(R.id.textview)).perform(
232                doubleTapAndDragOnText(helloWorld.indexOf(" Jungle!"), helloWorld.indexOf("king")));
233
234        onView(withId(R.id.textview)).check(hasSelection("king of the"));
235    }
236
237    public void testToolbarAppearsAfterSelection() throws Exception {
238        final String text = "Toolbar appears after selection.";
239        assertFloatingToolbarIsNotDisplayed();
240        onView(withId(R.id.textview)).perform(replaceText(text));
241        onView(withId(R.id.textview)).perform(
242                longPressOnTextAtIndex(text.indexOf("appears")));
243
244        sleepForFloatingToolbarPopup();
245        assertFloatingToolbarIsDisplayed();
246
247        final String text2 = "Toolbar disappears after typing text.";
248        onView(withId(R.id.textview)).perform(replaceText(text2));
249        sleepForFloatingToolbarPopup();
250        assertFloatingToolbarIsNotDisplayed();
251    }
252
253    public void testToolbarAppearsAfterSelection_withFirstStringLtrAlgorithmAndRtlHint()
254            throws Exception {
255        // after the hint layout change, the floating toolbar was not visible in the case below
256        // this test tests that the floating toolbar is displayed on the screen and is visible to
257        // user.
258        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
259        textView.post(new Runnable() {
260            @Override
261            public void run() {
262                textView.setTextDirection(TextView.TEXT_DIRECTION_FIRST_STRONG_LTR);
263                textView.setInputType(InputType.TYPE_CLASS_TEXT);
264                textView.setSingleLine(true);
265                textView.setHint("الروبوت");
266            }
267        });
268        getInstrumentation().waitForIdleSync();
269
270        onView(withId(R.id.textview)).perform(replaceText("test"));
271        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(1));
272        clickFloatingToolbarItem(
273                getActivity().getString(com.android.internal.R.string.cut));
274        onView(withId(R.id.textview)).perform(longClick());
275        sleepForFloatingToolbarPopup();
276
277        assertFloatingToolbarIsDisplayed();
278    }
279
280    public void testToolbarAndInsertionHandle() throws Exception {
281        final String text = "text";
282        onView(withId(R.id.textview)).perform(replaceText(text));
283        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
284        assertFloatingToolbarIsNotDisplayed();
285
286        onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
287        sleepForFloatingToolbarPopup();
288        assertFloatingToolbarIsDisplayed();
289
290        assertFloatingToolbarContainsItem(
291                getActivity().getString(com.android.internal.R.string.selectAll));
292        assertFloatingToolbarDoesNotContainItem(
293                getActivity().getString(com.android.internal.R.string.copy));
294        assertFloatingToolbarDoesNotContainItem(
295                getActivity().getString(com.android.internal.R.string.cut));
296    }
297
298    public void testToolbarAndSelectionHandle() throws Exception {
299        final String text = "abcd efg hijk";
300        onView(withId(R.id.textview)).perform(replaceText(text));
301
302        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("f")));
303        sleepForFloatingToolbarPopup();
304        assertFloatingToolbarIsDisplayed();
305
306        assertFloatingToolbarContainsItem(
307                getActivity().getString(com.android.internal.R.string.selectAll));
308        assertFloatingToolbarContainsItem(
309                getActivity().getString(com.android.internal.R.string.copy));
310        assertFloatingToolbarContainsItem(
311                getActivity().getString(com.android.internal.R.string.cut));
312
313        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
314        onHandleView(com.android.internal.R.id.selection_start_handle)
315                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
316        sleepForFloatingToolbarPopup();
317        assertFloatingToolbarIsDisplayed();
318
319        onHandleView(com.android.internal.R.id.selection_end_handle)
320                .perform(dragHandle(textView, Handle.SELECTION_END, text.length()));
321        sleepForFloatingToolbarPopup();
322        assertFloatingToolbarIsDisplayed();
323
324        assertFloatingToolbarDoesNotContainItem(
325                getActivity().getString(com.android.internal.R.string.selectAll));
326        assertFloatingToolbarContainsItem(
327                getActivity().getString(com.android.internal.R.string.copy));
328        assertFloatingToolbarContainsItem(
329                getActivity().getString(com.android.internal.R.string.cut));
330    }
331
332    public void testInsertionHandle() throws Exception {
333        final String text = "abcd efg hijk ";
334        onView(withId(R.id.textview)).perform(replaceText(text));
335
336        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
337        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
338
339        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
340
341        onHandleView(com.android.internal.R.id.insertion_handle)
342                .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a')));
343        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a")));
344
345        onHandleView(com.android.internal.R.id.insertion_handle)
346                .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f')));
347        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
348    }
349
350    public void testInsertionHandle_multiLine() throws Exception {
351        final String text = "abcd\n" + "efg\n" + "hijk\n";
352        onView(withId(R.id.textview)).perform(replaceText(text));
353
354        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
355        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
356
357        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
358
359        onHandleView(com.android.internal.R.id.insertion_handle)
360                .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a')));
361        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a")));
362
363        onHandleView(com.android.internal.R.id.insertion_handle)
364                .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f')));
365        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
366    }
367
368    public void testSelectionHandles() throws Exception {
369        final String text = "abcd efg hijk lmn";
370        onView(withId(R.id.textview)).perform(replaceText(text));
371
372        assertNoSelectionHandles();
373
374        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
375
376        onHandleView(com.android.internal.R.id.selection_start_handle)
377                .check(matches(isDisplayed()));
378        onHandleView(com.android.internal.R.id.selection_end_handle)
379                .check(matches(isDisplayed()));
380
381        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
382        onHandleView(com.android.internal.R.id.selection_start_handle)
383                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
384        onView(withId(R.id.textview)).check(hasSelection("abcd efg"));
385
386        onHandleView(com.android.internal.R.id.selection_end_handle)
387                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('k') + 1));
388        onView(withId(R.id.textview)).check(hasSelection("abcd efg hijk"));
389    }
390
391    public void testSelectionHandles_bidi() throws Exception {
392        final String text = "abc \u0621\u0622\u0623 def";
393        onView(withId(R.id.textview)).perform(replaceText(text));
394
395        assertNoSelectionHandles();
396
397        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0622')));
398
399        onHandleView(com.android.internal.R.id.selection_start_handle)
400                .check(matches(isDisplayed()));
401        onHandleView(com.android.internal.R.id.selection_end_handle)
402                .check(matches(isDisplayed()));
403
404        onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
405
406        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
407        onHandleView(com.android.internal.R.id.selection_start_handle)
408                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('f')));
409        onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
410
411        onHandleView(com.android.internal.R.id.selection_end_handle)
412                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a')));
413        onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
414
415        onHandleView(com.android.internal.R.id.selection_start_handle)
416                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u0623'),
417                        false));
418        onView(withId(R.id.textview)).check(hasSelection("\u0623"));
419
420        onHandleView(com.android.internal.R.id.selection_start_handle)
421                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u0621'),
422                        false));
423        onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
424
425        onHandleView(com.android.internal.R.id.selection_start_handle)
426                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
427        onView(withId(R.id.textview)).check(hasSelection("abc \u0621\u0622\u0623"));
428
429        onHandleView(com.android.internal.R.id.selection_end_handle)
430                .perform(dragHandle(textView, Handle.SELECTION_END, text.length()));
431        onView(withId(R.id.textview)).check(hasSelection("abc \u0621\u0622\u0623 def"));
432    }
433
434    public void testSelectionHandles_multiLine() throws Exception {
435        final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr";
436        onView(withId(R.id.textview)).perform(replaceText(text));
437        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
438
439        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
440        onHandleView(com.android.internal.R.id.selection_start_handle)
441                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('e')));
442        onView(withId(R.id.textview)).check(hasSelection("efg\nhijk"));
443
444        onHandleView(com.android.internal.R.id.selection_start_handle)
445                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
446        onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk"));
447
448        onHandleView(com.android.internal.R.id.selection_end_handle)
449                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('n') + 1));
450        onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn"));
451
452        onHandleView(com.android.internal.R.id.selection_end_handle)
453                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('r') + 1));
454        onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn\nopqr"));
455    }
456
457    public void testSelectionHandles_multiLine_rtl() throws Exception {
458        // Arabic text.
459        final String text = "\u062A\u062B\u062C\n" + "\u062D\u062E\u062F\n"
460                + "\u0630\u0631\u0632\n" + "\u0633\u0634\u0635\n" + "\u0636\u0637\u0638\n"
461                + "\u0639\u063A\u063B";
462        onView(withId(R.id.textview)).perform(replaceText(text));
463        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0634')));
464
465        final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
466        onHandleView(com.android.internal.R.id.selection_start_handle)
467                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062E')));
468        onView(withId(R.id.textview)).check(hasSelection(
469                text.substring(text.indexOf('\u062D'), text.indexOf('\u0635') + 1)));
470
471        onHandleView(com.android.internal.R.id.selection_start_handle)
472                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062A')));
473        onView(withId(R.id.textview)).check(hasSelection(
474                text.substring(text.indexOf('\u062A'), text.indexOf('\u0635') + 1)));
475
476        onHandleView(com.android.internal.R.id.selection_end_handle)
477                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u0638')));
478        onView(withId(R.id.textview)).check(hasSelection(
479                text.substring(text.indexOf('\u062A'), text.indexOf('\u0638') + 1)));
480
481        onHandleView(com.android.internal.R.id.selection_end_handle)
482                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u063B')));
483        onView(withId(R.id.textview)).check(hasSelection(text));
484    }
485
486
487    public void testSelectionHandles_doesNotPassAnotherHandle() throws Exception {
488        final String text = "abcd efg hijk lmn";
489        onView(withId(R.id.textview)).perform(replaceText(text));
490        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
491
492        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
493        onHandleView(com.android.internal.R.id.selection_start_handle)
494                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('l')));
495        onView(withId(R.id.textview)).check(hasSelection("g"));
496
497        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
498        onHandleView(com.android.internal.R.id.selection_end_handle)
499                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a')));
500        onView(withId(R.id.textview)).check(hasSelection("e"));
501    }
502
503    public void testSelectionHandles_doesNotPassAnotherHandle_multiLine() throws Exception {
504        final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr";
505        onView(withId(R.id.textview)).perform(replaceText(text));
506        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
507
508        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
509        onHandleView(com.android.internal.R.id.selection_start_handle)
510                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('r') + 1));
511        onView(withId(R.id.textview)).check(hasSelection("k"));
512
513        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
514        onHandleView(com.android.internal.R.id.selection_end_handle)
515                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a')));
516        onView(withId(R.id.textview)).check(hasSelection("h"));
517    }
518
519    public void testSelectionHandles_snapToWordBoundary() throws Exception {
520        final String text = "abcd efg hijk lmn opqr";
521        onView(withId(R.id.textview)).perform(replaceText(text));
522        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
523
524        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
525
526        onHandleView(com.android.internal.R.id.selection_start_handle)
527                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('f')));
528        onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
529
530        onHandleView(com.android.internal.R.id.selection_start_handle)
531                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('d') + 1));
532        onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
533
534
535        onHandleView(com.android.internal.R.id.selection_start_handle)
536                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('c')));
537        onView(withId(R.id.textview)).check(hasSelection("abcd efg hijk"));
538
539        onHandleView(com.android.internal.R.id.selection_start_handle)
540                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('d')));
541        onView(withId(R.id.textview)).check(hasSelection("d efg hijk"));
542
543        onHandleView(com.android.internal.R.id.selection_start_handle)
544                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('b')));
545        onView(withId(R.id.textview)).check(hasSelection("bcd efg hijk"));
546
547        onView(withId(R.id.textview)).perform(click());
548        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
549
550        onHandleView(com.android.internal.R.id.selection_end_handle)
551                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('n')));
552        onView(withId(R.id.textview)).check(hasSelection("hijk lmn"));
553
554        onHandleView(com.android.internal.R.id.selection_end_handle)
555                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('o')));
556        onView(withId(R.id.textview)).check(hasSelection("hijk lmn"));
557
558        onHandleView(com.android.internal.R.id.selection_end_handle)
559                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('q')));
560        onView(withId(R.id.textview)).check(hasSelection("hijk lmn opqr"));
561
562        onHandleView(com.android.internal.R.id.selection_end_handle)
563                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('p')));
564        onView(withId(R.id.textview)).check(hasSelection("hijk lmn o"));
565
566        onHandleView(com.android.internal.R.id.selection_end_handle)
567                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('r')));
568        onView(withId(R.id.textview)).check(hasSelection("hijk lmn opq"));
569    }
570
571    public void testSelectionHandles_snapToWordBoundary_multiLine() throws Exception {
572        final String text = "abcd efg\n" + "hijk lmn\n" + "opqr stu";
573        onView(withId(R.id.textview)).perform(replaceText(text));
574        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('m')));
575
576        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
577
578        onHandleView(com.android.internal.R.id.selection_start_handle)
579                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('c')));
580        onView(withId(R.id.textview)).check(hasSelection("abcd efg\nhijk lmn"));
581
582        onHandleView(com.android.internal.R.id.selection_start_handle)
583                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('g')));
584        onView(withId(R.id.textview)).check(hasSelection("g\nhijk lmn"));
585
586        onHandleView(com.android.internal.R.id.selection_start_handle)
587                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('m')));
588        onView(withId(R.id.textview)).check(hasSelection("lmn"));
589
590        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
591
592        onHandleView(com.android.internal.R.id.selection_end_handle)
593                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('u')));
594        onView(withId(R.id.textview)).check(hasSelection("hijk lmn\nopqr stu"));
595
596        onHandleView(com.android.internal.R.id.selection_end_handle)
597                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('p')));
598        onView(withId(R.id.textview)).check(hasSelection("hijk lmn\no"));
599
600        onHandleView(com.android.internal.R.id.selection_end_handle)
601                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('i')));
602        onView(withId(R.id.textview)).check(hasSelection("hijk"));
603    }
604
605    public void testSetSelectionAndActionMode() throws Exception {
606        final String text = "abc def";
607        onView(withId(R.id.textview)).perform(replaceText(text));
608
609        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
610        assertFloatingToolbarIsNotDisplayed();
611        textView.post(() -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
612        getInstrumentation().waitForIdleSync();
613        sleepForFloatingToolbarPopup();
614        // Don't automatically start action mode.
615        assertFloatingToolbarIsNotDisplayed();
616        // Make sure that "Select All" is included in the selection action mode when the entire text
617        // is not selected.
618        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
619        sleepForFloatingToolbarPopup();
620        assertFloatingToolbarIsDisplayed();
621        // Changing the selection range by API should not interrupt the selection action mode.
622        textView.post(() -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
623        getInstrumentation().waitForIdleSync();
624        sleepForFloatingToolbarPopup();
625        assertFloatingToolbarIsDisplayed();
626        assertFloatingToolbarContainsItem(
627                getActivity().getString(com.android.internal.R.string.selectAll));
628        // Make sure that "Select All" is no longer included when the entire text is selected by
629        // API.
630        textView.post(
631                () -> Selection.setSelection((Spannable) textView.getText(), 0, text.length()));
632        getInstrumentation().waitForIdleSync();
633        sleepForFloatingToolbarPopup();
634        assertFloatingToolbarIsDisplayed();
635        assertFloatingToolbarDoesNotContainItem(
636                getActivity().getString(com.android.internal.R.string.selectAll));
637        // Make sure that shrinking the selection range to cursor (an empty range) by API
638        // terminates selection action mode and does not trigger the insertion action mode.
639        textView.post(() -> Selection.setSelection((Spannable) textView.getText(), 0));
640        getInstrumentation().waitForIdleSync();
641        sleepForFloatingToolbarPopup();
642        assertFloatingToolbarIsNotDisplayed();
643        // Make sure that user click can trigger the insertion action mode.
644        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
645        onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
646        sleepForFloatingToolbarPopup();
647        assertFloatingToolbarIsDisplayed();
648        // Make sure that an existing insertion action mode keeps alive after the insertion point is
649        // moved by API.
650        textView.post(() -> Selection.setSelection((Spannable) textView.getText(), 0));
651        getInstrumentation().waitForIdleSync();
652        sleepForFloatingToolbarPopup();
653        assertFloatingToolbarIsDisplayed();
654        assertFloatingToolbarDoesNotContainItem(
655                getActivity().getString(com.android.internal.R.string.copy));
656        // Make sure that selection action mode is started after selection is created by API when
657        // insertion action mode is active.
658        textView.post(
659                () -> Selection.setSelection((Spannable) textView.getText(), 1, text.length()));
660        getInstrumentation().waitForIdleSync();
661        sleepForFloatingToolbarPopup();
662        assertFloatingToolbarIsDisplayed();
663        assertFloatingToolbarContainsItem(
664                getActivity().getString(com.android.internal.R.string.copy));
665    }
666
667    public void testTransientState() throws Exception {
668        final String text = "abc def";
669        onView(withId(R.id.textview)).perform(replaceText(text));
670
671        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
672        assertFalse(textView.hasTransientState());
673
674        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('b')));
675        // hasTransientState should return true when user generated selection is active.
676        assertTrue(textView.hasTransientState());
677        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('d')));
678        // hasTransientState should return false as the selection has been cleared.
679        assertFalse(textView.hasTransientState());
680        textView.post(
681                () -> Selection.setSelection((Spannable) textView.getText(), 0, text.length()));
682        getInstrumentation().waitForIdleSync();
683        // hasTransientState should return false when selection is created by API.
684        assertFalse(textView.hasTransientState());
685    }
686
687    public void testAssistItemIsAtIndexZero() throws Exception {
688        getActivity().getSystemService(TextClassificationManager.class).setTextClassifier(null);
689        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
690        textView.post(() -> textView.setCustomSelectionActionModeCallback(
691                new ActionMode.Callback() {
692                    @Override
693                    public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
694                        // Create another item at order position 0 to confirm that it will never be
695                        // placed before the textAssist item.
696                        menu.add(Menu.NONE, 0 /* id */, 0 /* order */, "Test");
697                        return true;
698                    }
699
700                    @Override
701                    public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
702                        return true;
703                    }
704
705                    @Override
706                    public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
707                        return false;
708                    }
709
710                    @Override
711                    public void onDestroyActionMode(ActionMode actionMode) {}
712                }));
713        final String text = "droid@android.com";
714
715        onView(withId(R.id.textview)).perform(replaceText(text));
716        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('@')));
717        sleepForFloatingToolbarPopup();
718        assertFloatingToolbarItemIndex(android.R.id.textAssist, 0);
719    }
720}
721