blob: 4cecb65f5fa365644cb87d1c037d400d1a0b8f3f [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;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +090020
21import org.hamcrest.core.Is;
22import org.hamcrest.core.IsInstanceOf;
23
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +090024import android.graphics.Rect;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010025import android.support.test.espresso.PerformException;
26import android.support.test.espresso.ViewAction;
27import android.support.test.espresso.action.CoordinatesProvider;
28import android.support.test.espresso.action.GeneralClickAction;
29import android.support.test.espresso.action.Press;
30import android.support.test.espresso.action.Tap;
31import android.support.test.espresso.util.HumanReadables;
32import android.text.Layout;
Keisuke Kuroyanagia0b3c062015-12-07 11:09:40 -080033import android.view.MotionEvent;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010034import android.view.View;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +090035import android.widget.Editor;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +090036import android.widget.Editor.HandleView;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010037import android.widget.TextView;
38
39/**
40 * A collection of actions on a {@link android.widget.TextView}.
41 */
42public final class TextViewActions {
43
44 private TextViewActions() {}
45
46 /**
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +010047 * Returns an action that clicks on text at an index on the TextView.<br>
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010048 * <br>
49 * View constraints:
50 * <ul>
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +010051 * <li>must be a TextView displayed on screen
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010052 * <ul>
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +010053 *
54 * @param index The index of the TextView's text to click on.
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010055 */
56 public static ViewAction clickOnTextAtIndex(int index) {
57 return actionWithAssertions(
58 new GeneralClickAction(Tap.SINGLE, new TextCoordinates(index), Press.FINGER));
59 }
60
61 /**
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -080062 * Returns an action that clicks by mouse on text at an index on the TextView.<br>
63 * <br>
64 * View constraints:
65 * <ul>
66 * <li>must be a TextView displayed on screen
67 * <ul>
68 *
69 * @param index The index of the TextView's text to click on.
70 */
71 public static ViewAction mouseClickOnTextAtIndex(int index) {
Keisuke Kuroyanagia0b3c062015-12-07 11:09:40 -080072 return mouseClickOnTextAtIndex(index, MotionEvent.BUTTON_PRIMARY);
73 }
74
75 /**
76 * Returns an action that clicks by mouse on text at an index on the TextView.<br>
77 * <br>
78 * View constraints:
79 * <ul>
80 * <li>must be a TextView displayed on screen
81 * <ul>
82 *
83 * @param index The index of the TextView's text to click on.
84 * @param button the mouse button to use.
85 */
86 public static ViewAction mouseClickOnTextAtIndex(int index,
87 @MouseUiController.MouseButton int button) {
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -080088 return actionWithAssertions(
Keisuke Kuroyanagia0b3c062015-12-07 11:09:40 -080089 new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), button));
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -080090 }
91
92 /**
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +010093 * Returns an action that double-clicks on text at an index on the TextView.<br>
94 * <br>
95 * View constraints:
96 * <ul>
97 * <li>must be a TextView displayed on screen
98 * <ul>
99 *
100 * @param index The index of the TextView's text to double-click on.
101 */
102 public static ViewAction doubleClickOnTextAtIndex(int index) {
103 return actionWithAssertions(
104 new GeneralClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.FINGER));
105 }
106
107 /**
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800108 * Returns an action that double-clicks by mouse on text at an index on the TextView.<br>
109 * <br>
110 * View constraints:
111 * <ul>
112 * <li>must be a TextView displayed on screen
113 * <ul>
114 *
115 * @param index The index of the TextView's text to double-click on.
116 */
117 public static ViewAction mouseDoubleClickOnTextAtIndex(int index) {
118 return actionWithAssertions(
Keisuke Kuroyanagi46faad62015-12-06 10:03:23 -0800119 new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index)));
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800120 }
121
122 /**
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100123 * Returns an action that long presses on text at an index on the TextView.<br>
124 * <br>
125 * View constraints:
126 * <ul>
127 * <li>must be a TextView displayed on screen
128 * <ul>
129 *
130 * @param index The index of the TextView's text to long press on.
131 */
132 public static ViewAction longPressOnTextAtIndex(int index) {
133 return actionWithAssertions(
134 new GeneralClickAction(Tap.LONG, new TextCoordinates(index), Press.FINGER));
135 }
136
137 /**
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800138 * Returns an action that long click by mouse on text at an index on the TextView.<br>
139 * <br>
140 * View constraints:
141 * <ul>
142 * <li>must be a TextView displayed on screen
143 * <ul>
144 *
145 * @param index The index of the TextView's text to long click on.
146 */
147 public static ViewAction mouseLongClickOnTextAtIndex(int index) {
148 return actionWithAssertions(
Keisuke Kuroyanagi46faad62015-12-06 10:03:23 -0800149 new MouseClickAction(Tap.LONG, new TextCoordinates(index)));
150 }
151
152 /**
153 * Returns an action that triple-clicks by mouse on text at an index on the TextView.<br>
154 * <br>
155 * View constraints:
156 * <ul>
157 * <li>must be a TextView displayed on screen
158 * <ul>
159 *
160 * @param index The index of the TextView's text to triple-click on.
161 */
162 public static ViewAction mouseTripleClickOnTextAtIndex(int index) {
163 return actionWithAssertions(
164 new MouseClickAction(MouseClickAction.CLICK.TRIPLE, new TextCoordinates(index)));
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800165 }
166
167 /**
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100168 * Returns an action that long presses then drags on text from startIndex to endIndex on the
169 * TextView.<br>
170 * <br>
171 * View constraints:
172 * <ul>
173 * <li>must be a TextView displayed on screen
174 * <ul>
175 *
176 * @param startIndex The index of the TextView's text to start a drag from
177 * @param endIndex The index of the TextView's text to end the drag at
178 */
179 public static ViewAction longPressAndDragOnText(int startIndex, int endIndex) {
180 return actionWithAssertions(
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900181 new DragAction(
182 DragAction.Drag.LONG_PRESS,
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100183 new TextCoordinates(startIndex),
184 new TextCoordinates(endIndex),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900185 Press.FINGER,
186 TextView.class));
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100187 }
188
189 /**
190 * Returns an action that double taps then drags on text from startIndex to endIndex on the
191 * TextView.<br>
192 * <br>
193 * View constraints:
194 * <ul>
195 * <li>must be a TextView displayed on screen
196 * <ul>
197 *
198 * @param startIndex The index of the TextView's text to start a drag from
199 * @param endIndex The index of the TextView's text to end the drag at
200 */
201 public static ViewAction doubleTapAndDragOnText(int startIndex, int endIndex) {
202 return actionWithAssertions(
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900203 new DragAction(
204 DragAction.Drag.DOUBLE_TAP,
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100205 new TextCoordinates(startIndex),
206 new TextCoordinates(endIndex),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900207 Press.FINGER,
208 TextView.class));
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100209 }
210
211 /**
Keisuke Kuroyanagi88cabed2015-10-08 19:12:56 +0900212 * Returns an action that click then drags by mouse on text from startIndex to endIndex on the
213 * TextView.<br>
214 * <br>
215 * View constraints:
216 * <ul>
217 * <li>must be a TextView displayed on screen
218 * <ul>
219 *
220 * @param startIndex The index of the TextView's text to start a drag from
221 * @param endIndex The index of the TextView's text to end the drag at
222 */
223 public static ViewAction mouseDragOnText(int startIndex, int endIndex) {
224 return actionWithAssertions(
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900225 new DragAction(
226 DragAction.Drag.MOUSE_DOWN,
Keisuke Kuroyanagi88cabed2015-10-08 19:12:56 +0900227 new TextCoordinates(startIndex),
228 new TextCoordinates(endIndex),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900229 Press.PINPOINT,
230 TextView.class));
231 }
232
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800233 /**
234 * Returns an action that double click then drags by mouse on text from startIndex to endIndex
235 * on the TextView.<br>
236 * <br>
237 * View constraints:
238 * <ul>
239 * <li>must be a TextView displayed on screen
240 * <ul>
241 *
242 * @param startIndex The index of the TextView's text to start a drag from
243 * @param endIndex The index of the TextView's text to end the drag at
244 */
245 public static ViewAction mouseDoubleClickAndDragOnText(int startIndex, int endIndex) {
246 return actionWithAssertions(
247 new DragAction(
248 DragAction.Drag.MOUSE_DOUBLE_CLICK,
249 new TextCoordinates(startIndex),
250 new TextCoordinates(endIndex),
251 Press.PINPOINT,
252 TextView.class));
253 }
254
255 /**
256 * Returns an action that long click then drags by mouse on text from startIndex to endIndex
257 * on the TextView.<br>
258 * <br>
259 * View constraints:
260 * <ul>
261 * <li>must be a TextView displayed on screen
262 * <ul>
263 *
264 * @param startIndex The index of the TextView's text to start a drag from
265 * @param endIndex The index of the TextView's text to end the drag at
266 */
267 public static ViewAction mouseLongClickAndDragOnText(int startIndex, int endIndex) {
268 return actionWithAssertions(
269 new DragAction(
270 DragAction.Drag.MOUSE_LONG_CLICK,
271 new TextCoordinates(startIndex),
272 new TextCoordinates(endIndex),
273 Press.PINPOINT,
274 TextView.class));
275 }
276
Keisuke Kuroyanagi46faad62015-12-06 10:03:23 -0800277 /**
278 * Returns an action that triple click then drags by mouse on text from startIndex to endIndex
279 * on the TextView.<br>
280 * <br>
281 * View constraints:
282 * <ul>
283 * <li>must be a TextView displayed on screen
284 * <ul>
285 *
286 * @param startIndex The index of the TextView's text to start a drag from
287 * @param endIndex The index of the TextView's text to end the drag at
288 */
289 public static ViewAction mouseTripleClickAndDragOnText(int startIndex, int endIndex) {
290 return actionWithAssertions(
291 new DragAction(
292 DragAction.Drag.MOUSE_TRIPLE_CLICK,
293 new TextCoordinates(startIndex),
294 new TextCoordinates(endIndex),
295 Press.PINPOINT,
296 TextView.class));
297 }
298
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900299 public enum Handle {
300 SELECTION_START,
301 SELECTION_END,
302 INSERTION
303 };
304
305 /**
306 * Returns an action that tap then drags on the handle from the current position to endIndex on
307 * the TextView.<br>
308 * <br>
309 * View constraints:
310 * <ul>
311 * <li>must be a TextView's drag-handle displayed on screen
312 * <ul>
313 *
314 * @param textView TextView the handle is on
315 * @param handleType Type of the handle
316 * @param endIndex The index of the TextView's text to end the drag at
317 */
318 public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex) {
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900319 return dragHandle(textView, handleType, endIndex, true);
320 }
321
322 /**
323 * Returns an action that tap then drags on the handle from the current position to endIndex on
324 * the TextView.<br>
325 * <br>
326 * View constraints:
327 * <ul>
328 * <li>must be a TextView's drag-handle displayed on screen
329 * <ul>
330 *
331 * @param textView TextView the handle is on
332 * @param handleType Type of the handle
333 * @param endIndex The index of the TextView's text to end the drag at
334 * @param primary whether to use primary direction to get coordinate form index when endIndex is
335 * at a direction boundary.
336 */
337 public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex,
338 boolean primary) {
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900339 return actionWithAssertions(
340 new DragAction(
341 DragAction.Drag.TAP,
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900342 new CurrentHandleCoordinates(textView),
343 new HandleCoordinates(textView, handleType, endIndex, primary),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900344 Press.FINGER,
345 Editor.HandleView.class));
346 }
347
348 /**
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900349 * A provider of the x, y coordinates of the handle dragging point.
350 */
351 private static final class CurrentHandleCoordinates implements CoordinatesProvider {
352 // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS.
353 private final TextView mTextView;
354 private final String mActionDescription;
355
356
357 public CurrentHandleCoordinates(TextView textView) {
358 mTextView = textView;
359 mActionDescription = "Could not locate handle.";
360 }
361
362 @Override
363 public float[] calculateCoordinates(View view) {
364 try {
365 return locateHandle(view);
366 } catch (StringIndexOutOfBoundsException e) {
367 throw new PerformException.Builder()
368 .withActionDescription(mActionDescription)
369 .withViewDescription(HumanReadables.describe(view))
370 .withCause(e)
371 .build();
372 }
373 }
374
375 private float[] locateHandle(View view) {
376 final Rect bounds = new Rect();
377 view.getBoundsOnScreen(bounds);
378 final Rect visibleDisplayBounds = new Rect();
379 mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds);
380 visibleDisplayBounds.right -= 1;
381 visibleDisplayBounds.bottom -= 1;
382 if (!visibleDisplayBounds.intersect(bounds)) {
383 throw new PerformException.Builder()
384 .withActionDescription(mActionDescription
385 + " The handle is entirely out of the visible display frame of"
386 + "the TextView's window.")
387 .withViewDescription(HumanReadables.describe(view))
388 .build();
389 }
390 final float dragPointX = Math.max(Math.min(bounds.centerX(),
391 visibleDisplayBounds.right), visibleDisplayBounds.left);
392 final float verticalOffset = bounds.height() * 0.7f;
393 final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset,
394 visibleDisplayBounds.bottom), visibleDisplayBounds.top);
395 return new float[] {dragPointX, dragPointY};
396 }
397 }
398
399 /**
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900400 * A provider of the x, y coordinates of the handle that points the specified text index in a
401 * text view.
402 */
403 private static final class HandleCoordinates implements CoordinatesProvider {
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900404 // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS.
405 private final static float LINE_SLOP_MULTIPLIER = 0.6f;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900406 private final TextView mTextView;
407 private final Handle mHandleType;
408 private final int mIndex;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900409 private final boolean mPrimary;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900410 private final String mActionDescription;
411
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900412 public HandleCoordinates(TextView textView, Handle handleType, int index, boolean primary) {
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900413 mTextView = textView;
414 mHandleType = handleType;
415 mIndex = index;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900416 mPrimary = primary;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900417 mActionDescription = "Could not locate " + handleType.toString()
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900418 + " handle that points text index: " + index
419 + " (" + (primary ? "primary" : "secondary" ) + ")";
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900420 }
421
422 @Override
423 public float[] calculateCoordinates(View view) {
424 try {
425 return locateHandlePointsTextIndex(view);
426 } catch (StringIndexOutOfBoundsException e) {
427 throw new PerformException.Builder()
428 .withActionDescription(mActionDescription)
429 .withViewDescription(HumanReadables.describe(view))
430 .withCause(e)
431 .build();
432 }
433 }
434
435 private float[] locateHandlePointsTextIndex(View view) {
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900436 if (!(view instanceof HandleView)) {
437 throw new PerformException.Builder()
438 .withActionDescription(mActionDescription + " The view is not a HandleView")
439 .withViewDescription(HumanReadables.describe(view))
440 .build();
441 }
442 final HandleView handleView = (HandleView) view;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900443 final int currentOffset = mHandleType == Handle.SELECTION_START ?
444 mTextView.getSelectionStart() : mTextView.getSelectionEnd();
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900445
446 final Layout layout = mTextView.getLayout();
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900447
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900448 final int currentLine = layout.getLineForOffset(currentOffset);
449 final int targetLine = layout.getLineForOffset(mIndex);
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900450 final float currentX = handleView.getHorizontal(layout, currentOffset);
451 final float currentY = layout.getLineTop(currentLine);
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900452 final float[] currentCoordinates =
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900453 TextCoordinates.convertToScreenCoordinates(mTextView, currentX, currentY);
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900454 final float[] targetCoordinates =
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900455 (new TextCoordinates(mIndex, mPrimary)).calculateCoordinates(mTextView);
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900456 final Rect bounds = new Rect();
457 view.getBoundsOnScreen(bounds);
Keisuke Kuroyanagi5f71b5a2015-12-02 15:02:05 -0800458 final Rect visibleDisplayBounds = new Rect();
459 mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds);
460 visibleDisplayBounds.right -= 1;
461 visibleDisplayBounds.bottom -= 1;
462 if (!visibleDisplayBounds.intersect(bounds)) {
463 throw new PerformException.Builder()
464 .withActionDescription(mActionDescription
465 + " The handle is entirely out of the visible display frame of"
466 + "the TextView's window.")
467 .withViewDescription(HumanReadables.describe(view))
468 .build();
469 }
470 final float dragPointX = Math.max(Math.min(bounds.centerX(),
471 visibleDisplayBounds.right), visibleDisplayBounds.left);
472 final float diffX = dragPointX - currentCoordinates[0];
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900473 final float verticalOffset = bounds.height() * 0.7f;
Keisuke Kuroyanagi5f71b5a2015-12-02 15:02:05 -0800474 final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset,
475 visibleDisplayBounds.bottom), visibleDisplayBounds.top);
476 float diffY = dragPointY - currentCoordinates[1];
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900477 if (currentLine > targetLine) {
478 diffY -= mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER;
479 } else if (currentLine < targetLine) {
480 diffY += mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER;
481 }
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900482 return new float[] {targetCoordinates[0] + diffX, targetCoordinates[1] + diffY};
483 }
Keisuke Kuroyanagi88cabed2015-10-08 19:12:56 +0900484 }
485
486 /**
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100487 * A provider of the x, y coordinates of the text at the specified index in a text view.
488 */
489 private static final class TextCoordinates implements CoordinatesProvider {
490
491 private final int mIndex;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900492 private final boolean mPrimary;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100493 private final String mActionDescription;
494
495 public TextCoordinates(int index) {
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900496 this(index, true);
497 }
498
499 public TextCoordinates(int index, boolean primary) {
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100500 mIndex = index;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900501 mPrimary = primary;
502 mActionDescription = "Could not locate text at index: " + mIndex
503 + " (" + (primary ? "primary" : "secondary" ) + ")";
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100504 }
505
506 @Override
507 public float[] calculateCoordinates(View view) {
508 try {
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900509 return locateTextAtIndex((TextView) view, mIndex, mPrimary);
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100510 } catch (ClassCastException e) {
511 throw new PerformException.Builder()
512 .withActionDescription(mActionDescription)
513 .withViewDescription(HumanReadables.describe(view))
514 .withCause(e)
515 .build();
516 } catch (StringIndexOutOfBoundsException e) {
517 throw new PerformException.Builder()
518 .withActionDescription(mActionDescription)
519 .withViewDescription(HumanReadables.describe(view))
520 .withCause(e)
521 .build();
522 }
523 }
524
525 /**
526 * @throws StringIndexOutOfBoundsException
527 */
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900528 private float[] locateTextAtIndex(TextView textView, int index, boolean primary) {
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100529 if (index < 0 || index > textView.getText().length()) {
530 throw new StringIndexOutOfBoundsException(index);
531 }
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100532 final Layout layout = textView.getLayout();
533 final int line = layout.getLineForOffset(index);
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900534 return convertToScreenCoordinates(textView,
535 (primary ? layout.getPrimaryHorizontal(index)
536 : layout.getSecondaryHorizontal(index)),
537 layout.getLineTop(line));
538 }
539
540 /**
541 * Convert TextView's local coordinates to on screen coordinates.
542 * @param textView the TextView
543 * @param x local horizontal coordinate
544 * @param y local vertical coordinate
545 * @return
546 */
547 public static float[] convertToScreenCoordinates(TextView textView, float x, float y) {
548 final int[] xy = new int[2];
549 textView.getLocationOnScreen(xy);
550 return new float[]{ x + textView.getTotalPaddingLeft() - textView.getScrollX() + xy[0],
551 y + textView.getTotalPaddingTop() - textView.getScrollY() + xy[1] };
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100552 }
553 }
554}