Add selection handle dragging tests.
- Set an id for each HandleView to access the hanlde.
- Rename DragOnTextViewActions to DragAction to use it for
dragging handles.
- Introduce HandleCoordinates to provide proper coordinates
for handles.
More tests will be added in following CLs.
Bug: 25730231
Change-Id: I9276bf2f983983ec9aae0ddcf674d3dcee566892
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2fabe33..8cd4de3 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -3494,7 +3494,8 @@
}
}
- private abstract class HandleView extends View implements TextViewPositionListener {
+ @VisibleForTesting
+ public abstract class HandleView extends View implements TextViewPositionListener {
protected Drawable mDrawable;
protected Drawable mDrawableLtr;
protected Drawable mDrawableRtl;
@@ -3525,8 +3526,9 @@
// a different line.
protected int mPreviousLineTouched = UNSET_LINE;
- public HandleView(Drawable drawableLtr, Drawable drawableRtl) {
+ private HandleView(Drawable drawableLtr, Drawable drawableRtl, final int id) {
super(mTextView.getContext());
+ setId(id);
mContainer = new PopupWindow(mTextView.getContext(), null,
com.android.internal.R.attr.textSelectHandleWindowStyle);
mContainer.setSplitTouchEnabled(true);
@@ -3888,7 +3890,7 @@
private Runnable mHider;
public InsertionHandleView(Drawable drawable) {
- super(drawable, drawable);
+ super(drawable, drawable, com.android.internal.R.id.insertion_handle);
}
@Override
@@ -4073,7 +4075,7 @@
private final int[] mTextViewLocation = new int[2];
public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
- super(drawableLtr, drawableRtl);
+ super(drawableLtr, drawableRtl, com.android.internal.R.id.selection_start_handle);
ViewConfiguration viewConfiguration = ViewConfiguration.get(
mTextView.getContext());
mTextViewEdgeSlop = viewConfiguration.getScaledTouchSlop() * 4;
@@ -4313,7 +4315,7 @@
private final int[] mTextViewLocation = new int[2];
public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
- super(drawableLtr, drawableRtl);
+ super(drawableLtr, drawableRtl, com.android.internal.R.id.selection_end_handle);
ViewConfiguration viewConfiguration = ViewConfiguration.get(
mTextView.getContext());
mTextViewEdgeSlop = viewConfiguration.getScaledTouchSlop() * 4;
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 695dafa..a348767 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -94,6 +94,9 @@
<item type="id" name="redo" />
<item type="id" name="replaceText" />
<item type="id" name="shareText" />
+ <item type="id" name="selection_start_handle" />
+ <item type="id" name="selection_end_handle" />
+ <item type="id" name="insertion_handle" />
<item type="id" name="floating_toolbar_menu_item_image_button" />
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SHOW_ON_SCREEN}. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cda7faa..80e5668 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -221,6 +221,9 @@
<java-symbol type="id" name="profile_badge_line2" />
<java-symbol type="id" name="profile_badge_line3" />
<java-symbol type="id" name="transitionPosition" />
+ <java-symbol type="id" name="selection_start_handle" />
+ <java-symbol type="id" name="selection_end_handle" />
+ <java-symbol type="id" name="insertion_handle" />
<java-symbol type="attr" name="actionModeShareDrawable" />
<java-symbol type="attr" name="alertDialogCenterButtons" />
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index bb51570..b03552f 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -19,6 +19,8 @@
import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText;
import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex;
+import static android.widget.espresso.TextViewActions.dragHandle;
+import static android.widget.espresso.TextViewActions.Handle;
import static android.widget.espresso.TextViewActions.longPressAndDragOnText;
import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex;
import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
@@ -29,11 +31,16 @@
import static android.support.test.espresso.action.ViewActions.pressKey;
import static android.support.test.espresso.action.ViewActions.typeTextIntoFocusedView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
+import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static org.hamcrest.Matchers.allOf;
import com.android.frameworks.coretests.R;
+import android.support.test.espresso.ViewInteraction;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
@@ -157,4 +164,26 @@
Thread.sleep(100);
assertFloatingToolbarIsDisplayed(getActivity());
}
+
+ private static ViewInteraction onHandleView(int id) {
+ return onView(allOf(withId(id), isAssignableFrom(Editor.HandleView.class)))
+ .inRoot(withDecorView(hasDescendant(withId(id))));
+ }
+
+ @SmallTest
+ public void testSelectionHandles() throws Exception {
+ final String text = "abcd efg hijk lmn";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+ onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('f')));
+
+ final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
+ onView(withId(R.id.textview)).check(hasSelection("abcd efg"));
+
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('k') + 1));
+ onView(withId(R.id.textview)).check(hasSelection("abcd efg hijk"));
+ }
}
diff --git a/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java b/core/tests/coretests/src/android/widget/espresso/DragAction.java
similarity index 87%
rename from core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java
rename to core/tests/coretests/src/android/widget/espresso/DragAction.java
index 9ff8e82..07a2067 100644
--- a/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/DragAction.java
@@ -34,26 +34,25 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
-import android.widget.TextView;
import org.hamcrest.Matcher;
/**
- * Drags on text in a TextView using touch events.<br>
+ * Drags on a View using touch events.<br>
* <br>
* View constraints:
* <ul>
- * <li>must be a TextView displayed on screen
+ * <li>must be displayed on screen
* <ul>
*/
-public final class DragOnTextViewActions implements ViewAction {
+public final class DragAction implements ViewAction {
public interface Dragger extends Swiper {
UiController wrapUiController(UiController uiController);
}
/**
- * Executes different "drag on text" types to given positions.
+ * Executes different drag types to given positions.
*/
public enum Drag implements Dragger {
@@ -82,7 +81,7 @@
@Override
public String toString() {
- return "mouse down and drag to select";
+ return "mouse down and drag";
}
@Override
@@ -92,6 +91,35 @@
},
/**
+ * Starts a drag with a tap.
+ */
+ TAP {
+ private DownMotionPerformer downMotion = new DownMotionPerformer() {
+ @Override
+ public MotionEvent perform(
+ UiController uiController, float[] coordinates, float[] precision) {
+ MotionEvent downEvent = MotionEvents.sendDown(
+ uiController, coordinates, precision)
+ .down;
+ return downEvent;
+ }
+ };
+
+ @Override
+ public Status sendSwipe(
+ UiController uiController,
+ float[] startCoordinates, float[] endCoordinates, float[] precision) {
+ return sendLinearDrag(
+ uiController, downMotion, startCoordinates, endCoordinates, precision);
+ }
+
+ @Override
+ public String toString() {
+ return "tap and drag";
+ }
+ },
+
+ /**
* Starts a drag with a long-press.
*/
LONG_PRESS {
@@ -121,7 +149,7 @@
@Override
public String toString() {
- return "long press and drag to select";
+ return "long press and drag";
}
},
@@ -166,7 +194,7 @@
@Override
public String toString() {
- return "double-tap and drag to select";
+ return "double-tap and drag";
}
};
@@ -258,22 +286,25 @@
private final CoordinatesProvider mStartCoordinatesProvider;
private final CoordinatesProvider mEndCoordinatesProvider;
private final PrecisionDescriber mPrecisionDescriber;
+ private final Class<? extends View> mViewClass;
- public DragOnTextViewActions(
+ public DragAction(
Dragger dragger,
CoordinatesProvider startCoordinatesProvider,
CoordinatesProvider endCoordinatesProvider,
- PrecisionDescriber precisionDescriber) {
+ PrecisionDescriber precisionDescriber,
+ Class<? extends View> viewClass) {
mDragger = checkNotNull(dragger);
mStartCoordinatesProvider = checkNotNull(startCoordinatesProvider);
mEndCoordinatesProvider = checkNotNull(endCoordinatesProvider);
mPrecisionDescriber = checkNotNull(precisionDescriber);
+ mViewClass = viewClass;
}
@Override
@SuppressWarnings("unchecked")
public Matcher<View> getConstraints() {
- return allOf(isCompletelyDisplayed(), isAssignableFrom(TextView.class));
+ return allOf(isCompletelyDisplayed(), isAssignableFrom(mViewClass));
}
@Override
diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
index 4f5a72b..b84afff 100644
--- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
@@ -17,7 +17,7 @@
package android.widget.espresso;
import static android.support.test.espresso.action.ViewActions.actionWithAssertions;
-
+import android.graphics.Rect;
import android.support.test.espresso.PerformException;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.action.CoordinatesProvider;
@@ -27,6 +27,7 @@
import android.support.test.espresso.util.HumanReadables;
import android.text.Layout;
import android.view.View;
+import android.widget.Editor;
import android.widget.TextView;
/**
@@ -95,11 +96,12 @@
*/
public static ViewAction longPressAndDragOnText(int startIndex, int endIndex) {
return actionWithAssertions(
- new DragOnTextViewActions(
- DragOnTextViewActions.Drag.LONG_PRESS,
+ new DragAction(
+ DragAction.Drag.LONG_PRESS,
new TextCoordinates(startIndex),
new TextCoordinates(endIndex),
- Press.FINGER));
+ Press.FINGER,
+ TextView.class));
}
/**
@@ -116,11 +118,12 @@
*/
public static ViewAction doubleTapAndDragOnText(int startIndex, int endIndex) {
return actionWithAssertions(
- new DragOnTextViewActions(
- DragOnTextViewActions.Drag.DOUBLE_TAP,
+ new DragAction(
+ DragAction.Drag.DOUBLE_TAP,
new TextCoordinates(startIndex),
new TextCoordinates(endIndex),
- Press.FINGER));
+ Press.FINGER,
+ TextView.class));
}
/**
@@ -137,11 +140,89 @@
*/
public static ViewAction mouseDragOnText(int startIndex, int endIndex) {
return actionWithAssertions(
- new DragOnTextViewActions(
- DragOnTextViewActions.Drag.MOUSE_DOWN,
+ new DragAction(
+ DragAction.Drag.MOUSE_DOWN,
new TextCoordinates(startIndex),
new TextCoordinates(endIndex),
- Press.PINPOINT));
+ Press.PINPOINT,
+ TextView.class));
+ }
+
+ public enum Handle {
+ SELECTION_START,
+ SELECTION_END,
+ INSERTION
+ };
+
+ /**
+ * Returns an action that tap then drags on the 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 dragHandle(TextView textView, Handle handleType, int endIndex) {
+ final int currentOffset = handleType == Handle.SELECTION_START ?
+ textView.getSelectionStart() : textView.getSelectionEnd();
+ return actionWithAssertions(
+ new DragAction(
+ DragAction.Drag.TAP,
+ new HandleCoordinates(textView, handleType, currentOffset),
+ new HandleCoordinates(textView, handleType, endIndex),
+ Press.FINGER,
+ Editor.HandleView.class));
+ }
+
+ /**
+ * A provider of the x, y coordinates of the handle that points the specified text index in a
+ * text view.
+ */
+ private static final class HandleCoordinates implements CoordinatesProvider {
+ private final TextView mTextView;
+ private final Handle mHandleType;
+ private final int mIndex;
+ private final String mActionDescription;
+
+ public HandleCoordinates(TextView textView, Handle handleType, int index) {
+ mTextView = textView;
+ mHandleType = handleType;
+ mIndex = index;
+ mActionDescription = "Could not locate " + handleType.toString()
+ + " handle that points text index: " + index;
+ }
+
+ @Override
+ public float[] calculateCoordinates(View view) {
+ try {
+ return locateHandlePointsTextIndex(view);
+ } catch (StringIndexOutOfBoundsException e) {
+ throw new PerformException.Builder()
+ .withActionDescription(mActionDescription)
+ .withViewDescription(HumanReadables.describe(view))
+ .withCause(e)
+ .build();
+ }
+ }
+
+ private float[] locateHandlePointsTextIndex(View view) {
+ final int currentOffset = mHandleType == Handle.SELECTION_START ?
+ mTextView.getSelectionStart() : mTextView.getSelectionEnd();
+ final float[] currentCoordinates =
+ (new TextCoordinates(currentOffset)).calculateCoordinates(mTextView);
+ final float[] targetCoordinates =
+ (new TextCoordinates(mIndex)).calculateCoordinates(mTextView);
+ final Rect bounds = new Rect();
+ view.getBoundsOnScreen(bounds);
+ final float diffX = bounds.centerX() - currentCoordinates[0];
+ final float diffY = bounds.centerY() - currentCoordinates[1];
+ return new float[] {targetCoordinates[0] + diffX, targetCoordinates[1] + diffY};
+ }
}
/**