blob: 335d021efd14b1694e226117a2ef1a99364ff7fe [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 Kuroyanagida79ee62015-11-25 16:15:15 +090020import android.graphics.Rect;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010021import android.support.test.espresso.PerformException;
22import android.support.test.espresso.ViewAction;
23import android.support.test.espresso.action.CoordinatesProvider;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010024import android.support.test.espresso.action.Press;
25import android.support.test.espresso.action.Tap;
26import android.support.test.espresso.util.HumanReadables;
27import android.text.Layout;
Keisuke Kuroyanagia0b3c062015-12-07 11:09:40 -080028import android.view.MotionEvent;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010029import android.view.View;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +090030import android.widget.Editor;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +090031import android.widget.Editor.HandleView;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010032import android.widget.TextView;
33
34/**
35 * A collection of actions on a {@link android.widget.TextView}.
36 */
37public final class TextViewActions {
38
39 private TextViewActions() {}
40
41 /**
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +010042 * Returns an action that clicks on text at an index on the TextView.<br>
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010043 * <br>
44 * View constraints:
45 * <ul>
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +010046 * <li>must be a TextView displayed on screen
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010047 * <ul>
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +010048 *
49 * @param index The index of the TextView's text to click on.
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010050 */
51 public static ViewAction clickOnTextAtIndex(int index) {
52 return actionWithAssertions(
Keisuke Kuroyanagi5f318b62016-04-01 19:16:31 +090053 new ViewClickAction(Tap.SINGLE, new TextCoordinates(index), Press.FINGER));
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +010054 }
55
56 /**
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -080057 * Returns an action that clicks by mouse on text at an index on the TextView.<br>
58 * <br>
59 * View constraints:
60 * <ul>
61 * <li>must be a TextView displayed on screen
62 * <ul>
63 *
64 * @param index The index of the TextView's text to click on.
65 */
66 public static ViewAction mouseClickOnTextAtIndex(int index) {
Keisuke Kuroyanagia0b3c062015-12-07 11:09:40 -080067 return mouseClickOnTextAtIndex(index, MotionEvent.BUTTON_PRIMARY);
68 }
69
70 /**
71 * Returns an action that clicks by mouse on text at an index on the TextView.<br>
72 * <br>
73 * View constraints:
74 * <ul>
75 * <li>must be a TextView displayed on screen
76 * <ul>
77 *
78 * @param index The index of the TextView's text to click on.
79 * @param button the mouse button to use.
80 */
81 public static ViewAction mouseClickOnTextAtIndex(int index,
82 @MouseUiController.MouseButton int button) {
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -080083 return actionWithAssertions(
Keisuke Kuroyanagia0b3c062015-12-07 11:09:40 -080084 new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), button));
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -080085 }
86
87 /**
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +010088 * Returns an action that double-clicks 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 double-click on.
96 */
97 public static ViewAction doubleClickOnTextAtIndex(int index) {
98 return actionWithAssertions(
Keisuke Kuroyanagi5f318b62016-04-01 19:16:31 +090099 new ViewClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.FINGER));
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100100 }
101
102 /**
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800103 * Returns an action that double-clicks by mouse on text at an index on the TextView.<br>
104 * <br>
105 * View constraints:
106 * <ul>
107 * <li>must be a TextView displayed on screen
108 * <ul>
109 *
110 * @param index The index of the TextView's text to double-click on.
111 */
112 public static ViewAction mouseDoubleClickOnTextAtIndex(int index) {
113 return actionWithAssertions(
Keisuke Kuroyanagi46faad62015-12-06 10:03:23 -0800114 new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index)));
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800115 }
116
117 /**
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100118 * Returns an action that long presses on text at an index on the TextView.<br>
119 * <br>
120 * View constraints:
121 * <ul>
122 * <li>must be a TextView displayed on screen
123 * <ul>
124 *
125 * @param index The index of the TextView's text to long press on.
126 */
127 public static ViewAction longPressOnTextAtIndex(int index) {
128 return actionWithAssertions(
Keisuke Kuroyanagi5f318b62016-04-01 19:16:31 +0900129 new ViewClickAction(Tap.LONG, new TextCoordinates(index), Press.FINGER));
Abodunrinwa Toki8a5e1ae2015-09-28 21:59:04 +0100130 }
131
132 /**
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800133 * Returns an action that long click by mouse on text at an index on the TextView.<br>
134 * <br>
135 * View constraints:
136 * <ul>
137 * <li>must be a TextView displayed on screen
138 * <ul>
139 *
140 * @param index The index of the TextView's text to long click on.
141 */
142 public static ViewAction mouseLongClickOnTextAtIndex(int index) {
143 return actionWithAssertions(
Keisuke Kuroyanagi46faad62015-12-06 10:03:23 -0800144 new MouseClickAction(Tap.LONG, new TextCoordinates(index)));
145 }
146
147 /**
148 * Returns an action that triple-clicks by mouse on text at an index on the TextView.<br>
149 * <br>
150 * View constraints:
151 * <ul>
152 * <li>must be a TextView displayed on screen
153 * <ul>
154 *
155 * @param index The index of the TextView's text to triple-click on.
156 */
157 public static ViewAction mouseTripleClickOnTextAtIndex(int index) {
158 return actionWithAssertions(
159 new MouseClickAction(MouseClickAction.CLICK.TRIPLE, new TextCoordinates(index)));
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800160 }
161
162 /**
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100163 * Returns an action that long presses then drags on text from startIndex to endIndex on the
164 * TextView.<br>
165 * <br>
166 * View constraints:
167 * <ul>
168 * <li>must be a TextView displayed on screen
169 * <ul>
170 *
171 * @param startIndex The index of the TextView's text to start a drag from
172 * @param endIndex The index of the TextView's text to end the drag at
173 */
174 public static ViewAction longPressAndDragOnText(int startIndex, int endIndex) {
175 return actionWithAssertions(
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900176 new DragAction(
177 DragAction.Drag.LONG_PRESS,
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100178 new TextCoordinates(startIndex),
179 new TextCoordinates(endIndex),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900180 Press.FINGER,
181 TextView.class));
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100182 }
183
184 /**
185 * Returns an action that double taps then drags on text from startIndex to endIndex on the
186 * TextView.<br>
187 * <br>
188 * View constraints:
189 * <ul>
190 * <li>must be a TextView displayed on screen
191 * <ul>
192 *
193 * @param startIndex The index of the TextView's text to start a drag from
194 * @param endIndex The index of the TextView's text to end the drag at
195 */
196 public static ViewAction doubleTapAndDragOnText(int startIndex, int endIndex) {
197 return actionWithAssertions(
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900198 new DragAction(
199 DragAction.Drag.DOUBLE_TAP,
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100200 new TextCoordinates(startIndex),
201 new TextCoordinates(endIndex),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900202 Press.FINGER,
203 TextView.class));
Abodunrinwa Tokica4aaf32015-09-22 20:31:21 +0100204 }
205
206 /**
Keisuke Kuroyanagi88cabed2015-10-08 19:12:56 +0900207 * Returns an action that click then drags by mouse on text from startIndex to endIndex on the
208 * TextView.<br>
209 * <br>
210 * View constraints:
211 * <ul>
212 * <li>must be a TextView displayed on screen
213 * <ul>
214 *
215 * @param startIndex The index of the TextView's text to start a drag from
216 * @param endIndex The index of the TextView's text to end the drag at
217 */
218 public static ViewAction mouseDragOnText(int startIndex, int endIndex) {
219 return actionWithAssertions(
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900220 new DragAction(
221 DragAction.Drag.MOUSE_DOWN,
Keisuke Kuroyanagi88cabed2015-10-08 19:12:56 +0900222 new TextCoordinates(startIndex),
223 new TextCoordinates(endIndex),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900224 Press.PINPOINT,
225 TextView.class));
226 }
227
Keisuke Kuroyanagi2ff41d42015-12-01 16:58:53 -0800228 /**
229 * Returns an action that double click then drags by mouse on text from startIndex to endIndex
230 * on the TextView.<br>
231 * <br>
232 * View constraints:
233 * <ul>
234 * <li>must be a TextView displayed on screen
235 * <ul>
236 *
237 * @param startIndex The index of the TextView's text to start a drag from
238 * @param endIndex The index of the TextView's text to end the drag at
239 */
240 public static ViewAction mouseDoubleClickAndDragOnText(int startIndex, int endIndex) {
241 return actionWithAssertions(
242 new DragAction(
243 DragAction.Drag.MOUSE_DOUBLE_CLICK,
244 new TextCoordinates(startIndex),
245 new TextCoordinates(endIndex),
246 Press.PINPOINT,
247 TextView.class));
248 }
249
250 /**
251 * Returns an action that long click then drags by mouse on text from startIndex to endIndex
252 * on the TextView.<br>
253 * <br>
254 * View constraints:
255 * <ul>
256 * <li>must be a TextView displayed on screen
257 * <ul>
258 *
259 * @param startIndex The index of the TextView's text to start a drag from
260 * @param endIndex The index of the TextView's text to end the drag at
261 */
262 public static ViewAction mouseLongClickAndDragOnText(int startIndex, int endIndex) {
263 return actionWithAssertions(
264 new DragAction(
265 DragAction.Drag.MOUSE_LONG_CLICK,
266 new TextCoordinates(startIndex),
267 new TextCoordinates(endIndex),
268 Press.PINPOINT,
269 TextView.class));
270 }
271
Keisuke Kuroyanagi46faad62015-12-06 10:03:23 -0800272 /**
273 * Returns an action that triple click then drags by mouse on text from startIndex to endIndex
274 * on the TextView.<br>
275 * <br>
276 * View constraints:
277 * <ul>
278 * <li>must be a TextView displayed on screen
279 * <ul>
280 *
281 * @param startIndex The index of the TextView's text to start a drag from
282 * @param endIndex The index of the TextView's text to end the drag at
283 */
284 public static ViewAction mouseTripleClickAndDragOnText(int startIndex, int endIndex) {
285 return actionWithAssertions(
286 new DragAction(
287 DragAction.Drag.MOUSE_TRIPLE_CLICK,
288 new TextCoordinates(startIndex),
289 new TextCoordinates(endIndex),
290 Press.PINPOINT,
291 TextView.class));
292 }
293
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900294 public enum Handle {
295 SELECTION_START,
296 SELECTION_END,
297 INSERTION
298 };
299
300 /**
301 * Returns an action that tap then drags on the handle from the current position to endIndex on
302 * the TextView.<br>
303 * <br>
304 * View constraints:
305 * <ul>
306 * <li>must be a TextView's drag-handle displayed on screen
307 * <ul>
308 *
309 * @param textView TextView the handle is on
310 * @param handleType Type of the handle
311 * @param endIndex The index of the TextView's text to end the drag at
312 */
313 public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex) {
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900314 return dragHandle(textView, handleType, endIndex, true);
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 * @param primary whether to use primary direction to get coordinate form index when endIndex is
330 * at a direction boundary.
331 */
332 public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex,
333 boolean primary) {
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900334 return actionWithAssertions(
335 new DragAction(
336 DragAction.Drag.TAP,
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900337 new CurrentHandleCoordinates(textView),
338 new HandleCoordinates(textView, handleType, endIndex, primary),
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900339 Press.FINGER,
340 Editor.HandleView.class));
341 }
342
343 /**
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900344 * A provider of the x, y coordinates of the handle dragging point.
345 */
346 private static final class CurrentHandleCoordinates implements CoordinatesProvider {
347 // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS.
348 private final TextView mTextView;
349 private final String mActionDescription;
350
351
352 public CurrentHandleCoordinates(TextView textView) {
353 mTextView = textView;
354 mActionDescription = "Could not locate handle.";
355 }
356
357 @Override
358 public float[] calculateCoordinates(View view) {
359 try {
360 return locateHandle(view);
361 } catch (StringIndexOutOfBoundsException e) {
362 throw new PerformException.Builder()
363 .withActionDescription(mActionDescription)
364 .withViewDescription(HumanReadables.describe(view))
365 .withCause(e)
366 .build();
367 }
368 }
369
370 private float[] locateHandle(View view) {
371 final Rect bounds = new Rect();
372 view.getBoundsOnScreen(bounds);
373 final Rect visibleDisplayBounds = new Rect();
374 mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds);
375 visibleDisplayBounds.right -= 1;
376 visibleDisplayBounds.bottom -= 1;
377 if (!visibleDisplayBounds.intersect(bounds)) {
378 throw new PerformException.Builder()
379 .withActionDescription(mActionDescription
380 + " The handle is entirely out of the visible display frame of"
381 + "the TextView's window.")
382 .withViewDescription(HumanReadables.describe(view))
383 .build();
384 }
385 final float dragPointX = Math.max(Math.min(bounds.centerX(),
386 visibleDisplayBounds.right), visibleDisplayBounds.left);
387 final float verticalOffset = bounds.height() * 0.7f;
388 final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset,
389 visibleDisplayBounds.bottom), visibleDisplayBounds.top);
390 return new float[] {dragPointX, dragPointY};
391 }
392 }
393
394 /**
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900395 * A provider of the x, y coordinates of the handle that points the specified text index in a
396 * text view.
397 */
398 private static final class HandleCoordinates implements CoordinatesProvider {
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900399 // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS.
400 private final static float LINE_SLOP_MULTIPLIER = 0.6f;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900401 private final TextView mTextView;
402 private final Handle mHandleType;
403 private final int mIndex;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900404 private final boolean mPrimary;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900405 private final String mActionDescription;
406
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900407 public HandleCoordinates(TextView textView, Handle handleType, int index, boolean primary) {
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900408 mTextView = textView;
409 mHandleType = handleType;
410 mIndex = index;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900411 mPrimary = primary;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900412 mActionDescription = "Could not locate " + handleType.toString()
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900413 + " handle that points text index: " + index
414 + " (" + (primary ? "primary" : "secondary" ) + ")";
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900415 }
416
417 @Override
418 public float[] calculateCoordinates(View view) {
419 try {
420 return locateHandlePointsTextIndex(view);
421 } catch (StringIndexOutOfBoundsException e) {
422 throw new PerformException.Builder()
423 .withActionDescription(mActionDescription)
424 .withViewDescription(HumanReadables.describe(view))
425 .withCause(e)
426 .build();
427 }
428 }
429
430 private float[] locateHandlePointsTextIndex(View view) {
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900431 if (!(view instanceof HandleView)) {
432 throw new PerformException.Builder()
433 .withActionDescription(mActionDescription + " The view is not a HandleView")
434 .withViewDescription(HumanReadables.describe(view))
435 .build();
436 }
437 final HandleView handleView = (HandleView) view;
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900438 final int currentOffset = mHandleType == Handle.SELECTION_START ?
439 mTextView.getSelectionStart() : mTextView.getSelectionEnd();
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900440
441 final Layout layout = mTextView.getLayout();
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900442
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900443 final int currentLine = layout.getLineForOffset(currentOffset);
444 final int targetLine = layout.getLineForOffset(mIndex);
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900445 final float currentX = handleView.getHorizontal(layout, currentOffset);
446 final float currentY = layout.getLineTop(currentLine);
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900447 final float[] currentCoordinates =
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900448 TextCoordinates.convertToScreenCoordinates(mTextView, currentX, currentY);
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900449 final float[] targetCoordinates =
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900450 (new TextCoordinates(mIndex, mPrimary)).calculateCoordinates(mTextView);
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900451 final Rect bounds = new Rect();
452 view.getBoundsOnScreen(bounds);
Keisuke Kuroyanagi5f71b5a2015-12-02 15:02:05 -0800453 final Rect visibleDisplayBounds = new Rect();
454 mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds);
455 visibleDisplayBounds.right -= 1;
456 visibleDisplayBounds.bottom -= 1;
457 if (!visibleDisplayBounds.intersect(bounds)) {
458 throw new PerformException.Builder()
459 .withActionDescription(mActionDescription
460 + " The handle is entirely out of the visible display frame of"
461 + "the TextView's window.")
462 .withViewDescription(HumanReadables.describe(view))
463 .build();
464 }
465 final float dragPointX = Math.max(Math.min(bounds.centerX(),
466 visibleDisplayBounds.right), visibleDisplayBounds.left);
467 final float diffX = dragPointX - currentCoordinates[0];
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900468 final float verticalOffset = bounds.height() * 0.7f;
Keisuke Kuroyanagi5f71b5a2015-12-02 15:02:05 -0800469 final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset,
470 visibleDisplayBounds.bottom), visibleDisplayBounds.top);
471 float diffY = dragPointY - currentCoordinates[1];
Keisuke Kuroyanagic24c2bb2015-11-24 18:48:28 +0900472 if (currentLine > targetLine) {
473 diffY -= mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER;
474 } else if (currentLine < targetLine) {
475 diffY += mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER;
476 }
Keisuke Kuroyanagida79ee62015-11-25 16:15:15 +0900477 return new float[] {targetCoordinates[0] + diffX, targetCoordinates[1] + diffY};
478 }
Keisuke Kuroyanagi88cabed2015-10-08 19:12:56 +0900479 }
480
481 /**
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100482 * A provider of the x, y coordinates of the text at the specified index in a text view.
483 */
484 private static final class TextCoordinates implements CoordinatesProvider {
485
486 private final int mIndex;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900487 private final boolean mPrimary;
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100488 private final String mActionDescription;
489
490 public TextCoordinates(int index) {
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900491 this(index, true);
492 }
493
494 public TextCoordinates(int index, boolean primary) {
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100495 mIndex = index;
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900496 mPrimary = primary;
497 mActionDescription = "Could not locate text at index: " + mIndex
498 + " (" + (primary ? "primary" : "secondary" ) + ")";
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100499 }
500
501 @Override
502 public float[] calculateCoordinates(View view) {
503 try {
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900504 return locateTextAtIndex((TextView) view, mIndex, mPrimary);
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100505 } catch (ClassCastException e) {
506 throw new PerformException.Builder()
507 .withActionDescription(mActionDescription)
508 .withViewDescription(HumanReadables.describe(view))
509 .withCause(e)
510 .build();
511 } catch (StringIndexOutOfBoundsException e) {
512 throw new PerformException.Builder()
513 .withActionDescription(mActionDescription)
514 .withViewDescription(HumanReadables.describe(view))
515 .withCause(e)
516 .build();
517 }
518 }
519
520 /**
521 * @throws StringIndexOutOfBoundsException
522 */
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900523 private float[] locateTextAtIndex(TextView textView, int index, boolean primary) {
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100524 if (index < 0 || index > textView.getText().length()) {
525 throw new StringIndexOutOfBoundsException(index);
526 }
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100527 final Layout layout = textView.getLayout();
528 final int line = layout.getLineForOffset(index);
Keisuke Kuroyanagif0bb87b72016-02-08 23:52:55 +0900529 return convertToScreenCoordinates(textView,
530 (primary ? layout.getPrimaryHorizontal(index)
531 : layout.getSecondaryHorizontal(index)),
532 layout.getLineTop(line));
533 }
534
535 /**
536 * Convert TextView's local coordinates to on screen coordinates.
537 * @param textView the TextView
538 * @param x local horizontal coordinate
539 * @param y local vertical coordinate
540 * @return
541 */
542 public static float[] convertToScreenCoordinates(TextView textView, float x, float y) {
543 final int[] xy = new int[2];
544 textView.getLocationOnScreen(xy);
545 return new float[]{ x + textView.getTotalPaddingLeft() - textView.getScrollX() + xy[0],
546 y + textView.getTotalPaddingTop() - textView.getScrollY() + xy[1] };
Abodunrinwa Toki90cdfe02015-09-15 21:15:56 +0100547 }
548 }
549}