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