diff --git a/api/current.txt b/api/current.txt index 78ac7d3e0c67a..ef8cbb1df4b35 100644 --- a/api/current.txt +++ b/api/current.txt @@ -32297,6 +32297,7 @@ package android.view { method public final float getY(); method public final float getY(int); method public final float getYPrecision(); + method public final boolean isButtonPressed(int); method public static android.view.MotionEvent obtain(long, long, int, int, android.view.MotionEvent.PointerProperties[], android.view.MotionEvent.PointerCoords[], int, int, float, float, int, int, int, int); method public static deprecated android.view.MotionEvent obtain(long, long, int, int, int[], android.view.MotionEvent.PointerCoords[], int, float, float, int, int, int, int); method public static android.view.MotionEvent obtain(long, long, int, float, float, float, float, int, float, float, int, int); diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index ba6f1d4ba07a1..fcc3a4007e420 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -20,6 +20,7 @@ import android.graphics.Rect; import android.text.Layout; import android.text.Selection; import android.text.Spannable; +import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -221,11 +222,16 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme return lineEnd(widget, buffer); } + private static boolean isTouchSelecting(boolean isMouse, Spannable buffer) { + return isMouse ? Touch.isActivelySelecting(buffer) : isSelecting(buffer); + } + @Override public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { int initialScrollX = -1; int initialScrollY = -1; final int action = event.getAction(); + final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE); if (action == MotionEvent.ACTION_UP) { initialScrollX = Touch.getInitialScrollX(widget, buffer); @@ -236,19 +242,30 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme if (widget.isFocused() && !widget.didTouchFocusSelect()) { if (action == MotionEvent.ACTION_DOWN) { - if (isSelecting(buffer)) { - int offset = widget.getOffsetForPosition(event.getX(), event.getY()); - - buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT); - - // Disallow intercepting of the touch events, so that - // users can scroll and select at the same time. - // without this, users would get booted out of select - // mode once the view detected it needed to scroll. - widget.getParent().requestDisallowInterceptTouchEvent(true); - } + // Capture the mouse pointer down location to ensure selection starts + // right under the mouse (and is not influenced by cursor location). + // The code below needs to run for mouse events. + // For touch events, the code should run only when selection is active. + if (isMouse || isTouchSelecting(isMouse, buffer)) { + int offset = widget.getOffsetForPosition(event.getX(), event.getY()); + buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT); + // Disallow intercepting of the touch events, so that + // users can scroll and select at the same time. + // without this, users would get booted out of select + // mode once the view detected it needed to scroll. + widget.getParent().requestDisallowInterceptTouchEvent(true); + } } else if (action == MotionEvent.ACTION_MOVE) { - if (isSelecting(buffer) && handled) { + + // Cursor can be active at any location in the text while mouse pointer can start + // selection from a totally different location. Use LAST_TAP_DOWN span to ensure + // text selection will start from mouse pointer location. + if (isMouse && Touch.isSelectionStarted(buffer)) { + int offset = buffer.getSpanStart(LAST_TAP_DOWN); + Selection.setSelection(buffer, offset); + } + + if (isTouchSelecting(isMouse, buffer) && handled) { // Before selecting, make sure we've moved out of the "slop". // handled will be true, if we're in select mode AND we're // OUT of the slop @@ -277,7 +294,7 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme } int offset = widget.getOffsetForPosition(event.getX(), event.getY()); - if (isSelecting(buffer)) { + if (isTouchSelecting(isMouse, buffer)) { buffer.removeSpan(LAST_TAP_DOWN); Selection.extendSelection(buffer, offset); } @@ -288,7 +305,6 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme return true; } } - return handled; } diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java index 9394a0bae6a5c..fee737732b964 100644 --- a/core/java/android/text/method/Touch.java +++ b/core/java/android/text/method/Touch.java @@ -119,12 +119,18 @@ public class Touch { ds = buffer.getSpans(0, buffer.length(), DragState.class); if (ds.length > 0) { + ds[0].mIsSelectionStarted = false; + if (ds[0].mFarEnough == false) { int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop(); if (Math.abs(event.getX() - ds[0].mX) >= slop || Math.abs(event.getY() - ds[0].mY) >= slop) { ds[0].mFarEnough = true; + if (event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { + ds[0].mIsActivelySelecting = true; + ds[0].mIsSelectionStarted = true; + } } } @@ -135,9 +141,14 @@ public class Touch { MetaKeyKeyListener.META_SHIFT_ON) == 1 || MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0; + + if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { + ds[0].mIsActivelySelecting = false; + } + float dx; float dy; - if (cap) { + if (cap && event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { // if we're selecting, we want the scroll to go in // the direction of the drag dx = event.getX() - ds[0].mX; @@ -157,11 +168,13 @@ public class Touch { ny = Math.min(ny, layout.getHeight() - (widget.getHeight() - padding)); ny = Math.max(ny, 0); - + int oldX = widget.getScrollX(); int oldY = widget.getScrollY(); - scrollTo(widget, layout, nx, ny); + if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { + scrollTo(widget, layout, nx, ny); + } // If we actually scrolled, then cancel the up action. if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) { @@ -194,6 +207,37 @@ public class Touch { return ds.length > 0 ? ds[0].mScrollY : -1; } + /** + * Checks if selection is still active. + * This is useful for extending Selection span on buffer. + * @param buffer The text buffer. + * @return true if buffer has been marked for selection. + * + * @hide + */ + static boolean isActivelySelecting(Spannable buffer) { + DragState[] ds; + ds = buffer.getSpans(0, buffer.length(), DragState.class); + + return ds.length > 0 && ds[0].mIsActivelySelecting; + } + + /** + * Checks if selection has begun (are we out of slop?). + * Note: DragState.mIsSelectionStarted goes back to false with the very next event. + * This is useful for starting Selection span on buffer. + * @param buffer The text buffer. + * @return true if selection has started on the buffer. + * + * @hide + */ + static boolean isSelectionStarted(Spannable buffer) { + DragState[] ds; + ds = buffer.getSpans(0, buffer.length(), DragState.class); + + return ds.length > 0 && ds[0].mIsSelectionStarted; + } + private static class DragState implements NoCopySpan { public float mX; public float mY; @@ -201,6 +245,8 @@ public class Touch { public int mScrollY; public boolean mFarEnough; public boolean mUsed; + public boolean mIsActivelySelecting; + public boolean mIsSelectionStarted; public DragState(float x, float y, int scrollX, int scrollY) { mX = x; diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 7f2defdaadeb0..ae39b7aeee6de 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -3131,6 +3131,24 @@ public final class MotionEvent extends InputEvent implements Parcelable { return symbolicName != null ? symbolicName : Integer.toString(toolType); } + /** + * Checks if a mouse or stylus button (or combination of buttons) is pressed. + * @param button Button (or combination of buttons). + * @return True if specified buttons are pressed. + * + * @see #BUTTON_PRIMARY + * @see #BUTTON_SECONDARY + * @see #BUTTON_TERTIARY + * @see #BUTTON_FORWARD + * @see #BUTTON_BACK + */ + public final boolean isButtonPressed(int button) { + if (button == 0) { + return false; + } + return (getButtonState() & button) == button; + } + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public MotionEvent createFromParcel(Parcel in) {