190cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki/*
290cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki * Copyright (C) 2015 The Android Open Source Project
390cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki *
490cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki * Licensed under the Apache License, Version 2.0 (the "License");
590cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki * you may not use this file except in compliance with the License.
690cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki * You may obtain a copy of the License at
790cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki *
890cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki *      http://www.apache.org/licenses/LICENSE-2.0
990cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki *
1090cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki * Unless required by applicable law or agreed to in writing, software
1190cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki * distributed under the License is distributed on an "AS IS" BASIS,
1290cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1390cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki * See the License for the specific language governing permissions and
1490cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki * limitations under the License
1590cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki */
1690cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki
1790cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Tokipackage android.widget.espresso;
1890cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki
1990cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Tokiimport static android.support.test.espresso.action.ViewActions.actionWithAssertions;
206b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir
21da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagiimport android.graphics.Rect;
2290cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Tokiimport android.support.test.espresso.PerformException;
2390cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Tokiimport android.support.test.espresso.ViewAction;
2490cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Tokiimport android.support.test.espresso.action.CoordinatesProvider;
256b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinirimport android.support.test.espresso.action.GeneralLocation;
2690cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Tokiimport android.support.test.espresso.action.Press;
2790cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Tokiimport android.support.test.espresso.action.Tap;
2890cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Tokiimport android.support.test.espresso.util.HumanReadables;
2990cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Tokiimport android.text.Layout;
30a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagiimport android.view.MotionEvent;
3190cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Tokiimport android.view.View;
32da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagiimport android.widget.Editor;
33f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagiimport android.widget.Editor.HandleView;
3490cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Tokiimport android.widget.TextView;
3590cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki
3690cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki/**
3790cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki * A collection of actions on a {@link android.widget.TextView}.
3890cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki */
3990cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Tokipublic final class TextViewActions {
4090cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki
4190cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki    private TextViewActions() {}
4290cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki
4390cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki    /**
44ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * Returns an action that clicks on text at an index on the TextView.<br>
4590cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki     * <br>
4690cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki     * View constraints:
4790cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki     * <ul>
48ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * <li>must be a TextView displayed on screen
4990cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki     * <ul>
50ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     *
51ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * @param index The index of the TextView's text to click on.
5290cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki     */
5390cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki    public static ViewAction clickOnTextAtIndex(int index) {
5490cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki        return actionWithAssertions(
555f318b62853495f286e011de9cb1571e22431314Keisuke Kuroyanagi                new ViewClickAction(Tap.SINGLE, new TextCoordinates(index), Press.FINGER));
5690cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki    }
5790cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki
586b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir
596b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir    /**
606b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir     * Returns an action that single-clicks by mouse on the View.<br>
616b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir     * <br>
626b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir     * View constraints:
636b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir     * <ul>
646b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir     * <li>must be a View displayed on screen
656b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir     * <ul>
666b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir     */
676b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir    public static ViewAction mouseClick() {
686b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir        return actionWithAssertions(new MouseClickAction(Tap.SINGLE, GeneralLocation.VISIBLE_CENTER,
696b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir                MotionEvent.BUTTON_PRIMARY));
706b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir    }
716b61d816ddbdd251ce48d177c6cffe3dda507877Siyamed Sinir
7290cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki    /**
732ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * Returns an action that clicks by mouse on text at an index on the TextView.<br>
742ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <br>
752ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * View constraints:
762ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <ul>
772ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <li>must be a TextView displayed on screen
782ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <ul>
792ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     *
802ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * @param index The index of the TextView's text to click on.
812ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     */
822ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    public static ViewAction mouseClickOnTextAtIndex(int index) {
83a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi        return mouseClickOnTextAtIndex(index, MotionEvent.BUTTON_PRIMARY);
84a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi    }
85a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi
86a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi    /**
87a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi     * Returns an action that clicks by mouse on text at an index on the TextView.<br>
88a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi     * <br>
89a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi     * View constraints:
90a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi     * <ul>
91a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi     * <li>must be a TextView displayed on screen
92a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi     * <ul>
93a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi     *
94a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi     * @param index The index of the TextView's text to click on.
95a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi     * @param button the mouse button to use.
96a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi     */
97a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi    public static ViewAction mouseClickOnTextAtIndex(int index,
98a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi            @MouseUiController.MouseButton int button) {
992ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi        return actionWithAssertions(
100a0b3c068810b2bc4ed61d9ad35e9660aa247d2f6Keisuke Kuroyanagi                new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), button));
1012ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    }
1022ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi
1032ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    /**
1048a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * Returns an action that double-clicks on text at an index on the TextView.<br>
1058a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * <br>
1068a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * View constraints:
1078a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * <ul>
1088a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * <li>must be a TextView displayed on screen
1098a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * <ul>
1108a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     *
1118a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * @param index The index of the TextView's text to double-click on.
1128a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     */
1138a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki    public static ViewAction doubleClickOnTextAtIndex(int index) {
1148a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki        return actionWithAssertions(
1155f318b62853495f286e011de9cb1571e22431314Keisuke Kuroyanagi                new ViewClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.FINGER));
1168a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki    }
1178a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki
1188a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki    /**
1192ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * Returns an action that double-clicks by mouse on text at an index on the TextView.<br>
1202ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <br>
1212ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * View constraints:
1222ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <ul>
1232ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <li>must be a TextView displayed on screen
1242ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <ul>
1252ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     *
1262ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * @param index The index of the TextView's text to double-click on.
1272ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     */
1282ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    public static ViewAction mouseDoubleClickOnTextAtIndex(int index) {
1292ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi        return actionWithAssertions(
13046faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi                new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index)));
1312ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    }
1322ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi
1332ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    /**
1348a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * Returns an action that long presses on text at an index on the TextView.<br>
1358a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * <br>
1368a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * View constraints:
1378a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * <ul>
1388a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * <li>must be a TextView displayed on screen
1398a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * <ul>
1408a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     *
1418a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     * @param index The index of the TextView's text to long press on.
1428a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki     */
1438a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki    public static ViewAction longPressOnTextAtIndex(int index) {
1448a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki        return actionWithAssertions(
1455f318b62853495f286e011de9cb1571e22431314Keisuke Kuroyanagi                new ViewClickAction(Tap.LONG, new TextCoordinates(index), Press.FINGER));
1468a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki    }
1478a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki
1488a5e1ae2f4e1aaf2db2a217e841371e18851df3fAbodunrinwa Toki    /**
1492ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * Returns an action that long click by mouse on text at an index on the TextView.<br>
1502ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <br>
1512ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * View constraints:
1522ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <ul>
1532ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <li>must be a TextView displayed on screen
1542ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <ul>
1552ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     *
1562ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * @param index The index of the TextView's text to long click on.
1572ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     */
1582ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    public static ViewAction mouseLongClickOnTextAtIndex(int index) {
1592ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi        return actionWithAssertions(
16046faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi                new MouseClickAction(Tap.LONG, new TextCoordinates(index)));
16146faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    }
16246faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi
16346faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    /**
16446faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi     * Returns an action that triple-clicks by mouse on text at an index on the TextView.<br>
16546faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi     * <br>
16646faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi     * View constraints:
16746faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi     * <ul>
16846faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi     * <li>must be a TextView displayed on screen
16946faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi     * <ul>
17046faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi     *
17146faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi     * @param index The index of the TextView's text to triple-click on.
17246faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi     */
17346faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    public static ViewAction mouseTripleClickOnTextAtIndex(int index) {
17446faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi        return actionWithAssertions(
17546faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi                new MouseClickAction(MouseClickAction.CLICK.TRIPLE, new TextCoordinates(index)));
1762ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    }
1772ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi
1782ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    /**
179ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * Returns an action that long presses then drags on text from startIndex to endIndex on the
180ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * TextView.<br>
181ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * <br>
182ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * View constraints:
183ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * <ul>
184ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * <li>must be a TextView displayed on screen
185ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * <ul>
186ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     *
187ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * @param startIndex The index of the TextView's text to start a drag from
188ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * @param endIndex The index of the TextView's text to end the drag at
189ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     */
190ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki    public static ViewAction longPressAndDragOnText(int startIndex, int endIndex) {
191ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki        return actionWithAssertions(
192da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                new DragAction(
193da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        DragAction.Drag.LONG_PRESS,
194ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki                        new TextCoordinates(startIndex),
195ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki                        new TextCoordinates(endIndex),
196da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        Press.FINGER,
197da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        TextView.class));
198ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki    }
199ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki
200ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki    /**
201ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * Returns an action that double taps then drags on text from startIndex to endIndex on the
202ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * TextView.<br>
203ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * <br>
204ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * View constraints:
205ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * <ul>
206ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * <li>must be a TextView displayed on screen
207ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * <ul>
208ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     *
209ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * @param startIndex The index of the TextView's text to start a drag from
210ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     * @param endIndex The index of the TextView's text to end the drag at
211ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki     */
212ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki    public static ViewAction doubleTapAndDragOnText(int startIndex, int endIndex) {
213ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki        return actionWithAssertions(
214da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                new DragAction(
215da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        DragAction.Drag.DOUBLE_TAP,
216ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki                        new TextCoordinates(startIndex),
217ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki                        new TextCoordinates(endIndex),
218da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        Press.FINGER,
219da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        TextView.class));
220ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki    }
221ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki
222ca4aaf3c17fdea4e373accc212867bd9f1e2be7fAbodunrinwa Toki    /**
22388cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi     * Returns an action that click then drags by mouse on text from startIndex to endIndex on the
22488cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi     * TextView.<br>
22588cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi     * <br>
22688cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi     * View constraints:
22788cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi     * <ul>
22888cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi     * <li>must be a TextView displayed on screen
22988cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi     * <ul>
23088cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi     *
23188cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi     * @param startIndex The index of the TextView's text to start a drag from
23288cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi     * @param endIndex The index of the TextView's text to end the drag at
23388cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi     */
23488cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi    public static ViewAction mouseDragOnText(int startIndex, int endIndex) {
23588cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi        return actionWithAssertions(
236da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                new DragAction(
237da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        DragAction.Drag.MOUSE_DOWN,
23888cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi                        new TextCoordinates(startIndex),
2392ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi                        new TextCoordinates(endIndex),
2402ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi                        Press.PINPOINT,
2412ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi                        TextView.class));
2422ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    }
2432ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi
2442ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    /**
2452ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * Returns an action that double click then drags by mouse on text from startIndex to endIndex
2462ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * on the TextView.<br>
2472ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <br>
2482ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * View constraints:
2492ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <ul>
2502ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <li>must be a TextView displayed on screen
2512ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <ul>
2522ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     *
2532ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * @param startIndex The index of the TextView's text to start a drag from
2542ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * @param endIndex The index of the TextView's text to end the drag at
2552ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     */
2562ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    public static ViewAction mouseDoubleClickAndDragOnText(int startIndex, int endIndex) {
2572ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi        return actionWithAssertions(
2582ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi                new DragAction(
2592ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi                        DragAction.Drag.MOUSE_DOUBLE_CLICK,
2602ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi                        new TextCoordinates(startIndex),
2612ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi                        new TextCoordinates(endIndex),
2622ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi                        Press.PINPOINT,
2632ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi                        TextView.class));
2642ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    }
2652ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi
2662ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    /**
2672ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * Returns an action that long click then drags by mouse on text from startIndex to endIndex
2682ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * on the TextView.<br>
2692ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <br>
2702ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * View constraints:
2712ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <ul>
2722ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <li>must be a TextView displayed on screen
2732ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * <ul>
2742ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     *
2752ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * @param startIndex The index of the TextView's text to start a drag from
2762ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     * @param endIndex The index of the TextView's text to end the drag at
2772ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi     */
2782ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi    public static ViewAction mouseLongClickAndDragOnText(int startIndex, int endIndex) {
2792ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi        return actionWithAssertions(
2802ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi                new DragAction(
2812ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi                        DragAction.Drag.MOUSE_LONG_CLICK,
2822ff41d4afca7216cca4a224228caec2a5efaf278Keisuke Kuroyanagi                        new TextCoordinates(startIndex),
28388cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi                        new TextCoordinates(endIndex),
284da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        Press.PINPOINT,
285da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        TextView.class));
286da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi    }
287da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi
28846faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    /**
28946faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    * Returns an action that triple click then drags by mouse on text from startIndex to endIndex
29046faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    * on the TextView.<br>
29146faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    * <br>
29246faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    * View constraints:
29346faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    * <ul>
29446faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    * <li>must be a TextView displayed on screen
29546faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    * <ul>
29646faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    *
29746faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    * @param startIndex The index of the TextView's text to start a drag from
29846faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    * @param endIndex The index of the TextView's text to end the drag at
29946faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi    */
30046faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi   public static ViewAction mouseTripleClickAndDragOnText(int startIndex, int endIndex) {
30146faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi       return actionWithAssertions(
30246faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi               new DragAction(
30346faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi                       DragAction.Drag.MOUSE_TRIPLE_CLICK,
30446faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi                       new TextCoordinates(startIndex),
30546faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi                       new TextCoordinates(endIndex),
30646faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi                       Press.PINPOINT,
30746faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi                       TextView.class));
30846faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi   }
30946faad60230ade76b6a4944a2b9fae274698ab91Keisuke Kuroyanagi
310da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi    public enum Handle {
311da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        SELECTION_START,
312da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        SELECTION_END,
313da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        INSERTION
314da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi    };
315da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi
316da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi    /**
317da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     * Returns an action that tap then drags on the handle from the current position to endIndex on
318da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     * the TextView.<br>
319da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     * <br>
320da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     * View constraints:
321da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     * <ul>
322da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     * <li>must be a TextView's drag-handle displayed on screen
323da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     * <ul>
324da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     *
325da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     * @param textView TextView the handle is on
326da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     * @param handleType Type of the handle
327da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     * @param endIndex The index of the TextView's text to end the drag at
328da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     */
329da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi    public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex) {
330f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        return dragHandle(textView, handleType, endIndex, true);
331f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi    }
332f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi
333f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi    /**
334f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     * Returns an action that tap then drags on the handle from the current position to endIndex on
335f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     * the TextView.<br>
336f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     * <br>
337f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     * View constraints:
338f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     * <ul>
339f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     * <li>must be a TextView's drag-handle displayed on screen
340f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     * <ul>
341f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     *
342f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     * @param textView TextView the handle is on
343f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     * @param handleType Type of the handle
344f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     * @param endIndex The index of the TextView's text to end the drag at
345f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     * @param primary whether to use primary direction to get coordinate form index when endIndex is
346f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     * at a direction boundary.
347f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     */
348f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi    public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex,
349f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            boolean primary) {
350da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        return actionWithAssertions(
351da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                new DragAction(
352da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        DragAction.Drag.TAP,
353f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                        new CurrentHandleCoordinates(textView),
3547557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader                        new HandleCoordinates(textView, handleType, endIndex, primary),
355da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        Press.FINGER,
356da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        Editor.HandleView.class));
357da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi    }
3587557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader
359da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi    /**
360f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     * A provider of the x, y coordinates of the handle dragging point.
361f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi     */
362f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi    private static final class CurrentHandleCoordinates implements CoordinatesProvider {
363f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS.
364f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        private final TextView mTextView;
365f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        private final String mActionDescription;
366f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi
367f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi
368f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        public CurrentHandleCoordinates(TextView textView) {
369f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            mTextView = textView;
370f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            mActionDescription = "Could not locate handle.";
371f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        }
372f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi
373f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        @Override
374f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        public float[] calculateCoordinates(View view) {
375f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            try {
376f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                return locateHandle(view);
377f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            } catch (StringIndexOutOfBoundsException e) {
378f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                throw new PerformException.Builder()
379f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                        .withActionDescription(mActionDescription)
380f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                        .withViewDescription(HumanReadables.describe(view))
381f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                        .withCause(e)
382f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                        .build();
383f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            }
384f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        }
385f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi
386f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        private float[] locateHandle(View view) {
387f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            final Rect bounds = new Rect();
388f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            view.getBoundsOnScreen(bounds);
389f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            final Rect visibleDisplayBounds = new Rect();
390f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds);
391f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            visibleDisplayBounds.right -= 1;
392f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            visibleDisplayBounds.bottom -= 1;
393f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            if (!visibleDisplayBounds.intersect(bounds)) {
394f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                throw new PerformException.Builder()
395f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                        .withActionDescription(mActionDescription
396f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                                + " The handle is entirely out of the visible display frame of"
397f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                                + "the TextView's window.")
398f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                        .withViewDescription(HumanReadables.describe(view))
399f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                        .build();
400f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            }
401f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            final float dragPointX = Math.max(Math.min(bounds.centerX(),
402f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                    visibleDisplayBounds.right), visibleDisplayBounds.left);
403f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            final float verticalOffset = bounds.height() * 0.7f;
404f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset,
405f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                    visibleDisplayBounds.bottom), visibleDisplayBounds.top);
406f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            return new float[] {dragPointX, dragPointY};
407f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        }
408f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi    }
409f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi
410f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi    /**
411da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     * A provider of the x, y coordinates of the handle that points the specified text index in a
412da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     * text view.
413da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi     */
414da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi    private static final class HandleCoordinates implements CoordinatesProvider {
415c24c2bbb69634435d1d9fc97671229df245bd8d0Keisuke Kuroyanagi        // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS.
416c24c2bbb69634435d1d9fc97671229df245bd8d0Keisuke Kuroyanagi        private final static float LINE_SLOP_MULTIPLIER = 0.6f;
417da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        private final TextView mTextView;
418da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        private final Handle mHandleType;
419da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        private final int mIndex;
420f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        private final boolean mPrimary;
421da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        private final String mActionDescription;
422da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi
4237557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader        public HandleCoordinates(TextView textView, Handle handleType, int index, boolean primary) {
424da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi            mTextView = textView;
425da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi            mHandleType = handleType;
426da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi            mIndex = index;
427f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            mPrimary = primary;
428da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi            mActionDescription = "Could not locate " + handleType.toString()
429f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                    + " handle that points text index: " + index
430f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                    + " (" + (primary ? "primary" : "secondary" ) + ")";
431da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        }
432da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi
433da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        @Override
434da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        public float[] calculateCoordinates(View view) {
435da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi            try {
436da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                return locateHandlePointsTextIndex(view);
437da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi            } catch (StringIndexOutOfBoundsException e) {
438da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                throw new PerformException.Builder()
439da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        .withActionDescription(mActionDescription)
440da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        .withViewDescription(HumanReadables.describe(view))
441da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        .withCause(e)
442da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                        .build();
443da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi            }
444da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        }
445da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi
446da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        private float[] locateHandlePointsTextIndex(View view) {
447f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            if (!(view instanceof HandleView)) {
448f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                throw new PerformException.Builder()
449f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                        .withActionDescription(mActionDescription + " The view is not a HandleView")
450f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                        .withViewDescription(HumanReadables.describe(view))
451f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                        .build();
452f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            }
453f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            final HandleView handleView = (HandleView) view;
454da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi            final int currentOffset = mHandleType == Handle.SELECTION_START ?
455da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi                    mTextView.getSelectionStart() : mTextView.getSelectionEnd();
456c24c2bbb69634435d1d9fc97671229df245bd8d0Keisuke Kuroyanagi
457c24c2bbb69634435d1d9fc97671229df245bd8d0Keisuke Kuroyanagi            final Layout layout = mTextView.getLayout();
458f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi
459c24c2bbb69634435d1d9fc97671229df245bd8d0Keisuke Kuroyanagi            final int currentLine = layout.getLineForOffset(currentOffset);
460c24c2bbb69634435d1d9fc97671229df245bd8d0Keisuke Kuroyanagi            final int targetLine = layout.getLineForOffset(mIndex);
461f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            final float currentX = handleView.getHorizontal(layout, currentOffset);
462f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            final float currentY = layout.getLineTop(currentLine);
463da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi            final float[] currentCoordinates =
4647557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader                    TextCoordinates.convertToScreenCoordinates(mTextView, currentX, currentY);
465da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi            final float[] targetCoordinates =
4667557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader                    (new TextCoordinates(mIndex, mPrimary)).calculateCoordinates(mTextView);
467da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi            final Rect bounds = new Rect();
468da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi            view.getBoundsOnScreen(bounds);
4695f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi            final Rect visibleDisplayBounds = new Rect();
4705f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi            mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds);
4715f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi            visibleDisplayBounds.right -= 1;
4725f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi            visibleDisplayBounds.bottom -= 1;
4735f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi            if (!visibleDisplayBounds.intersect(bounds)) {
4745f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi                throw new PerformException.Builder()
4755f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi                        .withActionDescription(mActionDescription
4765f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi                                + " The handle is entirely out of the visible display frame of"
4775f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi                                + "the TextView's window.")
4785f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi                        .withViewDescription(HumanReadables.describe(view))
4795f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi                        .build();
4805f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi            }
4815f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi            final float dragPointX = Math.max(Math.min(bounds.centerX(),
4825f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi                    visibleDisplayBounds.right), visibleDisplayBounds.left);
4835f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi            final float diffX = dragPointX - currentCoordinates[0];
484c24c2bbb69634435d1d9fc97671229df245bd8d0Keisuke Kuroyanagi            final float verticalOffset = bounds.height() * 0.7f;
4855f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi            final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset,
4865f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi                    visibleDisplayBounds.bottom), visibleDisplayBounds.top);
4875f71b5afe83ea6a183a9a010c05ce4e1453e264bKeisuke Kuroyanagi            float diffY = dragPointY - currentCoordinates[1];
488c24c2bbb69634435d1d9fc97671229df245bd8d0Keisuke Kuroyanagi            if (currentLine > targetLine) {
489c24c2bbb69634435d1d9fc97671229df245bd8d0Keisuke Kuroyanagi                diffY -= mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER;
490c24c2bbb69634435d1d9fc97671229df245bd8d0Keisuke Kuroyanagi            } else if (currentLine < targetLine) {
491c24c2bbb69634435d1d9fc97671229df245bd8d0Keisuke Kuroyanagi                diffY += mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER;
492c24c2bbb69634435d1d9fc97671229df245bd8d0Keisuke Kuroyanagi            }
493da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi            return new float[] {targetCoordinates[0] + diffX, targetCoordinates[1] + diffY};
494da79ee683d74f2ad7d29147a30f3da17dd0ea6e2Keisuke Kuroyanagi        }
49588cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi    }
49688cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi
49788cabede9bc74ade7d0124cde3d40fadb6c97a85Keisuke Kuroyanagi    /**
49890cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki     * A provider of the x, y coordinates of the text at the specified index in a text view.
49990cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki     */
50090cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki    private static final class TextCoordinates implements CoordinatesProvider {
50190cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki
50290cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki        private final int mIndex;
503f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        private final boolean mPrimary;
50490cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki        private final String mActionDescription;
50590cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki
50690cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki        public TextCoordinates(int index) {
5077557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader            this(index, true);
508f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        }
509f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi
5107557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader        public TextCoordinates(int index, boolean primary) {
51190cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki            mIndex = index;
512f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            mPrimary = primary;
513f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            mActionDescription = "Could not locate text at index: " + mIndex
5147557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader                    + " (" + (primary ? "primary" : "secondary" ) + ")";
51590cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki        }
51690cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki
51790cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki        @Override
51890cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki        public float[] calculateCoordinates(View view) {
51990cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki            try {
5207557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader                return locateTextAtIndex((TextView) view, mIndex, mPrimary);
52190cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki            } catch (ClassCastException e) {
52290cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki                throw new PerformException.Builder()
52390cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki                        .withActionDescription(mActionDescription)
52490cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki                        .withViewDescription(HumanReadables.describe(view))
52590cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki                        .withCause(e)
52690cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki                        .build();
52790cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki            } catch (StringIndexOutOfBoundsException e) {
52890cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki                throw new PerformException.Builder()
52990cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki                        .withActionDescription(mActionDescription)
53090cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki                        .withViewDescription(HumanReadables.describe(view))
53190cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki                        .withCause(e)
53290cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki                        .build();
53390cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki            }
53490cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki        }
53590cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki
53690cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki        /**
53790cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki         * @throws StringIndexOutOfBoundsException
53890cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki         */
5397557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader        private float[] locateTextAtIndex(TextView textView, int index, boolean primary) {
54090cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki            if (index < 0 || index > textView.getText().length()) {
54190cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki                throw new StringIndexOutOfBoundsException(index);
54290cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki            }
54390cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki            final Layout layout = textView.getLayout();
5447557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader            final int line = layout.getLineForOffset(index);
545f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi            return convertToScreenCoordinates(textView,
5467557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader                    (primary ? layout.getPrimaryHorizontal(index)
5477557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader                            : layout.getSecondaryHorizontal(index)),
548f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi                    layout.getLineTop(line));
549f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi        }
550f0bb87b7c40efeeaee58d4c7b767961c9800463eKeisuke Kuroyanagi
5517557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader        /**
5527557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader         * Convert TextView's local coordinates to on screen coordinates.
5537557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader         * @param textView the TextView
5547557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader         * @param x local horizontal coordinate
5557557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader         * @param y local vertical coordinate
5567557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader         * @return
5577557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader         */
5587557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader        public static float[] convertToScreenCoordinates(TextView textView, float x, float y) {
5597557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader            final int[] xy = new int[2];
5607557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader            textView.getLocationOnScreen(xy);
5617557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader            return new float[]{ x + textView.getTotalPaddingLeft() - textView.getScrollX() + xy[0],
5627557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader                    y + textView.getTotalPaddingTop() - textView.getScrollY() + xy[1] };
5637557a5a090204dccfd32096af4a6ded1f9bb5b17Roozbeh Pournader        }
56490cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki    }
56590cdfe0514154bbc008d39f1c99f7a1d2684446cAbodunrinwa Toki}
566