blob: b4c547e1fbb7bfe25a0151d5aa9403ea8491bcb7 [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
19import static android.support.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.support.test.espresso.PerformException;
23import android.support.test.espresso.ViewAction;
24import android.support.test.espresso.action.CoordinatesProvider;
Siyamed Sinir6b61d812017-06-14 11:13:30 -070025import android.support.test.espresso.action.GeneralLocation;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010026import android.support.test.espresso.action.Press;
27import android.support.test.espresso.action.Tap;
28import android.support.test.espresso.util.HumanReadables;
29import android.text.Layout;
Keisuke Kuroyanagia0b3c062015-12-07 11:09:40 -080030import android.view.MotionEvent;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010031import android.view.View;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +090032import android.widget.Editor;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +090033import android.widget.Editor.HandleView;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010034import android.widget.TextView;
35
36/**
37 * A collection of actions on a {@link android.widget.TextView}.
38 */
39public final class TextViewActions {
40
41 private TextViewActions() {}
42
43 /**
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +010044 * Returns an action that clicks on text at an index on the TextView.<br>
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010045 * <br>
46 * View constraints:
47 * <ul>
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +010048 * <li>must be a TextView displayed on screen
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010049 * <ul>
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +010050 *
51 * @param index The index of the TextView's text to click on.
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010052 */
53 public static ViewAction clickOnTextAtIndex(int index) {
54 return actionWithAssertions(
Keisuke Kuroyanagi5f318b62016-04-01 19:16:31 +090055 new ViewClickAction(Tap.SINGLE, new TextCoordinates(index), Press.FINGER));
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010056 }
57
Siyamed Sinir6b61d812017-06-14 11:13:30 -070058
59 /**
60 * Returns an action that single-clicks by mouse on the View.<br>
61 * <br>
62 * View constraints:
63 * <ul>
64 * <li>must be a View displayed on screen
65 * <ul>
66 */
67 public static ViewAction mouseClick() {
68 return actionWithAssertions(new MouseClickAction(Tap.SINGLE, GeneralLocation.VISIBLE_CENTER,
69 MotionEvent.BUTTON_PRIMARY));
70 }
71
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010072 /**
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -080073 * Returns an action that clicks by mouse on text at an index on the TextView.<br>
74 * <br>
75 * View constraints:
76 * <ul>
77 * <li>must be a TextView displayed on screen
78 * <ul>
79 *
80 * @param index The index of the TextView's text to click on.
81 */
82 public static ViewAction mouseClickOnTextAtIndex(int index) {
Keisuke Kuroyanagia0b3c062015-12-07 11:09:40 -080083 return mouseClickOnTextAtIndex(index, MotionEvent.BUTTON_PRIMARY);
84 }
85
86 /**
87 * Returns an action that clicks by mouse on text at an index on the TextView.<br>
88 * <br>
89 * View constraints:
90 * <ul>
91 * <li>must be a TextView displayed on screen
92 * <ul>
93 *
94 * @param index The index of the TextView's text to click on.
95 * @param button the mouse button to use.
96 */
97 public static ViewAction mouseClickOnTextAtIndex(int index,
98 @MouseUiController.MouseButton int button) {
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -080099 return actionWithAssertions(
Keisuke Kuroyanagia0b3c062015-12-07 11:09:40 -0800100 new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), button));
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800101 }
102
103 /**
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100104 * Returns an action that double-clicks on text at an index on the TextView.<br>
105 * <br>
106 * View constraints:
107 * <ul>
108 * <li>must be a TextView displayed on screen
109 * <ul>
110 *
111 * @param index The index of the TextView's text to double-click on.
112 */
113 public static ViewAction doubleClickOnTextAtIndex(int index) {
114 return actionWithAssertions(
Keisuke Kuroyanagi5f318b62016-04-01 19:16:31 +0900115 new ViewClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.FINGER));
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100116 }
117
118 /**
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800119 * Returns an action that double-clicks by mouse on text at an index on the TextView.<br>
120 * <br>
121 * View constraints:
122 * <ul>
123 * <li>must be a TextView displayed on screen
124 * <ul>
125 *
126 * @param index The index of the TextView's text to double-click on.
127 */
128 public static ViewAction mouseDoubleClickOnTextAtIndex(int index) {
129 return actionWithAssertions(
Keisuke Kuroyanagi46faad62015-12-06 10:03:23 -0800130 new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index)));
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800131 }
132
133 /**
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100134 * Returns an action that long presses on text at an index on the TextView.<br>
135 * <br>
136 * View constraints:
137 * <ul>
138 * <li>must be a TextView displayed on screen
139 * <ul>
140 *
141 * @param index The index of the TextView's text to long press on.
142 */
143 public static ViewAction longPressOnTextAtIndex(int index) {
144 return actionWithAssertions(
Keisuke Kuroyanagi5f318b62016-04-01 19:16:31 +0900145 new ViewClickAction(Tap.LONG, new TextCoordinates(index), Press.FINGER));
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100146 }
147
148 /**
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800149 * Returns an action that long click by mouse on text at an index on the TextView.<br>
150 * <br>
151 * View constraints:
152 * <ul>
153 * <li>must be a TextView displayed on screen
154 * <ul>
155 *
156 * @param index The index of the TextView's text to long click on.
157 */
158 public static ViewAction mouseLongClickOnTextAtIndex(int index) {
159 return actionWithAssertions(
Keisuke Kuroyanagi46faad62015-12-06 10:03:23 -0800160 new MouseClickAction(Tap.LONG, new TextCoordinates(index)));
161 }
162
163 /**
164 * Returns an action that triple-clicks by mouse on text at an index on the TextView.<br>
165 * <br>
166 * View constraints:
167 * <ul>
168 * <li>must be a TextView displayed on screen
169 * <ul>
170 *
171 * @param index The index of the TextView's text to triple-click on.
172 */
173 public static ViewAction mouseTripleClickOnTextAtIndex(int index) {
174 return actionWithAssertions(
175 new MouseClickAction(MouseClickAction.CLICK.TRIPLE, new TextCoordinates(index)));
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800176 }
177
178 /**
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100179 * Returns an action that long presses then drags on text from startIndex to endIndex on the
180 * TextView.<br>
181 * <br>
182 * View constraints:
183 * <ul>
184 * <li>must be a TextView displayed on screen
185 * <ul>
186 *
187 * @param startIndex The index of the TextView's text to start a drag from
188 * @param endIndex The index of the TextView's text to end the drag at
189 */
190 public static ViewAction longPressAndDragOnText(int startIndex, int endIndex) {
191 return actionWithAssertions(
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900192 new DragAction(
193 DragAction.Drag.LONG_PRESS,
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100194 new TextCoordinates(startIndex),
195 new TextCoordinates(endIndex),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900196 Press.FINGER,
197 TextView.class));
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100198 }
199
200 /**
201 * Returns an action that double taps then drags on text from startIndex to endIndex on the
202 * TextView.<br>
203 * <br>
204 * View constraints:
205 * <ul>
206 * <li>must be a TextView displayed on screen
207 * <ul>
208 *
209 * @param startIndex The index of the TextView's text to start a drag from
210 * @param endIndex The index of the TextView's text to end the drag at
211 */
212 public static ViewAction doubleTapAndDragOnText(int startIndex, int endIndex) {
213 return actionWithAssertions(
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900214 new DragAction(
215 DragAction.Drag.DOUBLE_TAP,
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100216 new TextCoordinates(startIndex),
217 new TextCoordinates(endIndex),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900218 Press.FINGER,
219 TextView.class));
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100220 }
221
222 /**
Keisuke Kuroyanagi88cabed2015-10-08 19:12:56 +0900223 * Returns an action that click then drags by mouse on text from startIndex to endIndex on the
224 * TextView.<br>
225 * <br>
226 * View constraints:
227 * <ul>
228 * <li>must be a TextView displayed on screen
229 * <ul>
230 *
231 * @param startIndex The index of the TextView's text to start a drag from
232 * @param endIndex The index of the TextView's text to end the drag at
233 */
234 public static ViewAction mouseDragOnText(int startIndex, int endIndex) {
235 return actionWithAssertions(
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900236 new DragAction(
237 DragAction.Drag.MOUSE_DOWN,
Keisuke Kuroyanagi88cabed2015-10-08 19:12:56 +0900238 new TextCoordinates(startIndex),
239 new TextCoordinates(endIndex),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900240 Press.PINPOINT,
241 TextView.class));
242 }
243
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800244 /**
245 * Returns an action that double click then drags by mouse on text from startIndex to endIndex
246 * on the TextView.<br>
247 * <br>
248 * View constraints:
249 * <ul>
250 * <li>must be a TextView displayed on screen
251 * <ul>
252 *
253 * @param startIndex The index of the TextView's text to start a drag from
254 * @param endIndex The index of the TextView's text to end the drag at
255 */
256 public static ViewAction mouseDoubleClickAndDragOnText(int startIndex, int endIndex) {
257 return actionWithAssertions(
258 new DragAction(
259 DragAction.Drag.MOUSE_DOUBLE_CLICK,
260 new TextCoordinates(startIndex),
261 new TextCoordinates(endIndex),
262 Press.PINPOINT,
263 TextView.class));
264 }
265
266 /**
267 * Returns an action that long click then drags by mouse on text from startIndex to endIndex
268 * on the TextView.<br>
269 * <br>
270 * View constraints:
271 * <ul>
272 * <li>must be a TextView displayed on screen
273 * <ul>
274 *
275 * @param startIndex The index of the TextView's text to start a drag from
276 * @param endIndex The index of the TextView's text to end the drag at
277 */
278 public static ViewAction mouseLongClickAndDragOnText(int startIndex, int endIndex) {
279 return actionWithAssertions(
280 new DragAction(
281 DragAction.Drag.MOUSE_LONG_CLICK,
282 new TextCoordinates(startIndex),
283 new TextCoordinates(endIndex),
284 Press.PINPOINT,
285 TextView.class));
286 }
287
Keisuke Kuroyanagi46faad62015-12-06 10:03:23 -0800288 /**
289 * Returns an action that triple click then drags by mouse on text from startIndex to endIndex
290 * on the TextView.<br>
291 * <br>
292 * View constraints:
293 * <ul>
294 * <li>must be a TextView displayed on screen
295 * <ul>
296 *
297 * @param startIndex The index of the TextView's text to start a drag from
298 * @param endIndex The index of the TextView's text to end the drag at
299 */
300 public static ViewAction mouseTripleClickAndDragOnText(int startIndex, int endIndex) {
301 return actionWithAssertions(
302 new DragAction(
303 DragAction.Drag.MOUSE_TRIPLE_CLICK,
304 new TextCoordinates(startIndex),
305 new TextCoordinates(endIndex),
306 Press.PINPOINT,
307 TextView.class));
308 }
309
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900310 public enum Handle {
311 SELECTION_START,
312 SELECTION_END,
313 INSERTION
314 };
315
316 /**
317 * Returns an action that tap then drags on the handle from the current position to endIndex on
318 * the TextView.<br>
319 * <br>
320 * View constraints:
321 * <ul>
322 * <li>must be a TextView's drag-handle displayed on screen
323 * <ul>
324 *
325 * @param textView TextView the handle is on
326 * @param handleType Type of the handle
327 * @param endIndex The index of the TextView's text to end the drag at
328 */
329 public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex) {
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900330 return dragHandle(textView, handleType, endIndex, true);
331 }
332
333 /**
334 * Returns an action that tap then drags on the handle from the current position to endIndex on
335 * the TextView.<br>
336 * <br>
337 * View constraints:
338 * <ul>
339 * <li>must be a TextView's drag-handle displayed on screen
340 * <ul>
341 *
342 * @param textView TextView the handle is on
343 * @param handleType Type of the handle
344 * @param endIndex The index of the TextView's text to end the drag at
345 * @param primary whether to use primary direction to get coordinate form index when endIndex is
346 * at a direction boundary.
347 */
348 public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex,
349 boolean primary) {
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900350 return actionWithAssertions(
351 new DragAction(
352 DragAction.Drag.TAP,
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900353 new CurrentHandleCoordinates(textView),
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700354 new HandleCoordinates(textView, handleType, endIndex, primary),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900355 Press.FINGER,
356 Editor.HandleView.class));
357 }
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700358
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900359 /**
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900360 * A provider of the x, y coordinates of the handle dragging point.
361 */
362 private static final class CurrentHandleCoordinates implements CoordinatesProvider {
363 // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS.
364 private final TextView mTextView;
365 private final String mActionDescription;
366
367
368 public CurrentHandleCoordinates(TextView textView) {
369 mTextView = textView;
370 mActionDescription = "Could not locate handle.";
371 }
372
373 @Override
374 public float[] calculateCoordinates(View view) {
375 try {
376 return locateHandle(view);
377 } catch (StringIndexOutOfBoundsException e) {
378 throw new PerformException.Builder()
379 .withActionDescription(mActionDescription)
380 .withViewDescription(HumanReadables.describe(view))
381 .withCause(e)
382 .build();
383 }
384 }
385
386 private float[] locateHandle(View view) {
387 final Rect bounds = new Rect();
388 view.getBoundsOnScreen(bounds);
389 final Rect visibleDisplayBounds = new Rect();
390 mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds);
391 visibleDisplayBounds.right -= 1;
392 visibleDisplayBounds.bottom -= 1;
393 if (!visibleDisplayBounds.intersect(bounds)) {
394 throw new PerformException.Builder()
395 .withActionDescription(mActionDescription
396 + " The handle is entirely out of the visible display frame of"
397 + "the TextView's window.")
398 .withViewDescription(HumanReadables.describe(view))
399 .build();
400 }
401 final float dragPointX = Math.max(Math.min(bounds.centerX(),
402 visibleDisplayBounds.right), visibleDisplayBounds.left);
403 final float verticalOffset = bounds.height() * 0.7f;
404 final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset,
405 visibleDisplayBounds.bottom), visibleDisplayBounds.top);
406 return new float[] {dragPointX, dragPointY};
407 }
408 }
409
410 /**
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900411 * A provider of the x, y coordinates of the handle that points the specified text index in a
412 * text view.
413 */
414 private static final class HandleCoordinates implements CoordinatesProvider {
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900415 // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS.
416 private final static float LINE_SLOP_MULTIPLIER = 0.6f;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900417 private final TextView mTextView;
418 private final Handle mHandleType;
419 private final int mIndex;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900420 private final boolean mPrimary;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900421 private final String mActionDescription;
422
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700423 public HandleCoordinates(TextView textView, Handle handleType, int index, boolean primary) {
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900424 mTextView = textView;
425 mHandleType = handleType;
426 mIndex = index;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900427 mPrimary = primary;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900428 mActionDescription = "Could not locate " + handleType.toString()
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900429 + " handle that points text index: " + index
430 + " (" + (primary ? "primary" : "secondary" ) + ")";
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900431 }
432
433 @Override
434 public float[] calculateCoordinates(View view) {
435 try {
436 return locateHandlePointsTextIndex(view);
437 } catch (StringIndexOutOfBoundsException e) {
438 throw new PerformException.Builder()
439 .withActionDescription(mActionDescription)
440 .withViewDescription(HumanReadables.describe(view))
441 .withCause(e)
442 .build();
443 }
444 }
445
446 private float[] locateHandlePointsTextIndex(View view) {
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900447 if (!(view instanceof HandleView)) {
448 throw new PerformException.Builder()
449 .withActionDescription(mActionDescription + " The view is not a HandleView")
450 .withViewDescription(HumanReadables.describe(view))
451 .build();
452 }
453 final HandleView handleView = (HandleView) view;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900454 final int currentOffset = mHandleType == Handle.SELECTION_START ?
455 mTextView.getSelectionStart() : mTextView.getSelectionEnd();
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900456
457 final Layout layout = mTextView.getLayout();
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900458
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900459 final int currentLine = layout.getLineForOffset(currentOffset);
460 final int targetLine = layout.getLineForOffset(mIndex);
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900461 final float currentX = handleView.getHorizontal(layout, currentOffset);
462 final float currentY = layout.getLineTop(currentLine);
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900463 final float[] currentCoordinates =
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700464 TextCoordinates.convertToScreenCoordinates(mTextView, currentX, currentY);
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900465 final float[] targetCoordinates =
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700466 (new TextCoordinates(mIndex, mPrimary)).calculateCoordinates(mTextView);
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900467 final Rect bounds = new Rect();
468 view.getBoundsOnScreen(bounds);
Keisuke Kuroyanagi5f71b5a2015-12-02 15:02:05 -0800469 final Rect visibleDisplayBounds = new Rect();
470 mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds);
471 visibleDisplayBounds.right -= 1;
472 visibleDisplayBounds.bottom -= 1;
473 if (!visibleDisplayBounds.intersect(bounds)) {
474 throw new PerformException.Builder()
475 .withActionDescription(mActionDescription
476 + " The handle is entirely out of the visible display frame of"
477 + "the TextView's window.")
478 .withViewDescription(HumanReadables.describe(view))
479 .build();
480 }
481 final float dragPointX = Math.max(Math.min(bounds.centerX(),
482 visibleDisplayBounds.right), visibleDisplayBounds.left);
483 final float diffX = dragPointX - currentCoordinates[0];
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900484 final float verticalOffset = bounds.height() * 0.7f;
Keisuke Kuroyanagi5f71b5a2015-12-02 15:02:05 -0800485 final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset,
486 visibleDisplayBounds.bottom), visibleDisplayBounds.top);
487 float diffY = dragPointY - currentCoordinates[1];
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900488 if (currentLine > targetLine) {
489 diffY -= mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER;
490 } else if (currentLine < targetLine) {
491 diffY += mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER;
492 }
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900493 return new float[] {targetCoordinates[0] + diffX, targetCoordinates[1] + diffY};
494 }
Keisuke Kuroyanagi88cabed2015-10-08 19:12:56 +0900495 }
496
497 /**
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100498 * A provider of the x, y coordinates of the text at the specified index in a text view.
499 */
500 private static final class TextCoordinates implements CoordinatesProvider {
501
502 private final int mIndex;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900503 private final boolean mPrimary;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100504 private final String mActionDescription;
505
506 public TextCoordinates(int index) {
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700507 this(index, true);
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900508 }
509
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700510 public TextCoordinates(int index, boolean primary) {
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100511 mIndex = index;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900512 mPrimary = primary;
513 mActionDescription = "Could not locate text at index: " + mIndex
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700514 + " (" + (primary ? "primary" : "secondary" ) + ")";
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100515 }
516
517 @Override
518 public float[] calculateCoordinates(View view) {
519 try {
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700520 return locateTextAtIndex((TextView) view, mIndex, mPrimary);
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100521 } catch (ClassCastException e) {
522 throw new PerformException.Builder()
523 .withActionDescription(mActionDescription)
524 .withViewDescription(HumanReadables.describe(view))
525 .withCause(e)
526 .build();
527 } catch (StringIndexOutOfBoundsException e) {
528 throw new PerformException.Builder()
529 .withActionDescription(mActionDescription)
530 .withViewDescription(HumanReadables.describe(view))
531 .withCause(e)
532 .build();
533 }
534 }
535
536 /**
537 * @throws StringIndexOutOfBoundsException
538 */
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700539 private float[] locateTextAtIndex(TextView textView, int index, boolean primary) {
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100540 if (index < 0 || index > textView.getText().length()) {
541 throw new StringIndexOutOfBoundsException(index);
542 }
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100543 final Layout layout = textView.getLayout();
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700544 final int line = layout.getLineForOffset(index);
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900545 return convertToScreenCoordinates(textView,
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700546 (primary ? layout.getPrimaryHorizontal(index)
547 : layout.getSecondaryHorizontal(index)),
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900548 layout.getLineTop(line));
549 }
550
Roozbeh Pournader7557a5a2017-04-11 18:34:42 -0700551 /**
552 * Convert TextView's local coordinates to on screen coordinates.
553 * @param textView the TextView
554 * @param x local horizontal coordinate
555 * @param y local vertical coordinate
556 * @return
557 */
558 public static float[] convertToScreenCoordinates(TextView textView, float x, float y) {
559 final int[] xy = new int[2];
560 textView.getLocationOnScreen(xy);
561 return new float[]{ x + textView.getTotalPaddingLeft() - textView.getScrollX() + xy[0],
562 y + textView.getTotalPaddingTop() - textView.getScrollY() + xy[1] };
563 }
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100564 }
565}