Automated import from //branches/master/...@140719,140719
diff --git a/api/current.xml b/api/current.xml
index 9c7018a..a989a3c 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -110598,6 +110598,36 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<method name="getInitialScrollX"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="widget" type="android.widget.TextView">
+</parameter>
+<parameter name="buffer" type="android.text.Spannable">
+</parameter>
+</method>
+<method name="getInitialScrollY"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="widget" type="android.widget.TextView">
+</parameter>
+<parameter name="buffer" type="android.text.Spannable">
+</parameter>
+</method>
 <method name="onTouchEvent"
  return="boolean"
  abstract="false"
@@ -157428,6 +157458,17 @@
  visibility="public"
 >
 </method>
+<method name="moveCursorToVisibleOffset"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="onBeginBatchEdit"
  return="void"
  abstract="false"
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 17c7a6c..92f6289 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -18,6 +18,7 @@
 
 import android.util.Log;
 import android.view.KeyEvent;
+import android.graphics.Rect;
 import android.text.*;
 import android.widget.TextView;
 import android.view.View;
@@ -202,23 +203,54 @@
     
     public boolean onTouchEvent(TextView widget, Spannable buffer,
                                 MotionEvent event) {
+        int initialScrollX = -1, initialScrollY = -1;
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            initialScrollX = Touch.getInitialScrollX(widget, buffer);
+            initialScrollY = Touch.getInitialScrollY(widget, buffer);
+        }
+        
         boolean handled = Touch.onTouchEvent(widget, buffer, event);
 
         if (widget.isFocused() && !widget.didTouchFocusSelect()) {
             if (event.getAction() == MotionEvent.ACTION_UP) {
+                // If we have scrolled, then the up shouldn't move the cursor,
+                // but we do need to make sure the cursor is still visible at
+                // the current scroll offset to avoid the scroll jumping later
+                // to show it.
+                if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
+                        (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
+                    widget.moveCursorToVisibleOffset();
+                    return true;
+                }
+                
                 int x = (int) event.getX();
                 int y = (int) event.getY();
 
                 x -= widget.getTotalPaddingLeft();
                 y -= widget.getTotalPaddingTop();
 
+                // Clamp the position to inside of the view.
+                if (x < 0) {
+                    x = 0;
+                } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) {
+                    x = widget.getWidth()-widget.getTotalPaddingRight() - 1;
+                }
+                if (y < 0) {
+                    y = 0;
+                } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) {
+                    y = widget.getHeight()-widget.getTotalPaddingBottom() - 1;
+                }
+                
                 x += widget.getScrollX();
                 y += widget.getScrollY();
 
                 Layout layout = widget.getLayout();
                 int line = layout.getLineForVertical(y);
+                
                 int off = layout.getOffsetForHorizontal(line, x);
 
+                // XXX should do the same adjust for x as we do for the line.
+                
                 boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
                                 KeyEvent.META_SHIFT_ON) == 1) ||
                               (MetaKeyKeyListener.getMetaState(buffer,
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index 65036ad..f2fb9cb 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -21,7 +21,6 @@
 import android.text.Layout.Alignment;
 import android.text.Spannable;
 import android.view.MotionEvent;
-import android.view.View;
 import android.view.ViewConfiguration;
 import android.widget.TextView;
 
@@ -82,8 +81,9 @@
 
         switch (event.getAction()) {
         case MotionEvent.ACTION_DOWN:
-            buffer.setSpan(new DragState(event.getX(), event.getY()),
-                           0, 0, Spannable.SPAN_MARK_MARK);
+            buffer.setSpan(new DragState(event.getX(), event.getY(),
+                            widget.getScrollX(), widget.getScrollY()),
+                    0, 0, Spannable.SPAN_MARK_MARK);
             return true;
 
         case MotionEvent.ACTION_UP:
@@ -142,15 +142,29 @@
         return false;
     }
 
+    public static int getInitialScrollX(TextView widget, Spannable buffer) {
+        DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class);
+        return ds.length > 0 ? ds[0].mScrollX : -1;
+    }
+    
+    public static int getInitialScrollY(TextView widget, Spannable buffer) {
+        DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class);
+        return ds.length > 0 ? ds[0].mScrollY : -1;
+    }
+    
     private static class DragState implements NoCopySpan {
         public float mX;
         public float mY;
+        public int mScrollX;
+        public int mScrollY;
         public boolean mFarEnough;
         public boolean mUsed;
 
-        public DragState(float x, float y) {
+        public DragState(float x, float y, int scrollX, int scrollY) {
             mX = x;
             mY = y;
+            mScrollX = scrollX;
+            mScrollY = scrollY;
         }
     }
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 136752b..01b65b6 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5421,6 +5421,62 @@
         return changed;
     }
 
+    /**
+     * Move the cursor, if needed, so that it is at an offset that is visible
+     * to the user.  This will not move the cursor if it represents more than
+     * one character (a selection range).  This will only work if the
+     * TextView contains spannable text; otherwise it will do nothing.
+     */
+    public boolean moveCursorToVisibleOffset() {
+        if (!(mText instanceof Spannable)) {
+            return false;
+        }
+        int start = Selection.getSelectionStart(mText);
+        int end = Selection.getSelectionEnd(mText);
+        if (start != end) {
+            return false;
+        }
+        
+        // First: make sure the line is visible on screen:
+        
+        int line = mLayout.getLineForOffset(start);
+
+        final int top = mLayout.getLineTop(line);
+        final int bottom = mLayout.getLineTop(line+1);
+        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
+        int vslack = (bottom - top) / 2;
+        if (vslack > vspace / 4)
+            vslack = vspace / 4;
+        final int vs = mScrollY;
+
+        if (top < (vs+vslack)) {
+            line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
+        } else if (bottom > (vspace+vs-vslack)) {
+            line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
+        }
+        
+        // Next: make sure the character is visible on screen:
+        
+        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
+        final int hs = mScrollX;
+        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
+        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
+        
+        int newStart = start;
+        if (newStart < leftChar) {
+            newStart = leftChar;
+        } else if (newStart > rightChar) {
+            newStart = rightChar;
+        }
+        
+        if (newStart != start) {
+            Selection.setSelection((Spannable)mText, newStart);
+            return true;
+        }
+        
+        return false;
+    }
+
     @Override
     public void computeScroll() {
         if (mScroller != null) {