blob: bcb7cf393f33f9a2e98d5679973da05c3a768ab2 [file] [log] [blame]
Abodunrinwa Toki223c8392015-07-24 20:55:59 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package android.widget;
18
19import static android.support.test.espresso.Espresso.onView;
20import static android.support.test.espresso.action.ViewActions.click;
Abodunrinwa Tokic5b54ba2016-10-25 10:21:36 +010021import static android.support.test.espresso.action.ViewActions.longClick;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010022import static android.support.test.espresso.action.ViewActions.pressKey;
Keisuke Kuroyanagi26454142015-12-02 15:04:57 -080023import static android.support.test.espresso.action.ViewActions.replaceText;
Abodunrinwa Toki223c8392015-07-24 20:55:59 -070024import static android.support.test.espresso.assertion.ViewAssertions.matches;
Abodunrinwa Tokifc6e25e2015-11-30 19:45:32 +000025import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
Abodunrinwa Toki223c8392015-07-24 20:55:59 -070026import static android.support.test.espresso.matcher.ViewMatchers.withId;
27import static android.support.test.espresso.matcher.ViewMatchers.withText;
Siyamed Sinir21914182017-06-16 15:24:00 -070028import static android.widget.espresso.CustomViewActions.longPressAtRelativeCoordinates;
Siyamed Sinir21914182017-06-16 15:24:00 -070029import static android.widget.espresso.DragHandleUtils.onHandleView;
Richard Ledley26b87222017-11-30 10:54:08 +000030import static android.widget.espresso.FloatingToolbarEspressoUtils
31 .assertFloatingToolbarContainsItem;
32import static android.widget.espresso.FloatingToolbarEspressoUtils
33 .assertFloatingToolbarDoesNotContainItem;
Siyamed Sinir21914182017-06-16 15:24:00 -070034import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
Siyamed Sinir21914182017-06-16 15:24:00 -070035import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarItemIndex;
36import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem;
37import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
38import static android.widget.espresso.TextViewActions.Handle;
39import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
40import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex;
41import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText;
42import static android.widget.espresso.TextViewActions.dragHandle;
43import static android.widget.espresso.TextViewActions.longPressAndDragOnText;
44import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex;
45import static android.widget.espresso.TextViewAssertions.doesNotHaveStyledText;
46import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
47import static android.widget.espresso.TextViewAssertions.hasSelection;
48
49import static junit.framework.Assert.assertFalse;
50import static junit.framework.Assert.assertTrue;
51
Abodunrinwa Tokic5b54ba2016-10-25 10:21:36 +010052import static org.hamcrest.Matchers.anyOf;
53import static org.hamcrest.Matchers.is;
Abodunrinwa Toki6264cbd2017-11-06 15:12:52 +000054import static org.mockito.Matchers.any;
55import static org.mockito.Mockito.mock;
56import static org.mockito.Mockito.never;
57import static org.mockito.Mockito.verify;
58import static org.mockito.Mockito.when;
Abodunrinwa Toki223c8392015-07-24 20:55:59 -070059
Siyamed Sinir21914182017-06-16 15:24:00 -070060import android.app.Activity;
61import android.app.Instrumentation;
Abodunrinwa Tokiea6cb122017-04-28 22:14:13 +010062import android.content.ClipData;
63import android.content.ClipboardManager;
Siyamed Sinir21914182017-06-16 15:24:00 -070064import android.support.test.InstrumentationRegistry;
65import android.support.test.espresso.action.EspressoKey;
66import android.support.test.filters.MediumTest;
67import android.support.test.rule.ActivityTestRule;
68import android.support.test.runner.AndroidJUnit4;
Richard Ledley5f2f8202018-02-05 14:55:47 +000069import android.support.test.uiautomator.UiDevice;
Abodunrinwa Toki89550e82017-10-31 03:46:46 +000070import android.test.suitebuilder.annotation.Suppress;
Siyamed Sinir21914182017-06-16 15:24:00 -070071import android.text.InputType;
72import android.text.Selection;
73import android.text.Spannable;
Richard Ledley26b87222017-11-30 10:54:08 +000074import android.text.SpannableString;
75import android.text.method.LinkMovementMethod;
Abodunrinwa Toki6eecdc92017-04-11 05:15:25 +010076import android.view.ActionMode;
Siyamed Sinir21914182017-06-16 15:24:00 -070077import android.view.KeyEvent;
Abodunrinwa Toki6eecdc92017-04-11 05:15:25 +010078import android.view.Menu;
79import android.view.MenuItem;
80import android.view.textclassifier.TextClassificationManager;
81import android.view.textclassifier.TextClassifier;
Richard Ledley26b87222017-11-30 10:54:08 +000082import android.view.textclassifier.TextLinks;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010083import android.view.textclassifier.TextLinksParams;
Abodunrinwa Toki9479d3e2016-03-09 19:41:41 +000084import android.widget.espresso.CustomViewActions.RelativeCoordinatesProvider;
Abodunrinwa Toki223c8392015-07-24 20:55:59 -070085
Abodunrinwa Tokic5b54ba2016-10-25 10:21:36 +010086import com.android.frameworks.coretests.R;
Keisuke Kuroyanagie27e0852016-01-08 19:05:27 +090087
Siyamed Sinir21914182017-06-16 15:24:00 -070088import org.junit.Before;
89import org.junit.Rule;
90import org.junit.Test;
91import org.junit.runner.RunWith;
92
Abodunrinwa Toki223c8392015-07-24 20:55:59 -070093/**
94 * Tests the TextView widget from an Activity
95 */
Siyamed Sinir21914182017-06-16 15:24:00 -070096@RunWith(AndroidJUnit4.class)
Abodunrinwa Tokic5b54ba2016-10-25 10:21:36 +010097@MediumTest
Siyamed Sinir21914182017-06-16 15:24:00 -070098public class TextViewActivityTest {
Abodunrinwa Toki223c8392015-07-24 20:55:59 -070099
Siyamed Sinir21914182017-06-16 15:24:00 -0700100 @Rule
101 public ActivityTestRule<TextViewActivity> mActivityRule = new ActivityTestRule<>(
102 TextViewActivity.class);
Abodunrinwa Toki223c8392015-07-24 20:55:59 -0700103
Siyamed Sinir21914182017-06-16 15:24:00 -0700104 private Activity mActivity;
105 private Instrumentation mInstrumentation;
106
107 @Before
108 public void setUp() {
109 mActivity = mActivityRule.getActivity();
110 mInstrumentation = InstrumentationRegistry.getInstrumentation();
111 mActivity.getSystemService(TextClassificationManager.class)
Abodunrinwa Toki6eecdc92017-04-11 05:15:25 +0100112 .setTextClassifier(TextClassifier.NO_OP);
Abodunrinwa Tokic9e924e2015-10-08 12:01:35 +0100113 }
114
Siyamed Sinir21914182017-06-16 15:24:00 -0700115 @Test
116 public void testTypedTextIsOnScreen() {
Abodunrinwa Toki223c8392015-07-24 20:55:59 -0700117 final String helloWorld = "Hello world!";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900118 // We use replaceText instead of typeTextIntoFocusedView to input text to avoid
119 // unintentional interactions with software keyboard.
120 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
Abodunrinwa Toki223c8392015-07-24 20:55:59 -0700121
122 onView(withId(R.id.textview)).check(matches(withText(helloWorld)));
123 }
Siyamed Sinir21914182017-06-16 15:24:00 -0700124 @Test
125 public void testPositionCursorAtTextAtIndex() {
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100126 final String helloWorld = "Hello world!";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900127 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100128 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("world")));
129
130 // Delete text at specified index and see if we got the right one.
131 onView(withId(R.id.textview)).perform(pressKey(KeyEvent.KEYCODE_FORWARD_DEL));
132 onView(withId(R.id.textview)).check(matches(withText("Hello orld!")));
133 }
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100134
Siyamed Sinir21914182017-06-16 15:24:00 -0700135 @Test
136 public void testPositionCursorAtTextAtIndex_arabic() {
Keisuke Kuroyanagie27e0852016-01-08 19:05:27 +0900137 // Arabic text. The expected cursorable boundary is
138 // | \u0623 \u064F | \u067A | \u0633 \u0652 |
139 final String text = "\u0623\u064F\u067A\u0633\u0652";
Keisuke Kuroyanagie27e0852016-01-08 19:05:27 +0900140 onView(withId(R.id.textview)).perform(replaceText(text));
141
142 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
143 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
144 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1));
145 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2))));
146 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2));
147 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
148 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(3));
149 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(3));
150 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(4));
151 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(3), is(5))));
152 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(5));
153 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(5));
154 }
155
Siyamed Sinir21914182017-06-16 15:24:00 -0700156 @Test
157 public void testPositionCursorAtTextAtIndex_devanagari() {
Keisuke Kuroyanagie27e0852016-01-08 19:05:27 +0900158 // Devanagari text. The expected cursorable boundary is | \u0915 \u093E |
159 final String text = "\u0915\u093E";
Keisuke Kuroyanagie27e0852016-01-08 19:05:27 +0900160 onView(withId(R.id.textview)).perform(replaceText(text));
161
162 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
163 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
164 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1));
165 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2))));
166 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2));
167 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
168 }
169
Siyamed Sinir21914182017-06-16 15:24:00 -0700170 @Test
171 public void testLongPressToSelect() {
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100172 final String helloWorld = "Hello Kirk!";
173 onView(withId(R.id.textview)).perform(click());
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900174 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100175 onView(withId(R.id.textview)).perform(
176 longPressOnTextAtIndex(helloWorld.indexOf("Kirk")));
177
178 onView(withId(R.id.textview)).check(hasSelection("Kirk"));
179 }
180
Siyamed Sinir21914182017-06-16 15:24:00 -0700181 @Test
182 public void testLongPressEmptySpace() {
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100183 final String helloWorld = "Hello big round sun!";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900184 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100185 // Move cursor somewhere else
186 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("big")));
187 // Long-press at end of line.
Abodunrinwa Toki9479d3e2016-03-09 19:41:41 +0000188 onView(withId(R.id.textview)).perform(longPressAtRelativeCoordinates(
189 RelativeCoordinatesProvider.HorizontalReference.RIGHT, -5,
190 RelativeCoordinatesProvider.VerticalReference.CENTER, 0));
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100191
192 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(helloWorld.length()));
193 }
194
Siyamed Sinir21914182017-06-16 15:24:00 -0700195 @Test
196 public void testLongPressAndDragToSelect() {
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100197 final String helloWorld = "Hello little handsome boy!";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900198 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100199 onView(withId(R.id.textview)).perform(
200 longPressAndDragOnText(helloWorld.indexOf("little"), helloWorld.indexOf(" boy!")));
201
202 onView(withId(R.id.textview)).check(hasSelection("little handsome"));
203 }
204
Siyamed Sinir21914182017-06-16 15:24:00 -0700205 @Test
206 public void testLongPressAndDragToSelect_emoji() {
Keisuke Kuroyanagi133dfc02016-07-21 18:07:23 +0900207 final String text = "\uD83D\uDE00\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03";
Keisuke Kuroyanagi133dfc02016-07-21 18:07:23 +0900208 onView(withId(R.id.textview)).perform(replaceText(text));
209
210 onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 6));
211 onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE02"));
212
213 onView(withId(R.id.textview)).perform(click());
214
215 onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 2));
216 onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE01"));
217 }
218
Siyamed Sinir21914182017-06-16 15:24:00 -0700219 @Test
220 public void testDragAndDrop() {
Keisuke Kuroyanagi4307aeb2015-12-07 11:16:39 -0800221 final String text = "abc def ghi.";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900222 onView(withId(R.id.textview)).perform(replaceText(text));
Keisuke Kuroyanagi4307aeb2015-12-07 11:16:39 -0800223 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("e")));
224
225 onView(withId(R.id.textview)).perform(
226 longPressAndDragOnText(text.indexOf("e"), text.length()));
227
228 onView(withId(R.id.textview)).check(matches(withText("abc ghi.def")));
229 onView(withId(R.id.textview)).check(hasSelection(""));
Keisuke Kuroyanagi4307aeb2015-12-07 11:16:39 -0800230 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length()));
Keisuke Kuroyanagifae45782016-02-24 18:53:00 -0800231
232 // Test undo returns to the original state.
233 onView(withId(R.id.textview)).perform(pressKey(
234 (new EspressoKey.Builder()).withCtrlPressed(true).withKeyCode(KeyEvent.KEYCODE_Z)
235 .build()));
236 onView(withId(R.id.textview)).check(matches(withText(text)));
Keisuke Kuroyanagi4307aeb2015-12-07 11:16:39 -0800237 }
238
Siyamed Sinir21914182017-06-16 15:24:00 -0700239 @Test
240 public void testDoubleTapToSelect() {
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100241 final String helloWorld = "Hello SuetYi!";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900242 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
243
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100244 onView(withId(R.id.textview)).perform(
245 doubleClickOnTextAtIndex(helloWorld.indexOf("SuetYi")));
246
247 onView(withId(R.id.textview)).check(hasSelection("SuetYi"));
248 }
249
Siyamed Sinir21914182017-06-16 15:24:00 -0700250 @Test
251 public void testDoubleTapAndDragToSelect() {
252 final String helloWorld = "Hello young beautiful person!";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900253 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
Siyamed Sinir21914182017-06-16 15:24:00 -0700254 onView(withId(R.id.textview)).perform(doubleTapAndDragOnText(helloWorld.indexOf("young"),
255 helloWorld.indexOf(" person!")));
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100256
257 onView(withId(R.id.textview)).check(hasSelection("young beautiful"));
258 }
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100259
Siyamed Sinir21914182017-06-16 15:24:00 -0700260 @Test
261 public void testDoubleTapAndDragToSelect_multiLine() {
Sara Kato68c433d2016-08-09 14:40:45 +0900262 final String helloWorld = "abcd\n" + "efg\n" + "hijklm\n" + "nop";
Sara Kato68c433d2016-08-09 14:40:45 +0900263 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
264 onView(withId(R.id.textview)).perform(
265 doubleTapAndDragOnText(helloWorld.indexOf("m"), helloWorld.indexOf("a")));
266 onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijklm"));
267 }
268
Siyamed Sinir21914182017-06-16 15:24:00 -0700269 @Test
270 public void testSelectBackwordsByTouch() {
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100271 final String helloWorld = "Hello king of the Jungle!";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900272 onView(withId(R.id.textview)).perform(replaceText(helloWorld));
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100273 onView(withId(R.id.textview)).perform(
274 doubleTapAndDragOnText(helloWorld.indexOf(" Jungle!"), helloWorld.indexOf("king")));
275
276 onView(withId(R.id.textview)).check(hasSelection("king of the"));
277 }
278
Siyamed Sinir21914182017-06-16 15:24:00 -0700279 @Test
280 public void testToolbarAppearsAfterSelection() {
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100281 final String text = "Toolbar appears after selection.";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900282 onView(withId(R.id.textview)).perform(replaceText(text));
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100283 onView(withId(R.id.textview)).perform(
284 longPressOnTextAtIndex(text.indexOf("appears")));
285
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800286 sleepForFloatingToolbarPopup();
287 assertFloatingToolbarIsDisplayed();
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800288 }
289
Siyamed Sinir21914182017-06-16 15:24:00 -0700290 @Test
Siyamed Sinir987ec652016-02-17 19:44:41 -0800291 public void testToolbarAppearsAfterSelection_withFirstStringLtrAlgorithmAndRtlHint()
Siyamed Sinir21914182017-06-16 15:24:00 -0700292 throws Throwable {
Siyamed Sinir987ec652016-02-17 19:44:41 -0800293 // after the hint layout change, the floating toolbar was not visible in the case below
294 // this test tests that the floating toolbar is displayed on the screen and is visible to
295 // user.
Siyamed Sinir21914182017-06-16 15:24:00 -0700296 mActivityRule.runOnUiThread(() -> {
297 final TextView textView = mActivity.findViewById(R.id.textview);
298 textView.setTextDirection(TextView.TEXT_DIRECTION_FIRST_STRONG_LTR);
299 textView.setInputType(InputType.TYPE_CLASS_TEXT);
300 textView.setSingleLine(true);
301 textView.setHint("الروبوت");
Siyamed Sinir987ec652016-02-17 19:44:41 -0800302 });
Siyamed Sinir21914182017-06-16 15:24:00 -0700303 mInstrumentation.waitForIdleSync();
Siyamed Sinir987ec652016-02-17 19:44:41 -0800304
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900305 onView(withId(R.id.textview)).perform(replaceText("test"));
Siyamed Sinir987ec652016-02-17 19:44:41 -0800306 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(1));
Siyamed Sinir21914182017-06-16 15:24:00 -0700307 clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.cut));
Siyamed Sinir987ec652016-02-17 19:44:41 -0800308 onView(withId(R.id.textview)).perform(longClick());
309 sleepForFloatingToolbarPopup();
310
311 assertFloatingToolbarIsDisplayed();
312 }
313
Siyamed Sinir21914182017-06-16 15:24:00 -0700314 @Test
Richard Ledley26b87222017-11-30 10:54:08 +0000315 public void testToolbarAppearsAfterLinkClicked() throws Throwable {
Richard Ledley5f2f8202018-02-05 14:55:47 +0000316 TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview);
317 int position = (textLink.getStart() + textLink.getEnd()) / 2;
318 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position));
319 sleepForFloatingToolbarPopup();
320 assertFloatingToolbarIsDisplayed();
Richard Ledley724eff92017-12-21 10:11:34 +0000321 }
322
323 @Test
324 public void testToolbarAppearsAfterLinkClickedNonselectable() throws Throwable {
Abodunrinwa Toki29cb7682018-04-11 21:24:20 +0100325 final TextView textView = mActivity.findViewById(R.id.nonselectable_textview);
326 final TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
327 final int position = (textLink.getStart() + textLink.getEnd()) / 2;
328
Richard Ledley5f2f8202018-02-05 14:55:47 +0000329 onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
330 sleepForFloatingToolbarPopup();
331 assertFloatingToolbarIsDisplayed();
Abodunrinwa Toki29cb7682018-04-11 21:24:20 +0100332 assertTrue(textView.hasSelection());
333
334 // toggle
335 onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
336 sleepForFloatingToolbarPopup();
337 assertFalse(textView.hasSelection());
338
339 onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
340 sleepForFloatingToolbarPopup();
341 assertFloatingToolbarIsDisplayed();
342 assertTrue(textView.hasSelection());
343
344 // click outside
345 onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(0));
346 sleepForFloatingToolbarPopup();
347 assertFalse(textView.hasSelection());
Richard Ledley724eff92017-12-21 10:11:34 +0000348 }
349
Richard Ledley5f2f8202018-02-05 14:55:47 +0000350 @Test
351 public void testSelectionRemovedWhenNonselectableTextLosesFocus() throws Throwable {
Abodunrinwa Toki29cb7682018-04-11 21:24:20 +0100352 final TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
353 final int position = (textLink.getStart() + textLink.getEnd()) / 2;
354 final TextView textView = mActivity.findViewById(R.id.nonselectable_textview);
355 mActivityRule.runOnUiThread(() -> textView.setFocusableInTouchMode(true));
Richard Ledley5f2f8202018-02-05 14:55:47 +0000356
Abodunrinwa Toki29cb7682018-04-11 21:24:20 +0100357 onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
Richard Ledley5f2f8202018-02-05 14:55:47 +0000358 sleepForFloatingToolbarPopup();
359 assertFloatingToolbarIsDisplayed();
Abodunrinwa Toki29cb7682018-04-11 21:24:20 +0100360 assertTrue(textView.hasSelection());
Richard Ledley5f2f8202018-02-05 14:55:47 +0000361
Abodunrinwa Toki29cb7682018-04-11 21:24:20 +0100362 mActivityRule.runOnUiThread(() -> textView.clearFocus());
363 mInstrumentation.waitForIdleSync();
Richard Ledley5f2f8202018-02-05 14:55:47 +0000364 sleepForFloatingToolbarPopup();
Richard Ledley5f2f8202018-02-05 14:55:47 +0000365
Abodunrinwa Toki29cb7682018-04-11 21:24:20 +0100366 assertFalse(textView.hasSelection());
Richard Ledley5f2f8202018-02-05 14:55:47 +0000367 }
368
369 @Test
370 public void testSelectionRemovedFromNonselectableTextWhenWindowLosesFocus() throws Throwable {
371 TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
372 int nonselectablePosition = (textLink.getStart() + textLink.getEnd()) / 2;
373 TextView nonselectableTextView = mActivity.findViewById(R.id.nonselectable_textview);
374
375 onView(withId(R.id.nonselectable_textview))
376 .perform(clickOnTextAtIndex(nonselectablePosition));
377 sleepForFloatingToolbarPopup();
378 assertFloatingToolbarIsDisplayed();
379 assertTrue(nonselectableTextView.hasSelection());
380
381 UiDevice device = UiDevice.getInstance(mInstrumentation);
382 device.openNotification();
383 Thread.sleep(2000);
384 device.pressBack();
385 Thread.sleep(2000);
386
387 assertFalse(nonselectableTextView.hasSelection());
388 }
389
390 private TextLinks.TextLink addLinkifiedTextToTextView(int id) throws Throwable {
Richard Ledley724eff92017-12-21 10:11:34 +0000391 TextView textView = mActivity.findViewById(id);
Richard Ledley26b87222017-11-30 10:54:08 +0000392 useSystemDefaultTextClassifier();
393 TextClassificationManager textClassificationManager =
394 mActivity.getSystemService(TextClassificationManager.class);
395 TextClassifier textClassifier = textClassificationManager.getTextClassifier();
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000396 Spannable content = new SpannableString("Call me at +19148277737");
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100397 TextLinks.Request request = new TextLinks.Request.Builder(content).build();
398 TextLinks links = textClassifier.generateLinks(request);
399 TextLinksParams applyParams = new TextLinksParams.Builder()
400 .setApplyStrategy(TextLinks.APPLY_STRATEGY_REPLACE)
401 .build();
402 applyParams.apply(content, links);
Richard Ledley26b87222017-11-30 10:54:08 +0000403
404 mActivityRule.runOnUiThread(() -> {
405 textView.setText(content);
406 textView.setMovementMethod(LinkMovementMethod.getInstance());
407 });
408 mInstrumentation.waitForIdleSync();
409
410 // Wait for the UI thread to refresh
411 Thread.sleep(1000);
412
Richard Ledley5f2f8202018-02-05 14:55:47 +0000413 return links.getLinks().iterator().next();
Richard Ledley26b87222017-11-30 10:54:08 +0000414 }
415
416 @Test
Siyamed Sinir21914182017-06-16 15:24:00 -0700417 public void testToolbarAndInsertionHandle() {
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800418 final String text = "text";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900419 onView(withId(R.id.textview)).perform(replaceText(text));
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800420 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800421
422 onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
423 sleepForFloatingToolbarPopup();
424 assertFloatingToolbarIsDisplayed();
425
426 assertFloatingToolbarContainsItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700427 mActivity.getString(com.android.internal.R.string.selectAll));
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800428 assertFloatingToolbarDoesNotContainItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700429 mActivity.getString(com.android.internal.R.string.copy));
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800430 assertFloatingToolbarDoesNotContainItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700431 mActivity.getString(com.android.internal.R.string.cut));
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800432 }
433
Siyamed Sinir21914182017-06-16 15:24:00 -0700434 @Test
435 public void testToolbarAndSelectionHandle() {
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800436 final String text = "abcd efg hijk";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900437 onView(withId(R.id.textview)).perform(replaceText(text));
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800438
439 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("f")));
440 sleepForFloatingToolbarPopup();
441 assertFloatingToolbarIsDisplayed();
442
443 assertFloatingToolbarContainsItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700444 mActivity.getString(com.android.internal.R.string.selectAll));
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800445 assertFloatingToolbarContainsItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700446 mActivity.getString(com.android.internal.R.string.copy));
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800447 assertFloatingToolbarContainsItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700448 mActivity.getString(com.android.internal.R.string.cut));
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800449
Siyamed Sinir21914182017-06-16 15:24:00 -0700450 final TextView textView = mActivity.findViewById(R.id.textview);
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800451 onHandleView(com.android.internal.R.id.selection_start_handle)
452 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
453 sleepForFloatingToolbarPopup();
454 assertFloatingToolbarIsDisplayed();
455
456 onHandleView(com.android.internal.R.id.selection_end_handle)
457 .perform(dragHandle(textView, Handle.SELECTION_END, text.length()));
458 sleepForFloatingToolbarPopup();
459 assertFloatingToolbarIsDisplayed();
460
461 assertFloatingToolbarDoesNotContainItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700462 mActivity.getString(com.android.internal.R.string.selectAll));
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800463 assertFloatingToolbarContainsItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700464 mActivity.getString(com.android.internal.R.string.copy));
Keisuke Kuroyanagib0dd77c2015-12-02 13:27:01 -0800465 assertFloatingToolbarContainsItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700466 mActivity.getString(com.android.internal.R.string.cut));
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100467 }
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900468
Siyamed Sinir21914182017-06-16 15:24:00 -0700469 @Test
470 public void testInsertionHandle() {
Keisuke Kuroyanagie8b3dd42015-11-26 14:19:27 +0900471 final String text = "abcd efg hijk ";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900472 onView(withId(R.id.textview)).perform(replaceText(text));
Keisuke Kuroyanagie8b3dd42015-11-26 14:19:27 +0900473
474 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
475 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
476
Siyamed Sinir21914182017-06-16 15:24:00 -0700477 final TextView textView = mActivity.findViewById(R.id.textview);
Keisuke Kuroyanagie8b3dd42015-11-26 14:19:27 +0900478
479 onHandleView(com.android.internal.R.id.insertion_handle)
480 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a')));
481 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a")));
482
483 onHandleView(com.android.internal.R.id.insertion_handle)
484 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f')));
485 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
486 }
487
Siyamed Sinir21914182017-06-16 15:24:00 -0700488 @Test
489 public void testInsertionHandle_multiLine() {
Keisuke Kuroyanagie8b3dd42015-11-26 14:19:27 +0900490 final String text = "abcd\n" + "efg\n" + "hijk\n";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900491 onView(withId(R.id.textview)).perform(replaceText(text));
Keisuke Kuroyanagie8b3dd42015-11-26 14:19:27 +0900492
493 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
494 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
495
Siyamed Sinir21914182017-06-16 15:24:00 -0700496 final TextView textView = mActivity.findViewById(R.id.textview);
Keisuke Kuroyanagie8b3dd42015-11-26 14:19:27 +0900497
498 onHandleView(com.android.internal.R.id.insertion_handle)
499 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a')));
500 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a")));
501
502 onHandleView(com.android.internal.R.id.insertion_handle)
503 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f')));
504 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
505 }
506
Siyamed Sinir21914182017-06-16 15:24:00 -0700507 @Test
508 public void testSelectionHandles() {
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900509 final String text = "abcd efg hijk lmn";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900510 onView(withId(R.id.textview)).perform(replaceText(text));
Abodunrinwa Tokifc6e25e2015-11-30 19:45:32 +0000511
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900512 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900513
Abodunrinwa Tokifc6e25e2015-11-30 19:45:32 +0000514 onHandleView(com.android.internal.R.id.selection_start_handle)
515 .check(matches(isDisplayed()));
516 onHandleView(com.android.internal.R.id.selection_end_handle)
517 .check(matches(isDisplayed()));
518
Siyamed Sinir21914182017-06-16 15:24:00 -0700519 final TextView textView = mActivity.findViewById(R.id.textview);
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900520 onHandleView(com.android.internal.R.id.selection_start_handle)
521 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
522 onView(withId(R.id.textview)).check(hasSelection("abcd efg"));
523
524 onHandleView(com.android.internal.R.id.selection_end_handle)
525 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('k') + 1));
526 onView(withId(R.id.textview)).check(hasSelection("abcd efg hijk"));
527 }
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900528
Siyamed Sinir21914182017-06-16 15:24:00 -0700529 @Test
530 public void testSelectionHandles_bidi() {
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900531 final String text = "abc \u0621\u0622\u0623 def";
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900532 onView(withId(R.id.textview)).perform(replaceText(text));
533
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900534 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0622')));
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900535
536 onHandleView(com.android.internal.R.id.selection_start_handle)
537 .check(matches(isDisplayed()));
538 onHandleView(com.android.internal.R.id.selection_end_handle)
539 .check(matches(isDisplayed()));
540
541 onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
542
Siyamed Sinir21914182017-06-16 15:24:00 -0700543 final TextView textView = mActivity.findViewById(R.id.textview);
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900544 onHandleView(com.android.internal.R.id.selection_start_handle)
545 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('f')));
546 onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
547
548 onHandleView(com.android.internal.R.id.selection_end_handle)
549 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a')));
550 onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
551
552 onHandleView(com.android.internal.R.id.selection_start_handle)
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900553 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u0623'),
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900554 false));
555 onView(withId(R.id.textview)).check(hasSelection("\u0623"));
556
557 onHandleView(com.android.internal.R.id.selection_start_handle)
558 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u0621'),
559 false));
560 onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
561
562 onHandleView(com.android.internal.R.id.selection_start_handle)
563 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
564 onView(withId(R.id.textview)).check(hasSelection("abc \u0621\u0622\u0623"));
565
566 onHandleView(com.android.internal.R.id.selection_end_handle)
567 .perform(dragHandle(textView, Handle.SELECTION_END, text.length()));
568 onView(withId(R.id.textview)).check(hasSelection("abc \u0621\u0622\u0623 def"));
569 }
570
Siyamed Sinir21914182017-06-16 15:24:00 -0700571 @Test
572 public void testSelectionHandles_multiLine() {
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900573 final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900574 onView(withId(R.id.textview)).perform(replaceText(text));
575 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900576
Siyamed Sinir21914182017-06-16 15:24:00 -0700577 final TextView textView = mActivity.findViewById(R.id.textview);
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900578 onHandleView(com.android.internal.R.id.selection_start_handle)
579 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('e')));
580 onView(withId(R.id.textview)).check(hasSelection("efg\nhijk"));
581
582 onHandleView(com.android.internal.R.id.selection_start_handle)
583 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
584 onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk"));
585
586 onHandleView(com.android.internal.R.id.selection_end_handle)
587 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('n') + 1));
588 onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn"));
589
590 onHandleView(com.android.internal.R.id.selection_end_handle)
591 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('r') + 1));
592 onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn\nopqr"));
593 }
Keisuke Kuroyanagicb6e8422015-11-24 18:51:48 +0900594
Abodunrinwa Toki89550e82017-10-31 03:46:46 +0000595 @Suppress // Consistently failing.
Siyamed Sinir21914182017-06-16 15:24:00 -0700596 @Test
597 public void testSelectionHandles_multiLine_rtl() {
Keisuke Kuroyanagi26454142015-12-02 15:04:57 -0800598 // Arabic text.
599 final String text = "\u062A\u062B\u062C\n" + "\u062D\u062E\u062F\n"
600 + "\u0630\u0631\u0632\n" + "\u0633\u0634\u0635\n" + "\u0636\u0637\u0638\n"
601 + "\u0639\u063A\u063B";
Keisuke Kuroyanagi26454142015-12-02 15:04:57 -0800602 onView(withId(R.id.textview)).perform(replaceText(text));
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900603 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0634')));
Keisuke Kuroyanagi26454142015-12-02 15:04:57 -0800604
Siyamed Sinir21914182017-06-16 15:24:00 -0700605 final TextView textView = mActivity.findViewById(R.id.textview);
Keisuke Kuroyanagi26454142015-12-02 15:04:57 -0800606 onHandleView(com.android.internal.R.id.selection_start_handle)
607 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062E')));
608 onView(withId(R.id.textview)).check(hasSelection(
609 text.substring(text.indexOf('\u062D'), text.indexOf('\u0635') + 1)));
610
611 onHandleView(com.android.internal.R.id.selection_start_handle)
612 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062A')));
613 onView(withId(R.id.textview)).check(hasSelection(
614 text.substring(text.indexOf('\u062A'), text.indexOf('\u0635') + 1)));
615
616 onHandleView(com.android.internal.R.id.selection_end_handle)
617 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u0638')));
618 onView(withId(R.id.textview)).check(hasSelection(
619 text.substring(text.indexOf('\u062A'), text.indexOf('\u0638') + 1)));
620
621 onHandleView(com.android.internal.R.id.selection_end_handle)
622 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u063B')));
623 onView(withId(R.id.textview)).check(hasSelection(text));
624 }
625
Siyamed Sinir21914182017-06-16 15:24:00 -0700626 @Test
627 public void testSelectionHandles_doesNotPassAnotherHandle() {
Keisuke Kuroyanagicb6e8422015-11-24 18:51:48 +0900628 final String text = "abcd efg hijk lmn";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900629 onView(withId(R.id.textview)).perform(replaceText(text));
630 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
Keisuke Kuroyanagicb6e8422015-11-24 18:51:48 +0900631
Siyamed Sinir21914182017-06-16 15:24:00 -0700632 final TextView textView = mActivity.findViewById(R.id.textview);
Keisuke Kuroyanagicb6e8422015-11-24 18:51:48 +0900633 onHandleView(com.android.internal.R.id.selection_start_handle)
634 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('l')));
635 onView(withId(R.id.textview)).check(hasSelection("g"));
636
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900637 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
Keisuke Kuroyanagicb6e8422015-11-24 18:51:48 +0900638 onHandleView(com.android.internal.R.id.selection_end_handle)
639 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a')));
640 onView(withId(R.id.textview)).check(hasSelection("e"));
641 }
642
Siyamed Sinir21914182017-06-16 15:24:00 -0700643 @Test
644 public void testSelectionHandles_doesNotPassAnotherHandle_multiLine() {
Keisuke Kuroyanagicb6e8422015-11-24 18:51:48 +0900645 final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900646 onView(withId(R.id.textview)).perform(replaceText(text));
647 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
Keisuke Kuroyanagicb6e8422015-11-24 18:51:48 +0900648
Siyamed Sinir21914182017-06-16 15:24:00 -0700649 final TextView textView = mActivity.findViewById(R.id.textview);
Keisuke Kuroyanagicb6e8422015-11-24 18:51:48 +0900650 onHandleView(com.android.internal.R.id.selection_start_handle)
651 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('r') + 1));
652 onView(withId(R.id.textview)).check(hasSelection("k"));
653
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900654 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
Keisuke Kuroyanagicb6e8422015-11-24 18:51:48 +0900655 onHandleView(com.android.internal.R.id.selection_end_handle)
656 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a')));
657 onView(withId(R.id.textview)).check(hasSelection("h"));
658 }
Keisuke Kuroyanagiec913c02015-11-24 18:56:20 +0900659
Siyamed Sinir21914182017-06-16 15:24:00 -0700660 @Test
661 public void testSelectionHandles_snapToWordBoundary() {
Keisuke Kuroyanagiec913c02015-11-24 18:56:20 +0900662 final String text = "abcd efg hijk lmn opqr";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900663 onView(withId(R.id.textview)).perform(replaceText(text));
664 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
Keisuke Kuroyanagiec913c02015-11-24 18:56:20 +0900665
Siyamed Sinir21914182017-06-16 15:24:00 -0700666 final TextView textView = mActivity.findViewById(R.id.textview);
Keisuke Kuroyanagiec913c02015-11-24 18:56:20 +0900667
668 onHandleView(com.android.internal.R.id.selection_start_handle)
669 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('f')));
670 onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
671
672 onHandleView(com.android.internal.R.id.selection_start_handle)
673 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('d') + 1));
674 onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
675
676
677 onHandleView(com.android.internal.R.id.selection_start_handle)
678 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('c')));
679 onView(withId(R.id.textview)).check(hasSelection("abcd efg hijk"));
680
681 onHandleView(com.android.internal.R.id.selection_start_handle)
682 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('d')));
683 onView(withId(R.id.textview)).check(hasSelection("d efg hijk"));
684
685 onHandleView(com.android.internal.R.id.selection_start_handle)
686 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('b')));
687 onView(withId(R.id.textview)).check(hasSelection("bcd efg hijk"));
688
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900689 onView(withId(R.id.textview)).perform(click());
690 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
Keisuke Kuroyanagiec913c02015-11-24 18:56:20 +0900691
692 onHandleView(com.android.internal.R.id.selection_end_handle)
693 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('n')));
694 onView(withId(R.id.textview)).check(hasSelection("hijk lmn"));
695
696 onHandleView(com.android.internal.R.id.selection_end_handle)
697 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('o')));
698 onView(withId(R.id.textview)).check(hasSelection("hijk lmn"));
699
700 onHandleView(com.android.internal.R.id.selection_end_handle)
701 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('q')));
702 onView(withId(R.id.textview)).check(hasSelection("hijk lmn opqr"));
703
704 onHandleView(com.android.internal.R.id.selection_end_handle)
705 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('p')));
706 onView(withId(R.id.textview)).check(hasSelection("hijk lmn o"));
707
708 onHandleView(com.android.internal.R.id.selection_end_handle)
709 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('r')));
710 onView(withId(R.id.textview)).check(hasSelection("hijk lmn opq"));
711 }
712
Siyamed Sinir21914182017-06-16 15:24:00 -0700713 @Test
714 public void testSelectionHandles_snapToWordBoundary_multiLine() {
Keisuke Kuroyanagiec913c02015-11-24 18:56:20 +0900715 final String text = "abcd efg\n" + "hijk lmn\n" + "opqr stu";
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900716 onView(withId(R.id.textview)).perform(replaceText(text));
717 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('m')));
Keisuke Kuroyanagiec913c02015-11-24 18:56:20 +0900718
Siyamed Sinir21914182017-06-16 15:24:00 -0700719 final TextView textView = mActivity.findViewById(R.id.textview);
Keisuke Kuroyanagiec913c02015-11-24 18:56:20 +0900720
721 onHandleView(com.android.internal.R.id.selection_start_handle)
722 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('c')));
723 onView(withId(R.id.textview)).check(hasSelection("abcd efg\nhijk lmn"));
724
725 onHandleView(com.android.internal.R.id.selection_start_handle)
726 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('g')));
727 onView(withId(R.id.textview)).check(hasSelection("g\nhijk lmn"));
728
729 onHandleView(com.android.internal.R.id.selection_start_handle)
730 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('m')));
731 onView(withId(R.id.textview)).check(hasSelection("lmn"));
732
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900733 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
Keisuke Kuroyanagiec913c02015-11-24 18:56:20 +0900734
735 onHandleView(com.android.internal.R.id.selection_end_handle)
736 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('u')));
737 onView(withId(R.id.textview)).check(hasSelection("hijk lmn\nopqr stu"));
738
739 onHandleView(com.android.internal.R.id.selection_end_handle)
740 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('p')));
741 onView(withId(R.id.textview)).check(hasSelection("hijk lmn\no"));
742
743 onHandleView(com.android.internal.R.id.selection_end_handle)
744 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('i')));
745 onView(withId(R.id.textview)).check(hasSelection("hijk"));
746 }
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900747
Siyamed Sinir21914182017-06-16 15:24:00 -0700748 @Test
Abodunrinwa Tokideb2f492017-11-06 18:55:17 +0000749 public void testSelectionHandles_visibleEvenWithEmptyMenu() {
750 ((TextView) mActivity.findViewById(R.id.textview)).setCustomSelectionActionModeCallback(
751 new ActionMode.Callback() {
752 @Override
753 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
754 menu.clear();
755 return true;
756 }
757
758 @Override
759 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
760 menu.clear();
761 return true;
762 }
763
764 @Override
765 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
766 return false;
767 }
768
769 @Override
770 public void onDestroyActionMode(ActionMode mode) {}
771 });
772 final String text = "abcd efg hijk lmn";
773 onView(withId(R.id.textview)).perform(replaceText(text));
774
775 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
776
777 onHandleView(com.android.internal.R.id.selection_start_handle)
778 .check(matches(isDisplayed()));
779 onHandleView(com.android.internal.R.id.selection_end_handle)
780 .check(matches(isDisplayed()));
781 }
782
783 @Test
Siyamed Sinir21914182017-06-16 15:24:00 -0700784 public void testSetSelectionAndActionMode() throws Throwable {
Abodunrinwa Toki6264cbd2017-11-06 15:12:52 +0000785 final TextView textView = mActivity.findViewById(R.id.textview);
786 final ActionMode.Callback amCallback = mock(ActionMode.Callback.class);
787 when(amCallback.onCreateActionMode(any(ActionMode.class), any(Menu.class)))
788 .thenReturn(true);
789 when(amCallback.onPrepareActionMode(any(ActionMode.class), any(Menu.class)))
790 .thenReturn(true);
791 textView.setCustomSelectionActionModeCallback(amCallback);
792
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900793 final String text = "abc def";
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900794 onView(withId(R.id.textview)).perform(replaceText(text));
Siyamed Sinir21914182017-06-16 15:24:00 -0700795 mActivityRule.runOnUiThread(
796 () -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
797 mInstrumentation.waitForIdleSync();
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900798 // Don't automatically start action mode.
Abodunrinwa Toki6264cbd2017-11-06 15:12:52 +0000799 verify(amCallback, never()).onCreateActionMode(any(ActionMode.class), any(Menu.class));
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900800 // Make sure that "Select All" is included in the selection action mode when the entire text
801 // is not selected.
Keisuke Kuroyanagi99e8623182016-11-16 20:05:14 +0900802 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900803 sleepForFloatingToolbarPopup();
804 assertFloatingToolbarIsDisplayed();
805 // Changing the selection range by API should not interrupt the selection action mode.
Siyamed Sinir21914182017-06-16 15:24:00 -0700806 mActivityRule.runOnUiThread(
807 () -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
808 mInstrumentation.waitForIdleSync();
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900809 sleepForFloatingToolbarPopup();
810 assertFloatingToolbarIsDisplayed();
811 assertFloatingToolbarContainsItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700812 mActivity.getString(com.android.internal.R.string.selectAll));
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900813 // Make sure that "Select All" is no longer included when the entire text is selected by
814 // API.
Siyamed Sinir21914182017-06-16 15:24:00 -0700815 mActivityRule.runOnUiThread(
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900816 () -> Selection.setSelection((Spannable) textView.getText(), 0, text.length()));
Siyamed Sinir21914182017-06-16 15:24:00 -0700817 mInstrumentation.waitForIdleSync();
818
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900819 sleepForFloatingToolbarPopup();
820 assertFloatingToolbarIsDisplayed();
821 assertFloatingToolbarDoesNotContainItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700822 mActivity.getString(com.android.internal.R.string.selectAll));
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900823 // Make sure that shrinking the selection range to cursor (an empty range) by API
824 // terminates selection action mode and does not trigger the insertion action mode.
Siyamed Sinir21914182017-06-16 15:24:00 -0700825 mActivityRule.runOnUiThread(
826 () -> Selection.setSelection((Spannable) textView.getText(), 0));
827 mInstrumentation.waitForIdleSync();
828
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900829 // Make sure that user click can trigger the insertion action mode.
830 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
831 onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
832 sleepForFloatingToolbarPopup();
833 assertFloatingToolbarIsDisplayed();
834 // Make sure that an existing insertion action mode keeps alive after the insertion point is
835 // moved by API.
Siyamed Sinir21914182017-06-16 15:24:00 -0700836 mActivityRule.runOnUiThread(
837 () -> Selection.setSelection((Spannable) textView.getText(), 0));
838 mInstrumentation.waitForIdleSync();
839
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900840 sleepForFloatingToolbarPopup();
841 assertFloatingToolbarIsDisplayed();
842 assertFloatingToolbarDoesNotContainItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700843 mActivity.getString(com.android.internal.R.string.copy));
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900844 // Make sure that selection action mode is started after selection is created by API when
845 // insertion action mode is active.
Siyamed Sinir21914182017-06-16 15:24:00 -0700846 mActivityRule.runOnUiThread(
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900847 () -> Selection.setSelection((Spannable) textView.getText(), 1, text.length()));
Siyamed Sinir21914182017-06-16 15:24:00 -0700848 mInstrumentation.waitForIdleSync();
849
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900850 sleepForFloatingToolbarPopup();
851 assertFloatingToolbarIsDisplayed();
852 assertFloatingToolbarContainsItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700853 mActivity.getString(com.android.internal.R.string.copy));
Keisuke Kuroyanagic477b582016-03-15 15:38:40 +0900854 }
Keisuke Kuroyanagi183fd502016-04-01 15:00:53 +0900855
Siyamed Sinir21914182017-06-16 15:24:00 -0700856 @Test
857 public void testTransientState() throws Throwable {
Keisuke Kuroyanagi183fd502016-04-01 15:00:53 +0900858 final String text = "abc def";
Keisuke Kuroyanagi183fd502016-04-01 15:00:53 +0900859 onView(withId(R.id.textview)).perform(replaceText(text));
860
Siyamed Sinir21914182017-06-16 15:24:00 -0700861 final TextView textView = mActivity.findViewById(R.id.textview);
Keisuke Kuroyanagi183fd502016-04-01 15:00:53 +0900862 assertFalse(textView.hasTransientState());
863
864 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('b')));
865 // hasTransientState should return true when user generated selection is active.
866 assertTrue(textView.hasTransientState());
867 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('d')));
868 // hasTransientState should return false as the selection has been cleared.
869 assertFalse(textView.hasTransientState());
Siyamed Sinir21914182017-06-16 15:24:00 -0700870 mActivityRule.runOnUiThread(
Keisuke Kuroyanagi183fd502016-04-01 15:00:53 +0900871 () -> Selection.setSelection((Spannable) textView.getText(), 0, text.length()));
Siyamed Sinir21914182017-06-16 15:24:00 -0700872 mInstrumentation.waitForIdleSync();
873
Keisuke Kuroyanagi183fd502016-04-01 15:00:53 +0900874 // hasTransientState should return false when selection is created by API.
875 assertFalse(textView.hasTransientState());
876 }
Abodunrinwa Toki6eecdc92017-04-11 05:15:25 +0100877
Siyamed Sinir21914182017-06-16 15:24:00 -0700878 @Test
879 public void testResetMenuItemTitle() throws Throwable {
880 mActivity.getSystemService(TextClassificationManager.class).setTextClassifier(null);
881 final TextView textView = mActivity.findViewById(R.id.textview);
Abodunrinwa Toki7b971802017-05-23 19:01:56 +0100882 final int itemId = 1;
883 final String title1 = " AFIGBO";
884 final int index = title1.indexOf('I');
885 final String title2 = title1.substring(index);
886 final String[] title = new String[]{title1};
Siyamed Sinir21914182017-06-16 15:24:00 -0700887 mActivityRule.runOnUiThread(() -> textView.setCustomSelectionActionModeCallback(
Abodunrinwa Toki7b971802017-05-23 19:01:56 +0100888 new ActionMode.Callback() {
889 @Override
890 public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
891 return true;
892 }
893
894 @Override
895 public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
896 menu.removeItem(itemId);
897 menu.add(Menu.NONE /* group */, itemId, 0 /* order */, title[0]);
898 return true;
899 }
900
901 @Override
902 public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
903 return false;
904 }
905
906 @Override
907 public void onDestroyActionMode(ActionMode actionMode) {
908 }
909 }));
Siyamed Sinir21914182017-06-16 15:24:00 -0700910 mInstrumentation.waitForIdleSync();
911
Abodunrinwa Toki7b971802017-05-23 19:01:56 +0100912 onView(withId(R.id.textview)).perform(replaceText(title1));
913 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(index));
914 sleepForFloatingToolbarPopup();
915 assertFloatingToolbarContainsItem(title1);
916
917 // Change the menu item title.
918 title[0] = title2;
919 // Change the selection to invalidate the action mode without restarting it.
920 onHandleView(com.android.internal.R.id.selection_start_handle)
921 .perform(dragHandle(textView, Handle.SELECTION_START, index));
922 sleepForFloatingToolbarPopup();
923 assertFloatingToolbarContainsItem(title2);
924 }
925
Siyamed Sinir21914182017-06-16 15:24:00 -0700926 @Test
927 public void testAssistItemIsAtIndexZero() throws Throwable {
Abodunrinwa Toki76b51dc2017-07-13 23:37:11 +0100928 useSystemDefaultTextClassifier();
Siyamed Sinir21914182017-06-16 15:24:00 -0700929 final TextView textView = mActivity.findViewById(R.id.textview);
930 mActivityRule.runOnUiThread(() -> textView.setCustomSelectionActionModeCallback(
Abodunrinwa Toki6eecdc92017-04-11 05:15:25 +0100931 new ActionMode.Callback() {
932 @Override
933 public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
934 // Create another item at order position 0 to confirm that it will never be
935 // placed before the textAssist item.
936 menu.add(Menu.NONE, 0 /* id */, 0 /* order */, "Test");
937 return true;
938 }
939
940 @Override
941 public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
942 return true;
943 }
944
945 @Override
946 public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
947 return false;
948 }
949
950 @Override
Abodunrinwa Tokiea6cb122017-04-28 22:14:13 +0100951 public void onDestroyActionMode(ActionMode actionMode) {
952 }
Abodunrinwa Toki6eecdc92017-04-11 05:15:25 +0100953 }));
Siyamed Sinir21914182017-06-16 15:24:00 -0700954 mInstrumentation.waitForIdleSync();
Abodunrinwa Toki6eecdc92017-04-11 05:15:25 +0100955 final String text = "droid@android.com";
956
957 onView(withId(R.id.textview)).perform(replaceText(text));
958 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('@')));
959 sleepForFloatingToolbarPopup();
960 assertFloatingToolbarItemIndex(android.R.id.textAssist, 0);
961 }
Abodunrinwa Tokiea6cb122017-04-28 22:14:13 +0100962
Siyamed Sinir21914182017-06-16 15:24:00 -0700963 @Test
Abodunrinwa Toki76b51dc2017-07-13 23:37:11 +0100964 public void testNoAssistItemForPasswordField() throws Throwable {
965 useSystemDefaultTextClassifier();
966 final TextView textView = mActivity.findViewById(R.id.textview);
967 mActivityRule.runOnUiThread(() -> {
968 textView.setInputType(
969 InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
970 });
971 mInstrumentation.waitForIdleSync();
972 final String password = "afigbo@android.com";
973
974 onView(withId(R.id.textview)).perform(replaceText(password));
975 onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(password.indexOf('@')));
976 sleepForFloatingToolbarPopup();
977 assertFloatingToolbarDoesNotContainItem(android.R.id.textAssist);
978 }
979
980 @Test
Siyamed Sinir21914182017-06-16 15:24:00 -0700981 public void testPastePlainText_menuAction() {
Abodunrinwa Tokiea6cb122017-04-28 22:14:13 +0100982 initializeClipboardWithText(TextStyle.STYLED);
983
984 onView(withId(R.id.textview)).perform(replaceText(""));
985 onView(withId(R.id.textview)).perform(longClick());
986 sleepForFloatingToolbarPopup();
987 clickFloatingToolbarItem(
Siyamed Sinir21914182017-06-16 15:24:00 -0700988 mActivity.getString(com.android.internal.R.string.paste_as_plain_text));
989 mInstrumentation.waitForIdleSync();
Abodunrinwa Tokiea6cb122017-04-28 22:14:13 +0100990
991 onView(withId(R.id.textview)).check(matches(withText("styledtext")));
992 onView(withId(R.id.textview)).check(doesNotHaveStyledText());
993 }
994
Siyamed Sinir21914182017-06-16 15:24:00 -0700995 @Test
Abodunrinwa Tokiea6cb122017-04-28 22:14:13 +0100996 public void testPastePlainText_noMenuItemForPlainText() {
997 initializeClipboardWithText(TextStyle.PLAIN);
998
999 onView(withId(R.id.textview)).perform(replaceText(""));
1000 onView(withId(R.id.textview)).perform(longClick());
1001 sleepForFloatingToolbarPopup();
1002
1003 assertFloatingToolbarDoesNotContainItem(
Siyamed Sinir21914182017-06-16 15:24:00 -07001004 mActivity.getString(com.android.internal.R.string.paste_as_plain_text));
Abodunrinwa Tokiea6cb122017-04-28 22:14:13 +01001005 }
1006
Abodunrinwa Toki76b51dc2017-07-13 23:37:11 +01001007 private void useSystemDefaultTextClassifier() {
1008 mActivity.getSystemService(TextClassificationManager.class).setTextClassifier(null);
1009 }
1010
Abodunrinwa Tokiea6cb122017-04-28 22:14:13 +01001011 private void initializeClipboardWithText(TextStyle textStyle) {
1012 final ClipData clip;
1013 switch (textStyle) {
1014 case STYLED:
1015 clip = ClipData.newHtmlText("html", "styledtext", "<b>styledtext</b>");
1016 break;
1017 case PLAIN:
1018 clip = ClipData.newPlainText("plain", "plaintext");
1019 break;
1020 default:
1021 throw new IllegalArgumentException("Invalid text style");
1022 }
Siyamed Sinir21914182017-06-16 15:24:00 -07001023 mActivity.getWindow().getDecorView().post(() ->
1024 mActivity.getSystemService(ClipboardManager.class).setPrimaryClip(clip));
1025 mInstrumentation.waitForIdleSync();
Abodunrinwa Tokiea6cb122017-04-28 22:14:13 +01001026 }
1027
1028 private enum TextStyle {
1029 PLAIN, STYLED
1030 }
Abodunrinwa Toki223c8392015-07-24 20:55:59 -07001031}