1 /*
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 
17 package android.widget.espresso;
18 
19 import static androidx.test.espresso.action.ViewActions.actionWithAssertions;
20 
21 import android.graphics.Rect;
22 import android.text.Layout;
23 import android.view.MotionEvent;
24 import android.view.View;
25 import android.widget.Editor;
26 import android.widget.Editor.HandleView;
27 import android.widget.TextView;
28 
29 import androidx.test.espresso.PerformException;
30 import androidx.test.espresso.ViewAction;
31 import androidx.test.espresso.action.CoordinatesProvider;
32 import androidx.test.espresso.action.GeneralLocation;
33 import androidx.test.espresso.action.Press;
34 import androidx.test.espresso.action.Tap;
35 import androidx.test.espresso.util.HumanReadables;
36 
37 /**
38  * A collection of actions on a {@link android.widget.TextView}.
39  */
40 public final class TextViewActions {
41 
TextViewActions()42     private TextViewActions() {}
43 
44     /**
45      * Returns an action that clicks on text at an index on the TextView.<br>
46      * <br>
47      * View constraints:
48      * <ul>
49      * <li>must be a TextView displayed on screen
50      * <ul>
51      *
52      * @param index The index of the TextView's text to click on.
53      */
clickOnTextAtIndex(int index)54     public static ViewAction clickOnTextAtIndex(int index) {
55         return actionWithAssertions(
56                 new ViewClickAction(Tap.SINGLE, new TextCoordinates(index), Press.FINGER));
57     }
58 
59 
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      */
mouseClick()68     public static ViewAction mouseClick() {
69         return actionWithAssertions(new MouseClickAction(Tap.SINGLE, GeneralLocation.VISIBLE_CENTER,
70                 MotionEvent.BUTTON_PRIMARY));
71     }
72 
73     /**
74      * 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      */
mouseClickOnTextAtIndex(int index)83     public static ViewAction mouseClickOnTextAtIndex(int index) {
84         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      */
mouseClickOnTextAtIndex(int index, @MouseUiController.MouseButton int button)98     public static ViewAction mouseClickOnTextAtIndex(int index,
99             @MouseUiController.MouseButton int button) {
100         return actionWithAssertions(
101                 new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), button));
102     }
103 
104     /**
105      * 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      */
doubleClickOnTextAtIndex(int index)114     public static ViewAction doubleClickOnTextAtIndex(int index) {
115         return actionWithAssertions(
116                 new ViewClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.FINGER));
117     }
118 
119     /**
120      * 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      */
mouseDoubleClickOnTextAtIndex(int index)129     public static ViewAction mouseDoubleClickOnTextAtIndex(int index) {
130         return actionWithAssertions(
131                 new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index)));
132     }
133 
134     /**
135      * 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      */
longPressOnTextAtIndex(int index)144     public static ViewAction longPressOnTextAtIndex(int index) {
145         return actionWithAssertions(
146                 new ViewClickAction(Tap.LONG, new TextCoordinates(index), Press.FINGER));
147     }
148 
149     /**
150      * 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      */
mouseLongClickOnTextAtIndex(int index)159     public static ViewAction mouseLongClickOnTextAtIndex(int index) {
160         return actionWithAssertions(
161                 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      */
mouseTripleClickOnTextAtIndex(int index)174     public static ViewAction mouseTripleClickOnTextAtIndex(int index) {
175         return actionWithAssertions(
176                 new MouseClickAction(MouseClickAction.CLICK.TRIPLE, new TextCoordinates(index)));
177     }
178 
179     /**
180      * 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      */
longPressAndDragOnText(int startIndex, int endIndex)191     public static ViewAction longPressAndDragOnText(int startIndex, int endIndex) {
192         return actionWithAssertions(
193                 new DragAction(
194                         DragAction.Drag.LONG_PRESS,
195                         new TextCoordinates(startIndex),
196                         new TextCoordinates(endIndex),
197                         Press.FINGER,
198                         TextView.class));
199     }
200 
201     /**
202      * Returns an action that long presses then drags on handle from the current position to
203      * endIndex on the TextView.<br>
204      * <br>
205      * View constraints:
206      * <ul>
207      * <li>must be a TextView's drag-handle displayed on screen
208      * <ul>
209      *
210      * @param textView TextView the handle is on
211      * @param handleType Type of the handle
212      * @param endIndex The index of the TextView's text to end the drag at
213      */
longPressAndDragHandle(TextView textView, Handle handleType, int endIndex)214     public static ViewAction longPressAndDragHandle(TextView textView, Handle handleType,
215             int endIndex) {
216         return actionWithAssertions(
217                 new DragAction(
218                         DragAction.Drag.LONG_PRESS,
219                         new CurrentHandleCoordinates(textView),
220                         new HandleCoordinates(textView, handleType, endIndex, true),
221                         Press.FINGER,
222                         Editor.HandleView.class));
223     }
224 
225     /**
226      * Returns an action that long presses on the current handle.<br>
227      * <br>
228      * View constraints:
229      * <ul>
230      * <li>must be a TextView's drag-handle displayed on screen
231      * <ul>
232      *
233      * @param textView TextView the handle is on
234      */
longPressHandle(TextView textView)235     public static ViewAction longPressHandle(TextView textView) {
236         return actionWithAssertions(
237                 new ViewClickAction(Tap.LONG, new CurrentHandleCoordinates(textView),
238                         Press.FINGER));
239     }
240 
241     /**
242      * Returns an action that double tap then drags on handle from the current position to
243      * endIndex on the TextView.<br>
244      * <br>
245      * View constraints:
246      * <ul>
247      * <li>must be a TextView's drag-handle displayed on screen
248      * <ul>
249      *
250      * @param textView TextView the handle is on
251      * @param handleType Type of the handle
252      * @param endIndex The index of the TextView's text to end the drag at
253      */
doubleTapAndDragHandle(TextView textView, Handle handleType, int endIndex)254     public static ViewAction doubleTapAndDragHandle(TextView textView, Handle handleType,
255             int endIndex) {
256         return actionWithAssertions(
257                 new DragAction(
258                         DragAction.Drag.DOUBLE_TAP,
259                         new CurrentHandleCoordinates(textView),
260                         new HandleCoordinates(textView, handleType, endIndex, true),
261                         Press.FINGER,
262                         Editor.HandleView.class));
263     }
264 
265     /**
266      * Returns an action that double tap on the current handle.<br>
267      * <br>
268      * View constraints:
269      * <ul>
270      * <li>must be a TextView's drag-handle displayed on screen
271      * <ul>
272      *
273      * @param textView TextView the handle is on
274      */
doubleTapHandle(TextView textView)275     public static ViewAction doubleTapHandle(TextView textView) {
276         return actionWithAssertions(
277                 new ViewClickAction(Tap.DOUBLE, new CurrentHandleCoordinates(textView),
278                         Press.FINGER));
279     }
280 
281     /**
282      * Returns an action that double taps then drags on text from startIndex to endIndex on the
283      * TextView.<br>
284      * <br>
285      * View constraints:
286      * <ul>
287      * <li>must be a TextView displayed on screen
288      * <ul>
289      *
290      * @param startIndex The index of the TextView's text to start a drag from
291      * @param endIndex The index of the TextView's text to end the drag at
292      */
doubleTapAndDragOnText(int startIndex, int endIndex)293     public static ViewAction doubleTapAndDragOnText(int startIndex, int endIndex) {
294         return actionWithAssertions(
295                 new DragAction(
296                         DragAction.Drag.DOUBLE_TAP,
297                         new TextCoordinates(startIndex),
298                         new TextCoordinates(endIndex),
299                         Press.FINGER,
300                         TextView.class));
301     }
302 
303     /**
304      * Returns an action that click then drags by mouse on text from startIndex to endIndex on the
305      * TextView.<br>
306      * <br>
307      * View constraints:
308      * <ul>
309      * <li>must be a TextView displayed on screen
310      * <ul>
311      *
312      * @param startIndex The index of the TextView's text to start a drag from
313      * @param endIndex The index of the TextView's text to end the drag at
314      */
mouseDragOnText(int startIndex, int endIndex)315     public static ViewAction mouseDragOnText(int startIndex, int endIndex) {
316         return actionWithAssertions(
317                 new DragAction(
318                         DragAction.Drag.MOUSE_DOWN,
319                         new TextCoordinates(startIndex),
320                         new TextCoordinates(endIndex),
321                         Press.PINPOINT,
322                         TextView.class));
323     }
324 
325     /**
326      * Returns an action that double click then drags by mouse on text from startIndex to endIndex
327      * on the TextView.<br>
328      * <br>
329      * View constraints:
330      * <ul>
331      * <li>must be a TextView displayed on screen
332      * <ul>
333      *
334      * @param startIndex The index of the TextView's text to start a drag from
335      * @param endIndex The index of the TextView's text to end the drag at
336      */
mouseDoubleClickAndDragOnText(int startIndex, int endIndex)337     public static ViewAction mouseDoubleClickAndDragOnText(int startIndex, int endIndex) {
338         return actionWithAssertions(
339                 new DragAction(
340                         DragAction.Drag.MOUSE_DOUBLE_CLICK,
341                         new TextCoordinates(startIndex),
342                         new TextCoordinates(endIndex),
343                         Press.PINPOINT,
344                         TextView.class));
345     }
346 
347     /**
348      * Returns an action that long click then drags by mouse on text from startIndex to endIndex
349      * on the TextView.<br>
350      * <br>
351      * View constraints:
352      * <ul>
353      * <li>must be a TextView displayed on screen
354      * <ul>
355      *
356      * @param startIndex The index of the TextView's text to start a drag from
357      * @param endIndex The index of the TextView's text to end the drag at
358      */
mouseLongClickAndDragOnText(int startIndex, int endIndex)359     public static ViewAction mouseLongClickAndDragOnText(int startIndex, int endIndex) {
360         return actionWithAssertions(
361                 new DragAction(
362                         DragAction.Drag.MOUSE_LONG_CLICK,
363                         new TextCoordinates(startIndex),
364                         new TextCoordinates(endIndex),
365                         Press.PINPOINT,
366                         TextView.class));
367     }
368 
369     /**
370     * Returns an action that triple click then drags by mouse on text from startIndex to endIndex
371     * on the TextView.<br>
372     * <br>
373     * View constraints:
374     * <ul>
375     * <li>must be a TextView displayed on screen
376     * <ul>
377     *
378     * @param startIndex The index of the TextView's text to start a drag from
379     * @param endIndex The index of the TextView's text to end the drag at
380     */
mouseTripleClickAndDragOnText(int startIndex, int endIndex)381    public static ViewAction mouseTripleClickAndDragOnText(int startIndex, int endIndex) {
382        return actionWithAssertions(
383                new DragAction(
384                        DragAction.Drag.MOUSE_TRIPLE_CLICK,
385                        new TextCoordinates(startIndex),
386                        new TextCoordinates(endIndex),
387                        Press.PINPOINT,
388                        TextView.class));
389    }
390 
391     public enum Handle {
392         SELECTION_START,
393         SELECTION_END,
394         INSERTION
395     };
396 
397     /**
398      * Returns an action that tap then drags on the handle from the current position to endIndex on
399      * the TextView.<br>
400      * <br>
401      * View constraints:
402      * <ul>
403      * <li>must be a TextView's drag-handle displayed on screen
404      * <ul>
405      *
406      * @param textView TextView the handle is on
407      * @param handleType Type of the handle
408      * @param endIndex The index of the TextView's text to end the drag at
409      */
dragHandle(TextView textView, Handle handleType, int endIndex)410     public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex) {
411         return dragHandle(textView, handleType, endIndex, true);
412     }
413 
414     /**
415      * Returns an action that tap then drags on the handle from the current position to endIndex on
416      * the TextView.<br>
417      * <br>
418      * View constraints:
419      * <ul>
420      * <li>must be a TextView's drag-handle displayed on screen
421      * <ul>
422      *
423      * @param textView TextView the handle is on
424      * @param handleType Type of the handle
425      * @param endIndex The index of the TextView's text to end the drag at
426      * @param primary whether to use primary direction to get coordinate form index when endIndex is
427      * at a direction boundary.
428      */
dragHandle(TextView textView, Handle handleType, int endIndex, boolean primary)429     public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex,
430             boolean primary) {
431         return actionWithAssertions(
432                 new DragAction(
433                         DragAction.Drag.TAP,
434                         new CurrentHandleCoordinates(textView),
435                         new HandleCoordinates(textView, handleType, endIndex, primary),
436                         Press.FINGER,
437                         Editor.HandleView.class));
438     }
439 
440     /**
441      * Returns an action that drags on text from startIndex to endIndex on the TextView.<br>
442      * <br>
443      * View constraints:
444      * <ul>
445      * <li>must be a TextView displayed on screen
446      * <ul>
447      *
448      * @param startIndex The index of the TextView's text to start a drag from
449      * @param endIndex The index of the TextView's text to end the drag at
450      */
dragOnText(int startIndex, int endIndex)451     public static ViewAction dragOnText(int startIndex, int endIndex) {
452         return actionWithAssertions(
453                 new DragAction(
454                         DragAction.Drag.TAP,
455                         new TextCoordinates(startIndex),
456                         new TextCoordinates(endIndex),
457                         Press.FINGER,
458                         TextView.class));
459     }
460 
461     /**
462      * A provider of the x, y coordinates of the handle dragging point.
463      */
464     private static final class CurrentHandleCoordinates implements CoordinatesProvider {
465         // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS.
466         private final TextView mTextView;
467         private final String mActionDescription;
468 
469 
CurrentHandleCoordinates(TextView textView)470         public CurrentHandleCoordinates(TextView textView) {
471             mTextView = textView;
472             mActionDescription = "Could not locate handle.";
473         }
474 
475         @Override
calculateCoordinates(View view)476         public float[] calculateCoordinates(View view) {
477             try {
478                 return locateHandle(view);
479             } catch (StringIndexOutOfBoundsException e) {
480                 throw new PerformException.Builder()
481                         .withActionDescription(mActionDescription)
482                         .withViewDescription(HumanReadables.describe(view))
483                         .withCause(e)
484                         .build();
485             }
486         }
487 
locateHandle(View view)488         private float[] locateHandle(View view) {
489             final Rect bounds = new Rect();
490             view.getBoundsOnScreen(bounds);
491             final Rect visibleDisplayBounds = new Rect();
492             mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds);
493             visibleDisplayBounds.right -= 1;
494             visibleDisplayBounds.bottom -= 1;
495             if (!visibleDisplayBounds.intersect(bounds)) {
496                 throw new PerformException.Builder()
497                         .withActionDescription(mActionDescription
498                                 + " The handle is entirely out of the visible display frame of"
499                                 + "the TextView's window.")
500                         .withViewDescription(HumanReadables.describe(view))
501                         .build();
502             }
503             final float dragPointX = Math.max(Math.min(bounds.centerX(),
504                     visibleDisplayBounds.right), visibleDisplayBounds.left);
505             final float verticalOffset = bounds.height() * 0.7f;
506             final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset,
507                     visibleDisplayBounds.bottom), visibleDisplayBounds.top);
508             return new float[] {dragPointX, dragPointY};
509         }
510     }
511 
512     /**
513      * A provider of the x, y coordinates of the handle that points the specified text index in a
514      * text view.
515      */
516     private static final class HandleCoordinates implements CoordinatesProvider {
517         // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS.
518         private final static float LINE_SLOP_MULTIPLIER = 0.6f;
519         private final TextView mTextView;
520         private final Handle mHandleType;
521         private final int mIndex;
522         private final boolean mPrimary;
523         private final String mActionDescription;
524 
HandleCoordinates(TextView textView, Handle handleType, int index, boolean primary)525         public HandleCoordinates(TextView textView, Handle handleType, int index, boolean primary) {
526             mTextView = textView;
527             mHandleType = handleType;
528             mIndex = index;
529             mPrimary = primary;
530             mActionDescription = "Could not locate " + handleType.toString()
531                     + " handle that points text index: " + index
532                     + " (" + (primary ? "primary" : "secondary" ) + ")";
533         }
534 
535         @Override
calculateCoordinates(View view)536         public float[] calculateCoordinates(View view) {
537             try {
538                 return locateHandlePointsTextIndex(view);
539             } catch (StringIndexOutOfBoundsException e) {
540                 throw new PerformException.Builder()
541                         .withActionDescription(mActionDescription)
542                         .withViewDescription(HumanReadables.describe(view))
543                         .withCause(e)
544                         .build();
545             }
546         }
547 
locateHandlePointsTextIndex(View view)548         private float[] locateHandlePointsTextIndex(View view) {
549             if (!(view instanceof HandleView)) {
550                 throw new PerformException.Builder()
551                         .withActionDescription(mActionDescription + " The view is not a HandleView")
552                         .withViewDescription(HumanReadables.describe(view))
553                         .build();
554             }
555             final HandleView handleView = (HandleView) view;
556             final int currentOffset = mHandleType == Handle.SELECTION_START ?
557                     mTextView.getSelectionStart() : mTextView.getSelectionEnd();
558 
559             final Layout layout = mTextView.getLayout();
560 
561             final int currentLine = layout.getLineForOffset(currentOffset);
562             final int targetLine = layout.getLineForOffset(mIndex);
563             final float currentX = handleView.getHorizontal(layout, currentOffset);
564             final float currentY = layout.getLineTop(currentLine);
565             final float[] currentCoordinates =
566                     TextCoordinates.convertToScreenCoordinates(mTextView, currentX, currentY);
567             final float[] targetCoordinates =
568                     (new TextCoordinates(mIndex, mPrimary)).calculateCoordinates(mTextView);
569             final Rect bounds = new Rect();
570             view.getBoundsOnScreen(bounds);
571             final Rect visibleDisplayBounds = new Rect();
572             mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds);
573             visibleDisplayBounds.right -= 1;
574             visibleDisplayBounds.bottom -= 1;
575             if (!visibleDisplayBounds.intersect(bounds)) {
576                 throw new PerformException.Builder()
577                         .withActionDescription(mActionDescription
578                                 + " The handle is entirely out of the visible display frame of"
579                                 + "the TextView's window.")
580                         .withViewDescription(HumanReadables.describe(view))
581                         .build();
582             }
583             final float dragPointX = Math.max(Math.min(bounds.centerX(),
584                     visibleDisplayBounds.right), visibleDisplayBounds.left);
585             final float diffX = dragPointX - currentCoordinates[0];
586             final float verticalOffset = bounds.height() * 0.7f;
587             final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset,
588                     visibleDisplayBounds.bottom), visibleDisplayBounds.top);
589             float diffY = dragPointY - currentCoordinates[1];
590             if (currentLine > targetLine) {
591                 diffY -= mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER;
592             } else if (currentLine < targetLine) {
593                 diffY += mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER;
594             }
595             return new float[] {targetCoordinates[0] + diffX, targetCoordinates[1] + diffY};
596         }
597     }
598 
599     /**
600      * A provider of the x, y coordinates of the text at the specified index in a text view.
601      */
602     private static final class TextCoordinates implements CoordinatesProvider {
603 
604         private final int mIndex;
605         private final boolean mPrimary;
606         private final String mActionDescription;
607 
TextCoordinates(int index)608         public TextCoordinates(int index) {
609             this(index, true);
610         }
611 
TextCoordinates(int index, boolean primary)612         public TextCoordinates(int index, boolean primary) {
613             mIndex = index;
614             mPrimary = primary;
615             mActionDescription = "Could not locate text at index: " + mIndex
616                     + " (" + (primary ? "primary" : "secondary" ) + ")";
617         }
618 
619         @Override
calculateCoordinates(View view)620         public float[] calculateCoordinates(View view) {
621             try {
622                 return locateTextAtIndex((TextView) view, mIndex, mPrimary);
623             } catch (ClassCastException e) {
624                 throw new PerformException.Builder()
625                         .withActionDescription(mActionDescription)
626                         .withViewDescription(HumanReadables.describe(view))
627                         .withCause(e)
628                         .build();
629             } catch (StringIndexOutOfBoundsException e) {
630                 throw new PerformException.Builder()
631                         .withActionDescription(mActionDescription)
632                         .withViewDescription(HumanReadables.describe(view))
633                         .withCause(e)
634                         .build();
635             }
636         }
637 
638         /**
639          * @throws StringIndexOutOfBoundsException
640          */
locateTextAtIndex(TextView textView, int index, boolean primary)641         private float[] locateTextAtIndex(TextView textView, int index, boolean primary) {
642             if (index < 0 || index > textView.getText().length()) {
643                 throw new StringIndexOutOfBoundsException(index);
644             }
645             final Layout layout = textView.getLayout();
646             final int line = layout.getLineForOffset(index);
647             return convertToScreenCoordinates(textView,
648                     (primary ? layout.getPrimaryHorizontal(index)
649                             : layout.getSecondaryHorizontal(index)),
650                     layout.getLineTop(line));
651         }
652 
653         /**
654          * Convert TextView's local coordinates to on screen coordinates.
655          * @param textView the TextView
656          * @param x local horizontal coordinate
657          * @param y local vertical coordinate
658          * @return
659          */
convertToScreenCoordinates(TextView textView, float x, float y)660         public static float[] convertToScreenCoordinates(TextView textView, float x, float y) {
661             final int[] xy = new int[2];
662             textView.getLocationOnScreen(xy);
663             return new float[]{ x + textView.getTotalPaddingLeft() - textView.getScrollX() + xy[0],
664                     y + textView.getTotalPaddingTop() - textView.getScrollY() + xy[1] };
665         }
666     }
667 }
668