blob: 83ce67bd96ad11e316f34d6f2b9c9fe87761f13c [file] [log] [blame]
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +01001/*
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.espresso;
18
Tadashi G. Takaoka7a4047f2019-02-02 09:55:59 +090019import static androidx.test.espresso.action.ViewActions.actionWithAssertions;
Siyamed Sinir6b61d812017-06-14 11:13:30 -070020
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +090021import android.graphics.Rect;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010022import android.text.Layout;
Keisuke Kuroyanagia0b3c062015-12-07 11:09:40 -080023import android.view.MotionEvent;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010024import android.view.View;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +090025import android.widget.Editor;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +090026import android.widget.Editor.HandleView;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010027import android.widget.TextView;
28
Tadashi G. Takaoka7a4047f2019-02-02 09:55:59 +090029import androidx.test.espresso.PerformException;
30import androidx.test.espresso.ViewAction;
31import androidx.test.espresso.action.CoordinatesProvider;
32import androidx.test.espresso.action.GeneralLocation;
33import androidx.test.espresso.action.Press;
34import androidx.test.espresso.action.Tap;
35import androidx.test.espresso.util.HumanReadables;
36
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010037/**
38 * A collection of actions on a {@link android.widget.TextView}.
39 */
40public final class TextViewActions {
41
42 private TextViewActions() {}
43
44 /**
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +010045 * Returns an action that clicks on text at an index on the TextView.<br>
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010046 * <br>
47 * View constraints:
48 * <ul>
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +010049 * <li>must be a TextView displayed on screen
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010050 * <ul>
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +010051 *
52 * @param index The index of the TextView's text to click on.
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010053 */
54 public static ViewAction clickOnTextAtIndex(int index) {
55 return actionWithAssertions(
Keisuke Kuroyanagi5f318b62016-04-01 19:16:31 +090056 new ViewClickAction(Tap.SINGLE, new TextCoordinates(index), Press.FINGER));
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010057 }
58
Siyamed Sinir6b61d812017-06-14 11:13:30 -070059
60 /**
61 * Returns an action that single-clicks by mouse on the View.<br>
62 * <br>
63 * View constraints:
64 * <ul>
65 * <li>must be a View displayed on screen
66 * <ul>
67 */
68 public static ViewAction mouseClick() {
69 return actionWithAssertions(new MouseClickAction(Tap.SINGLE, GeneralLocation.VISIBLE_CENTER,
70 MotionEvent.BUTTON_PRIMARY));
71 }
72
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010073 /**
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -080074 * Returns an action that clicks by mouse on text at an index on the TextView.<br>
75 * <br>
76 * View constraints:
77 * <ul>
78 * <li>must be a TextView displayed on screen
79 * <ul>
80 *
81 * @param index The index of the TextView's text to click on.
82 */
83 public static ViewAction mouseClickOnTextAtIndex(int index) {
Keisuke Kuroyanagia0b3c062015-12-07 11:09:40 -080084 return mouseClickOnTextAtIndex(index, MotionEvent.BUTTON_PRIMARY);
85 }
86
87 /**
88 * Returns an action that clicks by mouse on text at an index on the TextView.<br>
89 * <br>
90 * View constraints:
91 * <ul>
92 * <li>must be a TextView displayed on screen
93 * <ul>
94 *
95 * @param index The index of the TextView's text to click on.
96 * @param button the mouse button to use.
97 */
98 public static ViewAction mouseClickOnTextAtIndex(int index,
99 @MouseUiController.MouseButton int button) {
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800100 return actionWithAssertions(
Keisuke Kuroyanagia0b3c062015-12-07 11:09:40 -0800101 new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), button));
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800102 }
103
104 /**
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100105 * Returns an action that double-clicks on text at an index on the TextView.<br>
106 * <br>
107 * View constraints:
108 * <ul>
109 * <li>must be a TextView displayed on screen
110 * <ul>
111 *
112 * @param index The index of the TextView's text to double-click on.
113 */
114 public static ViewAction doubleClickOnTextAtIndex(int index) {
115 return actionWithAssertions(
Keisuke Kuroyanagi5f318b62016-04-01 19:16:31 +0900116 new ViewClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.FINGER));
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100117 }
118
119 /**
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800120 * Returns an action that double-clicks by mouse on text at an index on the TextView.<br>
121 * <br>
122 * View constraints:
123 * <ul>
124 * <li>must be a TextView displayed on screen
125 * <ul>
126 *
127 * @param index The index of the TextView's text to double-click on.
128 */
129 public static ViewAction mouseDoubleClickOnTextAtIndex(int index) {
130 return actionWithAssertions(
Keisuke Kuroyanagi46faad62015-12-06 10:03:23 -0800131 new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index)));
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800132 }
133
134 /**
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100135 * Returns an action that long presses on text at an index on the TextView.<br>
136 * <br>
137 * View constraints:
138 * <ul>
139 * <li>must be a TextView displayed on screen
140 * <ul>
141 *
142 * @param index The index of the TextView's text to long press on.
143 */
144 public static ViewAction longPressOnTextAtIndex(int index) {
145 return actionWithAssertions(
Keisuke Kuroyanagi5f318b62016-04-01 19:16:31 +0900146 new ViewClickAction(Tap.LONG, new TextCoordinates(index), Press.FINGER));
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100147 }
148
149 /**
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800150 * Returns an action that long click by mouse on text at an index on the TextView.<br>
151 * <br>
152 * View constraints:
153 * <ul>
154 * <li>must be a TextView displayed on screen
155 * <ul>
156 *
157 * @param index The index of the TextView's text to long click on.
158 */
159 public static ViewAction mouseLongClickOnTextAtIndex(int index) {
160 return actionWithAssertions(
Keisuke Kuroyanagi46faad62015-12-06 10:03:23 -0800161 new MouseClickAction(Tap.LONG, new TextCoordinates(index)));
162 }
163
164 /**
165 * Returns an action that triple-clicks by mouse on text at an index on the TextView.<br>
166 * <br>
167 * View constraints:
168 * <ul>
169 * <li>must be a TextView displayed on screen
170 * <ul>
171 *
172 * @param index The index of the TextView's text to triple-click on.
173 */
174 public static ViewAction mouseTripleClickOnTextAtIndex(int index) {
175 return actionWithAssertions(
176 new MouseClickAction(MouseClickAction.CLICK.TRIPLE, new TextCoordinates(index)));
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800177 }
178
179 /**
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100180 * Returns an action that long presses then drags on text from startIndex to endIndex on the
181 * TextView.<br>
182 * <br>
183 * View constraints:
184 * <ul>
185 * <li>must be a TextView displayed on screen
186 * <ul>
187 *
188 * @param startIndex The index of the TextView's text to start a drag from
189 * @param endIndex The index of the TextView's text to end the drag at
190 */
191 public static ViewAction longPressAndDragOnText(int startIndex, int endIndex) {
192 return actionWithAssertions(
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900193 new DragAction(
194 DragAction.Drag.LONG_PRESS,
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100195 new TextCoordinates(startIndex),
196 new TextCoordinates(endIndex),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900197 Press.FINGER,
198 TextView.class));
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100199 }
200
201 /**
202 * Returns an action that double taps then drags on text from startIndex to endIndex on the
203 * TextView.<br>
204 * <br>
205 * View constraints:
206 * <ul>
207 * <li>must be a TextView displayed on screen
208 * <ul>
209 *
210 * @param startIndex The index of the TextView's text to start a drag from
211 * @param endIndex The index of the TextView's text to end the drag at
212 */
213 public static ViewAction doubleTapAndDragOnText(int startIndex, int endIndex) {
214 return actionWithAssertions(
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900215 new DragAction(
216 DragAction.Drag.DOUBLE_TAP,
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100217 new TextCoordinates(startIndex),
218 new TextCoordinates(endIndex),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900219 Press.FINGER,
220 TextView.class));
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100221 }
222
223 /**
Keisuke Kuroyanagi88cabed2015-10-08 19:12:56 +0900224 * Returns an action that click then drags by mouse on text from startIndex to endIndex on the
225 * TextView.<br>
226 * <br>
227 * View constraints:
228 * <ul>
229 * <li>must be a TextView displayed on screen
230 * <ul>
231 *
232 * @param startIndex The index of the TextView's text to start a drag from
233 * @param endIndex The index of the TextView's text to end the drag at
234 */
235 public static ViewAction mouseDragOnText(int startIndex, int endIndex) {
236 return actionWithAssertions(
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900237 new DragAction(
238 DragAction.Drag.MOUSE_DOWN,
Keisuke Kuroyanagi88cabed2015-10-08 19:12:56 +0900239 new TextCoordinates(startIndex),
240 new TextCoordinates(endIndex),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900241 Press.PINPOINT,
242 TextView.class));
243 }
244
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800245 /**
246 * Returns an action that double click then drags by mouse on text from startIndex to endIndex
247 * on the TextView.<br>
248 * <br>
249 * View constraints:
250 * <ul>
251 * <li>must be a TextView displayed on screen
252 * <ul>
253 *
254 * @param startIndex The index of the TextView's text to start a drag from
255 * @param endIndex The index of the TextView's text to end the drag at
256 */
257 public static ViewAction mouseDoubleClickAndDragOnText(int startIndex, int endIndex) {
258 return actionWithAssertions(
259 new DragAction(
260 DragAction.Drag.MOUSE_DOUBLE_CLICK,
261 new TextCoordinates(startIndex),
262 new TextCoordinates(endIndex),
263 Press.PINPOINT,
264 TextView.class));
265 }
266
267 /**
268 * Returns an action that long click then drags by mouse on text from startIndex to endIndex
269 * on the TextView.<br>
270 * <br>
271 * View constraints:
272 * <ul>
273 * <li>must be a TextView displayed on screen
274 * <ul>
275 *
276 * @param startIndex The index of the TextView's text to start a drag from
277 * @param endIndex The index of the TextView's text to end the drag at
278 */
279 public static ViewAction mouseLongClickAndDragOnText(int startIndex, int endIndex) {
280 return actionWithAssertions(
281 new DragAction(
282 DragAction.Drag.MOUSE_LONG_CLICK,
283 new TextCoordinates(startIndex),
284 new TextCoordinates(endIndex),
285 Press.PINPOINT,
286 TextView.class));
287 }
288
Keisuke Kuroyanagi46faad62015-12-06 10:03:23 -0800289 /**
290 * Returns an action that triple click then drags by mouse on text from startIndex to endIndex
291 * on the TextView.<br>
292 * <br>
293 * View constraints:
294 * <ul>
295 * <li>must be a TextView displayed on screen
296 * <ul>
297 *
298 * @param startIndex The index of the TextView's text to start a drag from
299 * @param endIndex The index of the TextView's text to end the drag at
300 */
301 public static ViewAction mouseTripleClickAndDragOnText(int startIndex, int endIndex) {
302 return actionWithAssertions(
303 new DragAction(
304 DragAction.Drag.MOUSE_TRIPLE_CLICK,
305 new TextCoordinates(startIndex),
306 new TextCoordinates(endIndex),
307 Press.PINPOINT,
308 TextView.class));
309 }
310
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900311 public enum Handle {
312 SELECTION_START,
313 SELECTION_END,
314 INSERTION
315 };
316
317 /**
318 * Returns an action that tap then drags on the handle from the current position to endIndex on
319 * the TextView.<br>
320 * <br>
321 * View constraints:
322 * <ul>
323 * <li>must be a TextView's drag-handle displayed on screen
324 * <ul>
325 *
326 * @param textView TextView the handle is on
327 * @param handleType Type of the handle
328 * @param endIndex The index of the TextView's text to end the drag at
329 */
330 public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex) {
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900331 return dragHandle(textView, handleType, endIndex, true);
332 }
333
334 /**
335 * Returns an action that tap then drags on the handle from the current position to endIndex on
336 * the TextView.<br>
337 * <br>
338 * View constraints:
339 * <ul>
340 * <li>must be a TextView's drag-handle displayed on screen
341 * <ul>
342 *
343 * @param textView TextView the handle is on
344 * @param handleType Type of the handle
345 * @param endIndex The index of the TextView's text to end the drag at
346 * @param primary whether to use primary direction to get coordinate form index when endIndex is
347 * at a direction boundary.
348 */
349 public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex,
350 boolean primary) {
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900351 return actionWithAssertions(
352 new DragAction(
353 DragAction.Drag.TAP,
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900354 new CurrentHandleCoordinates(textView),
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700355 new HandleCoordinates(textView, handleType, endIndex, primary),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900356 Press.FINGER,
357 Editor.HandleView.class));
358 }
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700359
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900360 /**
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900361 * A provider of the x, y coordinates of the handle dragging point.
362 */
363 private static final class CurrentHandleCoordinates implements CoordinatesProvider {
364 // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS.
365 private final TextView mTextView;
366 private final String mActionDescription;
367
368
369 public CurrentHandleCoordinates(TextView textView) {
370 mTextView = textView;
371 mActionDescription = "Could not locate handle.";
372 }
373
374 @Override
375 public float[] calculateCoordinates(View view) {
376 try {
377 return locateHandle(view);
378 } catch (StringIndexOutOfBoundsException e) {
379 throw new PerformException.Builder()
380 .withActionDescription(mActionDescription)
381 .withViewDescription(HumanReadables.describe(view))
382 .withCause(e)
383 .build();
384 }
385 }
386
387 private float[] locateHandle(View view) {
388 final Rect bounds = new Rect();
389 view.getBoundsOnScreen(bounds);
390 final Rect visibleDisplayBounds = new Rect();
391 mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds);
392 visibleDisplayBounds.right -= 1;
393 visibleDisplayBounds.bottom -= 1;
394 if (!visibleDisplayBounds.intersect(bounds)) {
395 throw new PerformException.Builder()
396 .withActionDescription(mActionDescription
397 + " The handle is entirely out of the visible display frame of"
398 + "the TextView's window.")
399 .withViewDescription(HumanReadables.describe(view))
400 .build();
401 }
402 final float dragPointX = Math.max(Math.min(bounds.centerX(),
403 visibleDisplayBounds.right), visibleDisplayBounds.left);
404 final float verticalOffset = bounds.height() * 0.7f;
405 final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset,
406 visibleDisplayBounds.bottom), visibleDisplayBounds.top);
407 return new float[] {dragPointX, dragPointY};
408 }
409 }
410
411 /**
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900412 * A provider of the x, y coordinates of the handle that points the specified text index in a
413 * text view.
414 */
415 private static final class HandleCoordinates implements CoordinatesProvider {
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900416 // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS.
417 private final static float LINE_SLOP_MULTIPLIER = 0.6f;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900418 private final TextView mTextView;
419 private final Handle mHandleType;
420 private final int mIndex;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900421 private final boolean mPrimary;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900422 private final String mActionDescription;
423
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700424 public HandleCoordinates(TextView textView, Handle handleType, int index, boolean primary) {
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900425 mTextView = textView;
426 mHandleType = handleType;
427 mIndex = index;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900428 mPrimary = primary;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900429 mActionDescription = "Could not locate " + handleType.toString()
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900430 + " handle that points text index: " + index
431 + " (" + (primary ? "primary" : "secondary" ) + ")";
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900432 }
433
434 @Override
435 public float[] calculateCoordinates(View view) {
436 try {
437 return locateHandlePointsTextIndex(view);
438 } catch (StringIndexOutOfBoundsException e) {
439 throw new PerformException.Builder()
440 .withActionDescription(mActionDescription)
441 .withViewDescription(HumanReadables.describe(view))
442 .withCause(e)
443 .build();
444 }
445 }
446
447 private float[] locateHandlePointsTextIndex(View view) {
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900448 if (!(view instanceof HandleView)) {
449 throw new PerformException.Builder()
450 .withActionDescription(mActionDescription + " The view is not a HandleView")
451 .withViewDescription(HumanReadables.describe(view))
452 .build();
453 }
454 final HandleView handleView = (HandleView) view;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900455 final int currentOffset = mHandleType == Handle.SELECTION_START ?
456 mTextView.getSelectionStart() : mTextView.getSelectionEnd();
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900457
458 final Layout layout = mTextView.getLayout();
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900459
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900460 final int currentLine = layout.getLineForOffset(currentOffset);
461 final int targetLine = layout.getLineForOffset(mIndex);
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900462 final float currentX = handleView.getHorizontal(layout, currentOffset);
463 final float currentY = layout.getLineTop(currentLine);
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900464 final float[] currentCoordinates =
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700465 TextCoordinates.convertToScreenCoordinates(mTextView, currentX, currentY);
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900466 final float[] targetCoordinates =
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700467 (new TextCoordinates(mIndex, mPrimary)).calculateCoordinates(mTextView);
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900468 final Rect bounds = new Rect();
469 view.getBoundsOnScreen(bounds);
Keisuke Kuroyanagi5f71b5a2015-12-02 15:02:05 -0800470 final Rect visibleDisplayBounds = new Rect();
471 mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds);
472 visibleDisplayBounds.right -= 1;
473 visibleDisplayBounds.bottom -= 1;
474 if (!visibleDisplayBounds.intersect(bounds)) {
475 throw new PerformException.Builder()
476 .withActionDescription(mActionDescription
477 + " The handle is entirely out of the visible display frame of"
478 + "the TextView's window.")
479 .withViewDescription(HumanReadables.describe(view))
480 .build();
481 }
482 final float dragPointX = Math.max(Math.min(bounds.centerX(),
483 visibleDisplayBounds.right), visibleDisplayBounds.left);
484 final float diffX = dragPointX - currentCoordinates[0];
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900485 final float verticalOffset = bounds.height() * 0.7f;
Keisuke Kuroyanagi5f71b5a2015-12-02 15:02:05 -0800486 final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset,
487 visibleDisplayBounds.bottom), visibleDisplayBounds.top);
488 float diffY = dragPointY - currentCoordinates[1];
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900489 if (currentLine > targetLine) {
490 diffY -= mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER;
491 } else if (currentLine < targetLine) {
492 diffY += mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER;
493 }
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900494 return new float[] {targetCoordinates[0] + diffX, targetCoordinates[1] + diffY};
495 }
Keisuke Kuroyanagi88cabed2015-10-08 19:12:56 +0900496 }
497
498 /**
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100499 * A provider of the x, y coordinates of the text at the specified index in a text view.
500 */
501 private static final class TextCoordinates implements CoordinatesProvider {
502
503 private final int mIndex;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900504 private final boolean mPrimary;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100505 private final String mActionDescription;
506
507 public TextCoordinates(int index) {
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700508 this(index, true);
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900509 }
510
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700511 public TextCoordinates(int index, boolean primary) {
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100512 mIndex = index;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900513 mPrimary = primary;
514 mActionDescription = "Could not locate text at index: " + mIndex
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700515 + " (" + (primary ? "primary" : "secondary" ) + ")";
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100516 }
517
518 @Override
519 public float[] calculateCoordinates(View view) {
520 try {
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700521 return locateTextAtIndex((TextView) view, mIndex, mPrimary);
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100522 } catch (ClassCastException e) {
523 throw new PerformException.Builder()
524 .withActionDescription(mActionDescription)
525 .withViewDescription(HumanReadables.describe(view))
526 .withCause(e)
527 .build();
528 } catch (StringIndexOutOfBoundsException e) {
529 throw new PerformException.Builder()
530 .withActionDescription(mActionDescription)
531 .withViewDescription(HumanReadables.describe(view))
532 .withCause(e)
533 .build();
534 }
535 }
536
537 /**
538 * @throws StringIndexOutOfBoundsException
539 */
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700540 private float[] locateTextAtIndex(TextView textView, int index, boolean primary) {
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100541 if (index < 0 || index > textView.getText().length()) {
542 throw new StringIndexOutOfBoundsException(index);
543 }
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100544 final Layout layout = textView.getLayout();
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700545 final int line = layout.getLineForOffset(index);
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900546 return convertToScreenCoordinates(textView,
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700547 (primary ? layout.getPrimaryHorizontal(index)
548 : layout.getSecondaryHorizontal(index)),
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900549 layout.getLineTop(line));
550 }
551
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700552 /**
553 * Convert TextView's local coordinates to on screen coordinates.
554 * @param textView the TextView
555 * @param x local horizontal coordinate
556 * @param y local vertical coordinate
557 * @return
558 */
559 public static float[] convertToScreenCoordinates(TextView textView, float x, float y) {
560 final int[] xy = new int[2];
561 textView.getLocationOnScreen(xy);
562 return new float[]{ x + textView.getTotalPaddingLeft() - textView.getScrollX() + xy[0],
563 y + textView.getTotalPaddingTop() - textView.getScrollY() + xy[1] };
564 }
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100565 }
566}