Loading core/java/android/widget/Editor.java +159 −4 Original line number Diff line number Diff line Loading @@ -387,7 +387,7 @@ public class Editor { // Specifies whether the cursor control feature set is enabled. // This can only be true if the text view is editable. private final boolean mCursorControlEnabled; private boolean mCursorControlEnabled; Editor(TextView textView) { mTextView = textView; Loading @@ -411,6 +411,16 @@ public class Editor { } } @VisibleForTesting public void setCursorControlEnabled(boolean enabled) { mCursorControlEnabled = enabled; } @VisibleForTesting public boolean getCursorControlEnabled() { return mCursorControlEnabled; } ParcelableParcel saveInstanceState() { ParcelableParcel state = new ParcelableParcel(getClass().getClassLoader()); Parcel parcel = state.getParcel(); Loading Loading @@ -1204,7 +1214,7 @@ public class Editor { } // Long press in empty space moves cursor and starts the insertion action mode. if (!handled && !isPositionOnText(mTouchState.getLastDownX(), mTouchState.getLastDownY()) && mInsertionControllerEnabled) { && !mTouchState.isOnHandle() && mInsertionControllerEnabled) { final int offset = mTextView.getOffsetForPosition(mTouchState.getLastDownX(), mTouchState.getLastDownY()); Selection.setSelection((Spannable) mTextView.getText(), offset); Loading Loading @@ -5135,6 +5145,37 @@ public class Editor { private float mLastDownRawX, mLastDownRawY; private Runnable mHider; // Members for fake-dismiss effect in touch through mode. // It is to make InsertionHandleView can receive the MOVE/UP events after calling dismiss(), // which could happen in case of long-press (making selection will dismiss the insertion // handle). // Whether the finger is down and hasn't been up yet. private boolean mIsTouchDown = false; // Whether the popup window is in the invisible state and will be dismissed when finger up. private boolean mPendingDismissOnUp = false; // The alpha value of the drawable. private final int mDrawableOpacity = 255; // Members for toggling the insertion menu in touch through mode. // The coordinate for the touch down event, which is used for transforming the coordinates // of the events to the text view. private float mTouchDownX; private float mTouchDownY; // The cursor offset when touch down. This is to detect whether the cursor is moved when // finger move/up. private int mOffsetDown; // Whether the cursor offset has been changed on the move/up events. private boolean mOffsetChanged; // Whether it is in insertion action mode when finger down. private boolean mIsInActionMode; // The timestamp for the last up event, which is used for double tap detection. private long mLastUpTime; // The text height of the font of the text view, which is used to calculate the Y coordinate // of the touch through events. private float mTextHeight; public InsertionHandleView(Drawable drawable) { super(drawable, drawable, com.android.internal.R.id.insertion_handle); } Loading Loading @@ -5190,6 +5231,11 @@ public class Editor { @Override public boolean onTouchEvent(MotionEvent ev) { if (mCursorControlEnabled && FLAG_ENABLE_CURSOR_DRAG) { // Should only enable touch through when cursor drag is enabled. // Otherwise the insertion handle view cannot be moved. return touchThrough(ev); } final boolean result = super.onTouchEvent(ev); switch (ev.getActionMasked()) { Loading Loading @@ -5235,6 +5281,115 @@ public class Editor { return result; } // Handles the touch events in touch through mode. private boolean touchThrough(MotionEvent ev) { final int actionType = ev.getActionMasked(); switch (actionType) { case MotionEvent.ACTION_DOWN: mIsTouchDown = true; mOffsetChanged = false; mOffsetDown = mTextView.getSelectionStart(); mTouchDownX = ev.getX(); mTouchDownY = ev.getY(); mIsInActionMode = mTextActionMode != null; if (ev.getEventTime() - mLastUpTime < ViewConfiguration.getDoubleTapTimeout()) { stopTextActionMode(); // Avoid crash when double tap and drag backwards. } final Paint.FontMetrics fontMetrics = mTextView.getPaint().getFontMetrics(); mTextHeight = fontMetrics.descent - fontMetrics.ascent; mTouchState.setIsOnHandle(true); break; case MotionEvent.ACTION_UP: mLastUpTime = ev.getEventTime(); break; } // Performs the touch through by forward the events to the text view. boolean ret = mTextView.onTouchEvent(transformEventForTouchThrough(ev)); if (actionType == MotionEvent.ACTION_UP || actionType == MotionEvent.ACTION_CANCEL) { mIsTouchDown = false; if (mPendingDismissOnUp) { dismiss(); } mTouchState.setIsOnHandle(false); } // Checks for cursor offset change. if (!mOffsetChanged) { int start = mTextView.getSelectionStart(); int end = mTextView.getSelectionEnd(); if (start != end || mOffsetDown != start) { mOffsetChanged = true; } } // Toggling the insertion action mode on finger up. if (!mOffsetChanged && actionType == MotionEvent.ACTION_UP) { if (mIsInActionMode) { stopTextActionMode(); } else { startInsertionActionMode(); } } return ret; } private MotionEvent transformEventForTouchThrough(MotionEvent ev) { // Transforms the touch events to screen coordinates. // And also shift up to make the hit point is on the text. // Note: // - The revised X should reflect the distance to the horizontal center of touch down. // - The revised Y should be at the top of the text. Matrix m = new Matrix(); m.setTranslate(ev.getRawX() - ev.getX() + (getMeasuredWidth() >> 1) - mTouchDownX, ev.getRawY() - ev.getY() - mTouchDownY - mTextHeight); ev.transform(m); // Transforms the touch events to text view coordinates. mTextView.toLocalMotionEvent(ev); if (TextView.DEBUG_CURSOR) { logCursor("InsertionHandleView#transformEventForTouchThrough", "Touch through: %d, (%f, %f)", ev.getAction(), ev.getX(), ev.getY()); } return ev; } @Override public boolean isShowing() { if (mPendingDismissOnUp) { return false; } return super.isShowing(); } @Override public void show() { super.show(); mPendingDismissOnUp = false; mDrawable.setAlpha(mDrawableOpacity); } @Override public void dismiss() { if (mIsTouchDown) { if (TextView.DEBUG_CURSOR) { logCursor("InsertionHandleView#dismiss", "Suppressed the real dismiss, only become invisible"); } mPendingDismissOnUp = true; mDrawable.setAlpha(0); } else { super.dismiss(); mPendingDismissOnUp = false; } } @Override protected void updateDrawable(final boolean updateDrawableWhenDragging) { super.updateDrawable(updateDrawableWhenDragging); mDrawable.setAlpha(mDrawableOpacity); } @Override public int getCurrentCursorOffset() { return mTextView.getSelectionStart(); Loading Loading @@ -6039,8 +6194,8 @@ public class Editor { eventX, eventY); // Double tap detection if (mTouchState.isMultiTapInSameArea() && (isMouse || isPositionOnText(eventX, eventY))) { if (mTouchState.isMultiTapInSameArea() && (isMouse || mTouchState.isOnHandle() || isPositionOnText(eventX, eventY))) { if (TextView.DEBUG_CURSOR) { logCursor("SelectionModifierCursorController: onTouchEvent", "ACTION_DOWN: select and start drag"); Loading core/java/android/widget/EditorTouchState.java +10 −1 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ public class EditorTouchState { private long mLastDownMillis; private float mLastUpX, mLastUpY; private long mLastUpMillis; private boolean mIsOnHandle; @IntDef({MultiTapStatus.NONE, MultiTapStatus.FIRST_TAP, MultiTapStatus.DOUBLE_TAP, MultiTapStatus.TRIPLE_CLICK}) Loading Loading @@ -98,7 +99,15 @@ public class EditorTouchState { } public boolean isDragCloseToVertical() { return mIsDragCloseToVertical; return mIsDragCloseToVertical && !mIsOnHandle; } public void setIsOnHandle(boolean onHandle) { mIsOnHandle = onHandle; } public boolean isOnHandle() { return mIsOnHandle; } /** Loading core/tests/coretests/src/android/widget/TextViewActivityTest.java +109 −0 Original line number Diff line number Diff line Loading @@ -27,9 +27,13 @@ import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloat import static android.widget.espresso.TextViewActions.Handle; import static android.widget.espresso.TextViewActions.clickOnTextAtIndex; import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex; import static android.widget.espresso.TextViewActions.doubleTapAndDragHandle; import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText; import static android.widget.espresso.TextViewActions.doubleTapHandle; import static android.widget.espresso.TextViewActions.dragHandle; import static android.widget.espresso.TextViewActions.longPressAndDragOnText; import static android.widget.espresso.TextViewActions.longPressAndDragHandle; import static android.widget.espresso.TextViewActions.longPressHandle; import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex; import static android.widget.espresso.TextViewAssertions.doesNotHaveStyledText; import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex; Loading Loading @@ -510,6 +514,111 @@ public class TextViewActivityTest { onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f"))); } @Test public void testInsertionHandle_touchThrough() { final TextView textView = mActivity.findViewById(R.id.textview); boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled(); boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG; textView.getEditorForTesting().setCursorControlEnabled(true); Editor.FLAG_ENABLE_CURSOR_DRAG = true; testInsertionHandle(); testInsertionHandle_multiLine(); textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled); Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled; } @Test public void testInsertionHandle_longPressToSelect() { // This test only makes sense when Cursor Control flag is enabled. final TextView textView = mActivity.findViewById(R.id.textview); boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled(); boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG; textView.getEditorForTesting().setCursorControlEnabled(true); Editor.FLAG_ENABLE_CURSOR_DRAG = true; final String text = "hello the world"; onView(withId(R.id.textview)).perform(replaceText(text)); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); onHandleView(com.android.internal.R.id.insertion_handle).perform(longPressHandle(textView)); onView(withId(R.id.textview)).check(hasSelection("world")); textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled); Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled; } @Test public void testInsertionHandle_longPressAndDragToSelect() { // This test only makes sense when Cursor Control flag is enabled. final TextView textView = mActivity.findViewById(R.id.textview); boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled(); boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG; textView.getEditorForTesting().setCursorControlEnabled(true); Editor.FLAG_ENABLE_CURSOR_DRAG = true; final String text = "hello the world"; onView(withId(R.id.textview)).perform(replaceText(text)); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); onHandleView(com.android.internal.R.id.insertion_handle) .perform(longPressAndDragHandle(textView, Handle.INSERTION, text.indexOf('t'))); onView(withId(R.id.textview)).check(hasSelection("the world")); textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled); Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled; } @Test public void testInsertionHandle_doubleTapToSelect() { // This test only makes sense when Cursor Control flag is enabled. final TextView textView = mActivity.findViewById(R.id.textview); boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled(); boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG; textView.getEditorForTesting().setCursorControlEnabled(true); Editor.FLAG_ENABLE_CURSOR_DRAG = true; final String text = "hello the world"; onView(withId(R.id.textview)).perform(replaceText(text)); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); onHandleView(com.android.internal.R.id.insertion_handle).perform(doubleTapHandle(textView)); onView(withId(R.id.textview)).check(hasSelection("world")); textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled); Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled; } @Test public void testInsertionHandle_doubleTapAndDragToSelect() { // This test only makes sense when Cursor Control flag is enabled. final TextView textView = mActivity.findViewById(R.id.textview); boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled(); boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG; textView.getEditorForTesting().setCursorControlEnabled(true); Editor.FLAG_ENABLE_CURSOR_DRAG = true; final String text = "hello the world"; onView(withId(R.id.textview)).perform(replaceText(text)); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); onHandleView(com.android.internal.R.id.insertion_handle) .perform(doubleTapAndDragHandle(textView, Handle.INSERTION, text.indexOf('t'))); onView(withId(R.id.textview)).check(hasSelection("the world")); textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled); Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled; } @Test public void testSelectionHandles() { final String text = "abcd efg hijk lmn"; Loading core/tests/coretests/src/android/widget/espresso/TextViewActions.java +80 −0 Original line number Diff line number Diff line Loading @@ -198,6 +198,86 @@ public final class TextViewActions { TextView.class)); } /** * Returns an action that long presses then drags on handle from the current position to * endIndex on the TextView.<br> * <br> * View constraints: * <ul> * <li>must be a TextView's drag-handle displayed on screen * <ul> * * @param textView TextView the handle is on * @param handleType Type of the handle * @param endIndex The index of the TextView's text to end the drag at */ public static ViewAction longPressAndDragHandle(TextView textView, Handle handleType, int endIndex) { return actionWithAssertions( new DragAction( DragAction.Drag.LONG_PRESS, new CurrentHandleCoordinates(textView), new HandleCoordinates(textView, handleType, endIndex, true), Press.FINGER, Editor.HandleView.class)); } /** * Returns an action that long presses on the current handle.<br> * <br> * View constraints: * <ul> * <li>must be a TextView's drag-handle displayed on screen * <ul> * * @param textView TextView the handle is on */ public static ViewAction longPressHandle(TextView textView) { return actionWithAssertions( new ViewClickAction(Tap.LONG, new CurrentHandleCoordinates(textView), Press.FINGER)); } /** * Returns an action that double tap then drags on handle from the current position to * endIndex on the TextView.<br> * <br> * View constraints: * <ul> * <li>must be a TextView's drag-handle displayed on screen * <ul> * * @param textView TextView the handle is on * @param handleType Type of the handle * @param endIndex The index of the TextView's text to end the drag at */ public static ViewAction doubleTapAndDragHandle(TextView textView, Handle handleType, int endIndex) { return actionWithAssertions( new DragAction( DragAction.Drag.DOUBLE_TAP, new CurrentHandleCoordinates(textView), new HandleCoordinates(textView, handleType, endIndex, true), Press.FINGER, Editor.HandleView.class)); } /** * Returns an action that double tap on the current handle.<br> * <br> * View constraints: * <ul> * <li>must be a TextView's drag-handle displayed on screen * <ul> * * @param textView TextView the handle is on */ public static ViewAction doubleTapHandle(TextView textView) { return actionWithAssertions( new ViewClickAction(Tap.DOUBLE, new CurrentHandleCoordinates(textView), Press.FINGER)); } /** * Returns an action that double taps then drags on text from startIndex to endIndex on the * TextView.<br> Loading Loading
core/java/android/widget/Editor.java +159 −4 Original line number Diff line number Diff line Loading @@ -387,7 +387,7 @@ public class Editor { // Specifies whether the cursor control feature set is enabled. // This can only be true if the text view is editable. private final boolean mCursorControlEnabled; private boolean mCursorControlEnabled; Editor(TextView textView) { mTextView = textView; Loading @@ -411,6 +411,16 @@ public class Editor { } } @VisibleForTesting public void setCursorControlEnabled(boolean enabled) { mCursorControlEnabled = enabled; } @VisibleForTesting public boolean getCursorControlEnabled() { return mCursorControlEnabled; } ParcelableParcel saveInstanceState() { ParcelableParcel state = new ParcelableParcel(getClass().getClassLoader()); Parcel parcel = state.getParcel(); Loading Loading @@ -1204,7 +1214,7 @@ public class Editor { } // Long press in empty space moves cursor and starts the insertion action mode. if (!handled && !isPositionOnText(mTouchState.getLastDownX(), mTouchState.getLastDownY()) && mInsertionControllerEnabled) { && !mTouchState.isOnHandle() && mInsertionControllerEnabled) { final int offset = mTextView.getOffsetForPosition(mTouchState.getLastDownX(), mTouchState.getLastDownY()); Selection.setSelection((Spannable) mTextView.getText(), offset); Loading Loading @@ -5135,6 +5145,37 @@ public class Editor { private float mLastDownRawX, mLastDownRawY; private Runnable mHider; // Members for fake-dismiss effect in touch through mode. // It is to make InsertionHandleView can receive the MOVE/UP events after calling dismiss(), // which could happen in case of long-press (making selection will dismiss the insertion // handle). // Whether the finger is down and hasn't been up yet. private boolean mIsTouchDown = false; // Whether the popup window is in the invisible state and will be dismissed when finger up. private boolean mPendingDismissOnUp = false; // The alpha value of the drawable. private final int mDrawableOpacity = 255; // Members for toggling the insertion menu in touch through mode. // The coordinate for the touch down event, which is used for transforming the coordinates // of the events to the text view. private float mTouchDownX; private float mTouchDownY; // The cursor offset when touch down. This is to detect whether the cursor is moved when // finger move/up. private int mOffsetDown; // Whether the cursor offset has been changed on the move/up events. private boolean mOffsetChanged; // Whether it is in insertion action mode when finger down. private boolean mIsInActionMode; // The timestamp for the last up event, which is used for double tap detection. private long mLastUpTime; // The text height of the font of the text view, which is used to calculate the Y coordinate // of the touch through events. private float mTextHeight; public InsertionHandleView(Drawable drawable) { super(drawable, drawable, com.android.internal.R.id.insertion_handle); } Loading Loading @@ -5190,6 +5231,11 @@ public class Editor { @Override public boolean onTouchEvent(MotionEvent ev) { if (mCursorControlEnabled && FLAG_ENABLE_CURSOR_DRAG) { // Should only enable touch through when cursor drag is enabled. // Otherwise the insertion handle view cannot be moved. return touchThrough(ev); } final boolean result = super.onTouchEvent(ev); switch (ev.getActionMasked()) { Loading Loading @@ -5235,6 +5281,115 @@ public class Editor { return result; } // Handles the touch events in touch through mode. private boolean touchThrough(MotionEvent ev) { final int actionType = ev.getActionMasked(); switch (actionType) { case MotionEvent.ACTION_DOWN: mIsTouchDown = true; mOffsetChanged = false; mOffsetDown = mTextView.getSelectionStart(); mTouchDownX = ev.getX(); mTouchDownY = ev.getY(); mIsInActionMode = mTextActionMode != null; if (ev.getEventTime() - mLastUpTime < ViewConfiguration.getDoubleTapTimeout()) { stopTextActionMode(); // Avoid crash when double tap and drag backwards. } final Paint.FontMetrics fontMetrics = mTextView.getPaint().getFontMetrics(); mTextHeight = fontMetrics.descent - fontMetrics.ascent; mTouchState.setIsOnHandle(true); break; case MotionEvent.ACTION_UP: mLastUpTime = ev.getEventTime(); break; } // Performs the touch through by forward the events to the text view. boolean ret = mTextView.onTouchEvent(transformEventForTouchThrough(ev)); if (actionType == MotionEvent.ACTION_UP || actionType == MotionEvent.ACTION_CANCEL) { mIsTouchDown = false; if (mPendingDismissOnUp) { dismiss(); } mTouchState.setIsOnHandle(false); } // Checks for cursor offset change. if (!mOffsetChanged) { int start = mTextView.getSelectionStart(); int end = mTextView.getSelectionEnd(); if (start != end || mOffsetDown != start) { mOffsetChanged = true; } } // Toggling the insertion action mode on finger up. if (!mOffsetChanged && actionType == MotionEvent.ACTION_UP) { if (mIsInActionMode) { stopTextActionMode(); } else { startInsertionActionMode(); } } return ret; } private MotionEvent transformEventForTouchThrough(MotionEvent ev) { // Transforms the touch events to screen coordinates. // And also shift up to make the hit point is on the text. // Note: // - The revised X should reflect the distance to the horizontal center of touch down. // - The revised Y should be at the top of the text. Matrix m = new Matrix(); m.setTranslate(ev.getRawX() - ev.getX() + (getMeasuredWidth() >> 1) - mTouchDownX, ev.getRawY() - ev.getY() - mTouchDownY - mTextHeight); ev.transform(m); // Transforms the touch events to text view coordinates. mTextView.toLocalMotionEvent(ev); if (TextView.DEBUG_CURSOR) { logCursor("InsertionHandleView#transformEventForTouchThrough", "Touch through: %d, (%f, %f)", ev.getAction(), ev.getX(), ev.getY()); } return ev; } @Override public boolean isShowing() { if (mPendingDismissOnUp) { return false; } return super.isShowing(); } @Override public void show() { super.show(); mPendingDismissOnUp = false; mDrawable.setAlpha(mDrawableOpacity); } @Override public void dismiss() { if (mIsTouchDown) { if (TextView.DEBUG_CURSOR) { logCursor("InsertionHandleView#dismiss", "Suppressed the real dismiss, only become invisible"); } mPendingDismissOnUp = true; mDrawable.setAlpha(0); } else { super.dismiss(); mPendingDismissOnUp = false; } } @Override protected void updateDrawable(final boolean updateDrawableWhenDragging) { super.updateDrawable(updateDrawableWhenDragging); mDrawable.setAlpha(mDrawableOpacity); } @Override public int getCurrentCursorOffset() { return mTextView.getSelectionStart(); Loading Loading @@ -6039,8 +6194,8 @@ public class Editor { eventX, eventY); // Double tap detection if (mTouchState.isMultiTapInSameArea() && (isMouse || isPositionOnText(eventX, eventY))) { if (mTouchState.isMultiTapInSameArea() && (isMouse || mTouchState.isOnHandle() || isPositionOnText(eventX, eventY))) { if (TextView.DEBUG_CURSOR) { logCursor("SelectionModifierCursorController: onTouchEvent", "ACTION_DOWN: select and start drag"); Loading
core/java/android/widget/EditorTouchState.java +10 −1 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ public class EditorTouchState { private long mLastDownMillis; private float mLastUpX, mLastUpY; private long mLastUpMillis; private boolean mIsOnHandle; @IntDef({MultiTapStatus.NONE, MultiTapStatus.FIRST_TAP, MultiTapStatus.DOUBLE_TAP, MultiTapStatus.TRIPLE_CLICK}) Loading Loading @@ -98,7 +99,15 @@ public class EditorTouchState { } public boolean isDragCloseToVertical() { return mIsDragCloseToVertical; return mIsDragCloseToVertical && !mIsOnHandle; } public void setIsOnHandle(boolean onHandle) { mIsOnHandle = onHandle; } public boolean isOnHandle() { return mIsOnHandle; } /** Loading
core/tests/coretests/src/android/widget/TextViewActivityTest.java +109 −0 Original line number Diff line number Diff line Loading @@ -27,9 +27,13 @@ import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloat import static android.widget.espresso.TextViewActions.Handle; import static android.widget.espresso.TextViewActions.clickOnTextAtIndex; import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex; import static android.widget.espresso.TextViewActions.doubleTapAndDragHandle; import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText; import static android.widget.espresso.TextViewActions.doubleTapHandle; import static android.widget.espresso.TextViewActions.dragHandle; import static android.widget.espresso.TextViewActions.longPressAndDragOnText; import static android.widget.espresso.TextViewActions.longPressAndDragHandle; import static android.widget.espresso.TextViewActions.longPressHandle; import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex; import static android.widget.espresso.TextViewAssertions.doesNotHaveStyledText; import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex; Loading Loading @@ -510,6 +514,111 @@ public class TextViewActivityTest { onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f"))); } @Test public void testInsertionHandle_touchThrough() { final TextView textView = mActivity.findViewById(R.id.textview); boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled(); boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG; textView.getEditorForTesting().setCursorControlEnabled(true); Editor.FLAG_ENABLE_CURSOR_DRAG = true; testInsertionHandle(); testInsertionHandle_multiLine(); textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled); Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled; } @Test public void testInsertionHandle_longPressToSelect() { // This test only makes sense when Cursor Control flag is enabled. final TextView textView = mActivity.findViewById(R.id.textview); boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled(); boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG; textView.getEditorForTesting().setCursorControlEnabled(true); Editor.FLAG_ENABLE_CURSOR_DRAG = true; final String text = "hello the world"; onView(withId(R.id.textview)).perform(replaceText(text)); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); onHandleView(com.android.internal.R.id.insertion_handle).perform(longPressHandle(textView)); onView(withId(R.id.textview)).check(hasSelection("world")); textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled); Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled; } @Test public void testInsertionHandle_longPressAndDragToSelect() { // This test only makes sense when Cursor Control flag is enabled. final TextView textView = mActivity.findViewById(R.id.textview); boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled(); boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG; textView.getEditorForTesting().setCursorControlEnabled(true); Editor.FLAG_ENABLE_CURSOR_DRAG = true; final String text = "hello the world"; onView(withId(R.id.textview)).perform(replaceText(text)); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); onHandleView(com.android.internal.R.id.insertion_handle) .perform(longPressAndDragHandle(textView, Handle.INSERTION, text.indexOf('t'))); onView(withId(R.id.textview)).check(hasSelection("the world")); textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled); Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled; } @Test public void testInsertionHandle_doubleTapToSelect() { // This test only makes sense when Cursor Control flag is enabled. final TextView textView = mActivity.findViewById(R.id.textview); boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled(); boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG; textView.getEditorForTesting().setCursorControlEnabled(true); Editor.FLAG_ENABLE_CURSOR_DRAG = true; final String text = "hello the world"; onView(withId(R.id.textview)).perform(replaceText(text)); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); onHandleView(com.android.internal.R.id.insertion_handle).perform(doubleTapHandle(textView)); onView(withId(R.id.textview)).check(hasSelection("world")); textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled); Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled; } @Test public void testInsertionHandle_doubleTapAndDragToSelect() { // This test only makes sense when Cursor Control flag is enabled. final TextView textView = mActivity.findViewById(R.id.textview); boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled(); boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG; textView.getEditorForTesting().setCursorControlEnabled(true); Editor.FLAG_ENABLE_CURSOR_DRAG = true; final String text = "hello the world"; onView(withId(R.id.textview)).perform(replaceText(text)); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); onHandleView(com.android.internal.R.id.insertion_handle) .perform(doubleTapAndDragHandle(textView, Handle.INSERTION, text.indexOf('t'))); onView(withId(R.id.textview)).check(hasSelection("the world")); textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled); Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled; } @Test public void testSelectionHandles() { final String text = "abcd efg hijk lmn"; Loading
core/tests/coretests/src/android/widget/espresso/TextViewActions.java +80 −0 Original line number Diff line number Diff line Loading @@ -198,6 +198,86 @@ public final class TextViewActions { TextView.class)); } /** * Returns an action that long presses then drags on handle from the current position to * endIndex on the TextView.<br> * <br> * View constraints: * <ul> * <li>must be a TextView's drag-handle displayed on screen * <ul> * * @param textView TextView the handle is on * @param handleType Type of the handle * @param endIndex The index of the TextView's text to end the drag at */ public static ViewAction longPressAndDragHandle(TextView textView, Handle handleType, int endIndex) { return actionWithAssertions( new DragAction( DragAction.Drag.LONG_PRESS, new CurrentHandleCoordinates(textView), new HandleCoordinates(textView, handleType, endIndex, true), Press.FINGER, Editor.HandleView.class)); } /** * Returns an action that long presses on the current handle.<br> * <br> * View constraints: * <ul> * <li>must be a TextView's drag-handle displayed on screen * <ul> * * @param textView TextView the handle is on */ public static ViewAction longPressHandle(TextView textView) { return actionWithAssertions( new ViewClickAction(Tap.LONG, new CurrentHandleCoordinates(textView), Press.FINGER)); } /** * Returns an action that double tap then drags on handle from the current position to * endIndex on the TextView.<br> * <br> * View constraints: * <ul> * <li>must be a TextView's drag-handle displayed on screen * <ul> * * @param textView TextView the handle is on * @param handleType Type of the handle * @param endIndex The index of the TextView's text to end the drag at */ public static ViewAction doubleTapAndDragHandle(TextView textView, Handle handleType, int endIndex) { return actionWithAssertions( new DragAction( DragAction.Drag.DOUBLE_TAP, new CurrentHandleCoordinates(textView), new HandleCoordinates(textView, handleType, endIndex, true), Press.FINGER, Editor.HandleView.class)); } /** * Returns an action that double tap on the current handle.<br> * <br> * View constraints: * <ul> * <li>must be a TextView's drag-handle displayed on screen * <ul> * * @param textView TextView the handle is on */ public static ViewAction doubleTapHandle(TextView textView) { return actionWithAssertions( new ViewClickAction(Tap.DOUBLE, new CurrentHandleCoordinates(textView), Press.FINGER)); } /** * Returns an action that double taps then drags on text from startIndex to endIndex on the * TextView.<br> Loading