blob: f497db2256dd166ae90844f724384606a251b111 [file] [log] [blame]
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -08001/*
2 * Copyright (C) 2019 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
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -080019import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -080020import static android.widget.espresso.TextViewActions.dragOnText;
21import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -080022import static android.widget.espresso.TextViewAssertions.hasSelection;
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -080023
24import static androidx.test.espresso.Espresso.onView;
25import static androidx.test.espresso.action.ViewActions.replaceText;
26import static androidx.test.espresso.matcher.ViewMatchers.withId;
27
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -080028import static org.hamcrest.Matchers.emptyString;
29import static org.hamcrest.Matchers.not;
Nikita Dubrovsky21c6a962019-12-27 08:48:02 -080030import static org.junit.Assert.assertFalse;
31import static org.junit.Assert.assertTrue;
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -080032
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -080033import android.app.Activity;
34import android.app.Instrumentation;
Nikita Dubrovsky21c6a962019-12-27 08:48:02 -080035import android.view.MotionEvent;
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -080036
37import androidx.test.InstrumentationRegistry;
38import androidx.test.filters.SmallTest;
39import androidx.test.rule.ActivityTestRule;
40import androidx.test.runner.AndroidJUnit4;
41
42import com.android.frameworks.coretests.R;
43
44import com.google.common.base.Strings;
45
46import org.junit.After;
47import org.junit.Before;
48import org.junit.Rule;
49import org.junit.Test;
50import org.junit.runner.RunWith;
51
52@RunWith(AndroidJUnit4.class)
53@SmallTest
54public class EditorCursorDragTest {
55 @Rule
56 public ActivityTestRule<TextViewActivity> mActivityRule = new ActivityTestRule<>(
57 TextViewActivity.class);
58
59 private boolean mOriginalFlagValue;
60 private Instrumentation mInstrumentation;
61 private Activity mActivity;
62
63 @Before
64 public void before() throws Throwable {
65 mInstrumentation = InstrumentationRegistry.getInstrumentation();
66 mActivity = mActivityRule.getActivity();
67 mOriginalFlagValue = Editor.FLAG_ENABLE_CURSOR_DRAG;
68 Editor.FLAG_ENABLE_CURSOR_DRAG = true;
69 }
70 @After
71 public void after() throws Throwable {
72 Editor.FLAG_ENABLE_CURSOR_DRAG = mOriginalFlagValue;
73 }
74
75 @Test
76 public void testCursorDrag_horizontal_whenTextViewContentsFitOnScreen() throws Throwable {
77 String text = "Hello world!";
78 onView(withId(R.id.textview)).perform(replaceText(text));
79 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
80
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -080081 // Swipe left to right to drag the cursor. The cursor should end up at the position where
82 // the finger is lifted.
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -080083 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("llo"), text.indexOf("!")));
84 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(11));
85
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -080086 // Swipe right to left to drag the cursor. The cursor should end up at the position where
87 // the finger is lifted.
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -080088 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("!"), text.indexOf("llo")));
89 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
90 }
91
92 @Test
93 public void testCursorDrag_horizontal_whenTextViewContentsLargerThanScreen() throws Throwable {
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -080094 String text = "Hello world!\n\n"
95 + Strings.repeat("Bla\n\n", 200) + "Bye";
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -080096 onView(withId(R.id.textview)).perform(replaceText(text));
97 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
98
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -080099 // Swipe left to right to drag the cursor. The cursor should end up at the position where
100 // the finger is lifted.
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800101 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("llo"), text.indexOf("!")));
102 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(11));
103
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800104 // Swipe right to left to drag the cursor. The cursor should end up at the position where
105 // the finger is lifted.
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800106 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("!"), text.indexOf("llo")));
107 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
108 }
109
110 @Test
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800111 public void testCursorDrag_diagonal_whenTextViewContentsFitOnScreen() throws Throwable {
112 StringBuilder sb = new StringBuilder();
113 for (int i = 1; i <= 9; i++) {
114 sb.append("line").append(i).append("\n");
115 }
116 String text = sb.toString();
117 onView(withId(R.id.textview)).perform(replaceText(text));
118 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
119
120 // Swipe along a diagonal path. This should drag the cursor.
121 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("2")));
122 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("2")));
123
124 // Swipe along a steeper diagonal path. This should still drag the cursor.
125 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("3")));
126 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("3")));
127
128 // Swipe right-down along a very steep diagonal path. This should not drag the cursor.
129 // Normally this would trigger a scroll, but since the full view fits on the screen there
130 // is nothing to scroll and the gesture will trigger a selection drag.
131 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("7")));
132 onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
133
134 // Swipe right-up along a very steep diagonal path. This should not drag the cursor.
135 // Normally this would trigger a scroll, but since the full view fits on the screen there
136 // is nothing to scroll and the gesture will trigger a selection drag.
137 int index = text.indexOf("line9");
138 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(index));
139 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
140 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line7"), text.indexOf("1")));
141 onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
142 }
143
144 @Test
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800145 public void testCursorDrag_diagonal_whenTextViewContentsLargerThanScreen() throws Throwable {
146 StringBuilder sb = new StringBuilder();
147 for (int i = 1; i <= 9; i++) {
148 sb.append("line").append(i).append("\n");
149 }
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800150 sb.append(Strings.repeat("0123456789\n", 400)).append("Last");
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800151 String text = sb.toString();
152 onView(withId(R.id.textview)).perform(replaceText(text));
153 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
154
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800155 // Swipe along a diagonal path. This should drag the cursor.
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800156 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("2")));
157 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("2")));
158
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800159 // Swipe along a steeper diagonal path. This should still drag the cursor.
160 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("3")));
161 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("3")));
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800162
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800163 // Swipe right-down along a very steep diagonal path. This should not drag the cursor.
164 // Normally this would trigger a scroll up, but since the view is already at the top there
165 // is nothing to scroll and the gesture will trigger a selection drag.
166 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("7")));
167 onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800168
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800169 // Swipe right-up along a very steep diagonal path. This should not drag the cursor. This
170 // will trigger a downward scroll and the cursor position will not change.
171 int index = text.indexOf("line9");
172 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(index));
173 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
174 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line7"), text.indexOf("1")));
175 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800176 }
177
178 @Test
179 public void testCursorDrag_vertical_whenTextViewContentsFitOnScreen() throws Throwable {
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800180 String text = "012345_aaa\n"
181 + "0123456789\n"
182 + "012345_bbb\n"
183 + "0123456789\n"
184 + "012345_ccc\n"
185 + "0123456789\n"
186 + "012345_ddd";
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800187 onView(withId(R.id.textview)).perform(replaceText(text));
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800188
189 // Swipe up vertically. This should not drag the cursor. Since there's also nothing to
190 // scroll, the gesture will trigger a selection drag.
191 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800192 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800193 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("bbb"), text.indexOf("aaa")));
194 onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800195
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800196 // Swipe down vertically. This should not drag the cursor. Since there's also nothing to
197 // scroll, the gesture will trigger a selection drag.
198 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
199 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
200 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("ccc"), text.indexOf("ddd")));
201 onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800202 }
203
204 @Test
205 public void testCursorDrag_vertical_whenTextViewContentsLargerThanScreen() throws Throwable {
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800206 String text = "012345_aaa\n"
207 + "0123456789\n"
208 + "012345_bbb\n"
209 + "0123456789\n"
210 + "012345_ccc\n"
211 + "0123456789\n"
212 + "012345_ddd\n"
213 + Strings.repeat("0123456789\n", 400) + "012345_zzz";
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800214 onView(withId(R.id.textview)).perform(replaceText(text));
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800215 onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf("ddd")));
216 int initialCursorPosition = text.indexOf("ddd");
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800217 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(initialCursorPosition));
218
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800219 // Swipe up vertically. This should trigger a downward scroll.
220 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("bbb"), text.indexOf("aaa")));
221 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(initialCursorPosition));
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800222
Nikita Dubrovskycd36c5e2019-12-19 16:15:17 -0800223 // Swipe down vertically. This should trigger an upward scroll.
224 onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("ccc"), text.indexOf("ddd")));
225 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(initialCursorPosition));
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800226 }
Nikita Dubrovsky21c6a962019-12-27 08:48:02 -0800227
228 @Test
229 public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
230 String text = "testEditor_onTouchEvent_cursorDrag";
231 onView(withId(R.id.textview)).perform(replaceText(text));
232 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
233
234 TextView tv = mActivity.findViewById(R.id.textview);
235 Editor editor = tv.getEditorForTesting();
236
237 // Simulate a tap-and-drag gesture. This should trigger a cursor drag.
238 long event1Time = 1001;
239 MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
240 mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
241 mInstrumentation.waitForIdleSync();
242 assertFalse(editor.getInsertionController().isCursorBeingModified());
243 assertFalse(editor.getSelectionController().isCursorBeingModified());
244
245 long event2Time = 1002;
246 MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
247 mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
248 mInstrumentation.waitForIdleSync();
249 assertFalse(editor.getInsertionController().isCursorBeingModified());
250 assertFalse(editor.getSelectionController().isCursorBeingModified());
251
252 long event3Time = 1003;
253 MotionEvent event3 = moveEvent(event3Time, event3Time, 120f, 30f);
254 mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
255 mInstrumentation.waitForIdleSync();
256 assertTrue(editor.getInsertionController().isCursorBeingModified());
257 assertFalse(editor.getSelectionController().isCursorBeingModified());
258
259 long event4Time = 1004;
260 MotionEvent event4 = upEvent(event3Time, event4Time, 120f, 30f);
261 mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
262 mInstrumentation.waitForIdleSync();
263 assertFalse(editor.getInsertionController().isCursorBeingModified());
264 assertFalse(editor.getSelectionController().isCursorBeingModified());
265 }
266
267 @Test
268 public void testEditor_onTouchEvent_selectionDrag() throws Throwable {
269 String text = "testEditor_onTouchEvent_selectionDrag";
270 onView(withId(R.id.textview)).perform(replaceText(text));
271 onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
272
273 TextView tv = mActivity.findViewById(R.id.textview);
274 Editor editor = tv.getEditorForTesting();
275
276 // Simulate a double-tap followed by a drag. This should trigger a selection drag.
277 long event1Time = 1001;
278 MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
279 mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
280 mInstrumentation.waitForIdleSync();
281 assertFalse(editor.getInsertionController().isCursorBeingModified());
282 assertFalse(editor.getSelectionController().isCursorBeingModified());
283
284 long event2Time = 1002;
285 MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
286 mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
287 mInstrumentation.waitForIdleSync();
288 assertFalse(editor.getInsertionController().isCursorBeingModified());
289 assertFalse(editor.getSelectionController().isCursorBeingModified());
290
291 long event3Time = 1003;
292 MotionEvent event3 = downEvent(event3Time, event3Time, 20f, 30f);
293 mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
294 mInstrumentation.waitForIdleSync();
295 assertFalse(editor.getInsertionController().isCursorBeingModified());
296 assertTrue(editor.getSelectionController().isCursorBeingModified());
297
298 long event4Time = 1004;
299 MotionEvent event4 = moveEvent(event3Time, event4Time, 120f, 30f);
300 mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
301 mInstrumentation.waitForIdleSync();
302 assertFalse(editor.getInsertionController().isCursorBeingModified());
303 assertTrue(editor.getSelectionController().isCursorBeingModified());
304
305 long event5Time = 1005;
306 MotionEvent event5 = upEvent(event3Time, event5Time, 120f, 30f);
307 mActivity.runOnUiThread(() -> editor.onTouchEvent(event5));
308 mInstrumentation.waitForIdleSync();
309 assertFalse(editor.getInsertionController().isCursorBeingModified());
310 assertFalse(editor.getSelectionController().isCursorBeingModified());
311 }
312
313 private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
314 return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0);
315 }
316
317 private static MotionEvent upEvent(long downTime, long eventTime, float x, float y) {
318 return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
319 }
320
321 private static MotionEvent moveEvent(long downTime, long eventTime, float x, float y) {
322 return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
323 }
Nikita Dubrovsky9a1369b2019-12-06 09:25:20 -0800324}