Merge "Text selection without trackball." into gingerbread
diff --git a/api/current.xml b/api/current.xml
index 61f5e16..06e9bfd 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -166341,6 +166341,29 @@
 <parameter name="event" type="android.view.MotionEvent">
 </parameter>
 </method>
+<method name="setCursorController"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cursorController" type="android.widget.TextView.CursorController">
+</parameter>
+</method>
+<field name="mCursorController"
+ type="android.widget.TextView.CursorController"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</field>
 </class>
 <class name="BaseKeyListener"
  extends="android.text.method.MetaKeyKeyListener"
@@ -222825,6 +222848,108 @@
 >
 </method>
 </class>
+<interface name="TextView.CursorController"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="draw"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="canvas" type="android.graphics.Canvas">
+</parameter>
+</method>
+<method name="getOffsetX"
+ return="float"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getOffsetY"
+ return="float"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="hide"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onTouchEvent"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.MotionEvent">
+</parameter>
+</method>
+<method name="show"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="updatePosition"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="offset" type="int">
+</parameter>
+</method>
+<field name="FADE_OUT_DURATION"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="400"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</interface>
 <interface name="TextView.OnEditorActionListener"
  abstract="true"
  static="true"
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
index bb98bce..13cb5e6 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -417,8 +417,8 @@
         }
     }
 
-    private static final class START implements NoCopySpan { };
-    private static final class END implements NoCopySpan { };
+    private static final class START implements NoCopySpan { }
+    private static final class END implements NoCopySpan { }
     
     /*
      * Public constants
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 9af42cc..79a0c37 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -16,30 +16,38 @@
 
 package android.text.method;
 
-import android.util.Log;
+import android.text.Layout;
+import android.text.Selection;
+import android.text.Spannable;
 import android.view.KeyEvent;
-import android.graphics.Rect;
-import android.text.*;
-import android.widget.TextView;
-import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.MotionEvent;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.TextView.CursorController;
 
 // XXX this doesn't extend MetaKeyKeyListener because the signatures
 // don't match.  Need to figure that out.  Meanwhile the meta keys
 // won't work in fields that don't take input.
 
-public class
-ArrowKeyMovementMethod
-implements MovementMethod
-{
+public class ArrowKeyMovementMethod implements MovementMethod {
+    /**
+     * An optional controller for the cursor.
+     * Use {@link #setCursorController(CursorController)} to set this field.
+     */
+    protected CursorController mCursorController;
+
+    private boolean isCap(Spannable buffer) {
+        return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) ||
+                (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
+    }
+
+    private boolean isAlt(Spannable buffer) {
+        return MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1;
+    }
+
     private boolean up(TextView widget, Spannable buffer) {
-        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_SHIFT_ON) == 1) ||
-                      (MetaKeyKeyListener.getMetaState(buffer,
-                        MetaKeyKeyListener.META_SELECTING) != 0);
-        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_ALT_ON) == 1;
+        boolean cap = isCap(buffer);
+        boolean alt = isAlt(buffer);
         Layout layout = widget.getLayout();
 
         if (cap) {
@@ -60,12 +68,8 @@
     }
 
     private boolean down(TextView widget, Spannable buffer) {
-        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_SHIFT_ON) == 1) ||
-                      (MetaKeyKeyListener.getMetaState(buffer,
-                        MetaKeyKeyListener.META_SELECTING) != 0);
-        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_ALT_ON) == 1;
+        boolean cap = isCap(buffer);
+        boolean alt = isAlt(buffer);
         Layout layout = widget.getLayout();
 
         if (cap) {
@@ -86,12 +90,8 @@
     }
 
     private boolean left(TextView widget, Spannable buffer) {
-        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_SHIFT_ON) == 1) ||
-                      (MetaKeyKeyListener.getMetaState(buffer,
-                        MetaKeyKeyListener.META_SELECTING) != 0);
-        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_ALT_ON) == 1;
+        boolean cap = isCap(buffer);
+        boolean alt = isAlt(buffer);
         Layout layout = widget.getLayout();
 
         if (cap) {
@@ -110,12 +110,8 @@
     }
 
     private boolean right(TextView widget, Spannable buffer) {
-        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_SHIFT_ON) == 1) ||
-                      (MetaKeyKeyListener.getMetaState(buffer,
-                        MetaKeyKeyListener.META_SELECTING) != 0);
-        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_ALT_ON) == 1;
+        boolean cap = isCap(buffer);
+        boolean alt = isAlt(buffer);
         Layout layout = widget.getLayout();
 
         if (cap) {
@@ -133,35 +129,6 @@
         }
     }
 
-    private int getOffset(int x, int y, TextView widget){
-      // Converts the absolute X,Y coordinates to the character offset for the
-      // character whose position is closest to the specified
-      // horizontal position.
-      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 offset = layout.getOffsetForHorizontal(line, x);
-      return offset;
-    }
-
     public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
         if (executeDown(widget, buffer, keyCode)) {
             MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
@@ -193,10 +160,9 @@
             break;
 
         case KeyEvent.KEYCODE_DPAD_CENTER:
-            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
-                if (widget.showContextMenu()) {
+            if ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) &&
+                (widget.showContextMenu())) {
                     handled = true;
-                }
             }
         }
 
@@ -214,8 +180,7 @@
 
     public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
         int code = event.getKeyCode();
-        if (code != KeyEvent.KEYCODE_UNKNOWN
-                && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
+        if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
             int repeat = event.getRepeatCount();
             boolean handled = false;
             while ((--repeat) > 0) {
@@ -226,13 +191,22 @@
         return false;
     }
 
-    public boolean onTrackballEvent(TextView widget, Spannable text,
-            MotionEvent event) {
+    public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
+        if (mCursorController != null) {
+            mCursorController.hide();
+        }
         return false;
     }
 
-    public boolean onTouchEvent(TextView widget, Spannable buffer,
-                                MotionEvent event) {
+    public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
+        if (mCursorController != null) {
+            return onTouchEventCursor(widget, buffer, event);
+        } else {
+            return onTouchEventStandard(widget, buffer, event);
+        }
+    }
+
+    private boolean onTouchEventStandard(TextView widget, Spannable buffer, MotionEvent event) {
         int initialScrollX = -1, initialScrollY = -1;
         if (event.getAction() == MotionEvent.ACTION_UP) {
             initialScrollX = Touch.getInitialScrollX(widget, buffer);
@@ -243,53 +217,20 @@
 
         if (widget.isFocused() && !widget.didTouchFocusSelect()) {
             if (event.getAction() == MotionEvent.ACTION_DOWN) {
-              boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                              KeyEvent.META_SHIFT_ON) == 1) ||
-                            (MetaKeyKeyListener.getMetaState(buffer,
-                              MetaKeyKeyListener.META_SELECTING) != 0);
-              int x = (int) event.getX();
-              int y = (int) event.getY();
-              int offset = getOffset(x, y, widget);
-
+              boolean cap = isCap(buffer);
               if (cap) {
-                  buffer.setSpan(LAST_TAP_DOWN, offset, offset,
-                                 Spannable.SPAN_POINT_POINT);
+                  int offset = widget.getOffset((int) event.getX(), (int) 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 {
-                  OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(),
-                      OnePointFiveTapState.class);
-
-                  if (tap.length > 0) {
-                      if (event.getEventTime() - tap[0].mWhen <=
-                          ViewConfiguration.getDoubleTapTimeout() &&
-                          sameWord(buffer, offset, Selection.getSelectionEnd(buffer))) {
-
-                          tap[0].active = true;
-                          MetaKeyKeyListener.startSelecting(widget, buffer);
-                          widget.getParent().requestDisallowInterceptTouchEvent(true);
-                          buffer.setSpan(LAST_TAP_DOWN, offset, offset,
-                              Spannable.SPAN_POINT_POINT);
-                      }
-
-                      tap[0].mWhen = event.getEventTime();
-                  } else {
-                      OnePointFiveTapState newtap = new OnePointFiveTapState();
-                      newtap.mWhen = event.getEventTime();
-                      newtap.active = false;
-                      buffer.setSpan(newtap, 0, buffer.length(),
-                          Spannable.SPAN_INCLUSIVE_INCLUSIVE);
-                  }
               }
             } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
-                boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                                KeyEvent.META_SHIFT_ON) == 1) ||
-                              (MetaKeyKeyListener.getMetaState(buffer,
-                                MetaKeyKeyListener.META_SELECTING) != 0);
+                boolean cap = isCap(buffer);
 
                 if (cap && handled) {
                     // Before selecting, make sure we've moved out of the "slop".
@@ -297,45 +238,15 @@
                     // OUT of the slop
 
                     // Turn long press off while we're selecting. User needs to
-                    // re-tap on the selection to enable longpress
+                    // re-tap on the selection to enable long press
                     widget.cancelLongPress();
 
                     // Update selection as we're moving the selection area.
 
                     // Get the current touch position
-                    int x = (int) event.getX();
-                    int y = (int) event.getY();
-                    int offset = getOffset(x, y, widget);
+                    int offset = widget.getOffset((int) event.getX(), (int) event.getY());
 
-                    final OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(),
-                            OnePointFiveTapState.class);
-
-                    if (tap.length > 0 && tap[0].active) {
-                        // Get the last down touch position (the position at which the
-                        // user started the selection)
-                        int lastDownOffset = buffer.getSpanStart(LAST_TAP_DOWN);
-
-                        // Compute the selection boundaries
-                        int spanstart;
-                        int spanend;
-                        if (offset >= lastDownOffset) {
-                            // Expand from word start of the original tap to new word
-                            // end, since we are selecting "forwards"
-                            spanstart = findWordStart(buffer, lastDownOffset);
-                            spanend = findWordEnd(buffer, offset);
-                        } else {
-                            // Expand to from new word start to word end of the original
-                            // tap since we are selecting "backwards".
-                            // The spanend will always need to be associated with the touch
-                            // up position, so that refining the selection with the
-                            // trackball will work as expected.
-                            spanstart = findWordEnd(buffer, lastDownOffset);
-                            spanend = findWordStart(buffer, offset);
-                        }
-                        Selection.setSelection(buffer, spanstart, spanend);
-                    } else {
-                        Selection.extendSelection(buffer, offset);
-                    }
+                    Selection.extendSelection(buffer, offset);
                     return true;
                 }
             } else if (event.getAction() == MotionEvent.ACTION_UP) {
@@ -344,70 +255,17 @@
                 // the current scroll offset to avoid the scroll jumping later
                 // to show it.
                 if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
-                        (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
+                    (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
                     widget.moveCursorToVisibleOffset();
                     return true;
                 }
 
-                int x = (int) event.getX();
-                int y = (int) event.getY();
-                int off = getOffset(x, y, widget);
-
-                // XXX should do the same adjust for x as we do for the line.
-
-                OnePointFiveTapState[] onepointfivetap = buffer.getSpans(0, buffer.length(),
-                    OnePointFiveTapState.class);
-                if (onepointfivetap.length > 0 && onepointfivetap[0].active &&
-                    Selection.getSelectionStart(buffer) == Selection.getSelectionEnd(buffer)) {
-                    // If we've set select mode, because there was a onepointfivetap,
-                    // but there was no ensuing swipe gesture, undo the select mode
-                    // and remove reference to the last onepointfivetap.
-                    MetaKeyKeyListener.stopSelecting(widget, buffer);
-                    for (int i=0; i < onepointfivetap.length; i++) {
-                        buffer.removeSpan(onepointfivetap[i]);
-                    }
+                int offset = widget.getOffset((int) event.getX(), (int) event.getY());
+                if (isCap(buffer)) {
                     buffer.removeSpan(LAST_TAP_DOWN);
-                }
-                boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                                KeyEvent.META_SHIFT_ON) == 1) ||
-                              (MetaKeyKeyListener.getMetaState(buffer,
-                                MetaKeyKeyListener.META_SELECTING) != 0);
-
-                DoubleTapState[] tap = buffer.getSpans(0, buffer.length(),
-                                                       DoubleTapState.class);
-                boolean doubletap = false;
-
-                if (tap.length > 0) {
-                    if (event.getEventTime() - tap[0].mWhen <=
-                        ViewConfiguration.getDoubleTapTimeout() &&
-                        sameWord(buffer, off, Selection.getSelectionEnd(buffer))) {
-
-                        doubletap = true;
-                    }
-
-                    tap[0].mWhen = event.getEventTime();
+                    Selection.extendSelection(buffer, offset);
                 } else {
-                    DoubleTapState newtap = new DoubleTapState();
-                    newtap.mWhen = event.getEventTime();
-                    buffer.setSpan(newtap, 0, buffer.length(),
-                                   Spannable.SPAN_INCLUSIVE_INCLUSIVE);
-                }
-
-                if (cap) {
-                    buffer.removeSpan(LAST_TAP_DOWN);
-                    if (onepointfivetap.length > 0 && onepointfivetap[0].active) {
-                        // If we selecting something with the onepointfivetap-and
-                        // swipe gesture, stop it on finger up.
-                        MetaKeyKeyListener.stopSelecting(widget, buffer);
-                    } else {
-                        Selection.extendSelection(buffer, off);
-                    }
-                } else if (doubletap) {
-                    Selection.setSelection(buffer,
-                                           findWordStart(buffer, off),
-                                           findWordEnd(buffer, off));
-                } else {
-                    Selection.setSelection(buffer, off);
+                    Selection.setSelection(buffer, offset);
                 }
 
                 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
@@ -420,73 +278,36 @@
         return handled;
     }
 
-    private static class DoubleTapState implements NoCopySpan {
-        long mWhen;
-    }
+    private boolean onTouchEventCursor(TextView widget, Spannable buffer, MotionEvent event) {
+        if (widget.isFocused() && !widget.didTouchFocusSelect()) {
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_MOVE:
+                    widget.cancelLongPress();
 
-    /* We check for a onepointfive tap. This is similar to
-    *  doubletap gesture (where a finger goes down, up, down, up, in a short
-    *  time period), except in the onepointfive tap, a users finger only needs
-    *  to go down, up, down in a short time period. We detect this type of tap
-    *  to implement the onepointfivetap-and-swipe selection gesture.
-    *  This gesture allows users to select a segment of text without going
-    *  through the "select text" option in the context menu.
-    */
-    private static class OnePointFiveTapState implements NoCopySpan {
-        long mWhen;
-        boolean active;
-    }
+                    // Offset the current touch position (from controller to cursor)
+                    final float x = event.getX() + mCursorController.getOffsetX();
+                    final float y = event.getY() + mCursorController.getOffsetY();
+                    int offset = widget.getOffset((int) x, (int) y);
+                    mCursorController.updatePosition(offset);
+                    return true;
 
-    private static boolean sameWord(CharSequence text, int one, int two) {
-        int start = findWordStart(text, one);
-        int end = findWordEnd(text, one);
-
-        if (end == start) {
-            return false;
-        }
-
-        return start == findWordStart(text, two) &&
-               end == findWordEnd(text, two);
-    }
-
-    // TODO: Unify with TextView.getWordForDictionary()
-    private static int findWordStart(CharSequence text, int start) {
-        for (; start > 0; start--) {
-            char c = text.charAt(start - 1);
-            int type = Character.getType(c);
-
-            if (c != '\'' &&
-                type != Character.UPPERCASE_LETTER &&
-                type != Character.LOWERCASE_LETTER &&
-                type != Character.TITLECASE_LETTER &&
-                type != Character.MODIFIER_LETTER &&
-                type != Character.DECIMAL_DIGIT_NUMBER) {
-                break;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    mCursorController = null;
+                    return true;
             }
         }
-
-        return start;
+        return false;
     }
 
-    // TODO: Unify with TextView.getWordForDictionary()
-    private static int findWordEnd(CharSequence text, int end) {
-        int len = text.length();
-
-        for (; end < len; end++) {
-            char c = text.charAt(end);
-            int type = Character.getType(c);
-
-            if (c != '\'' &&
-                type != Character.UPPERCASE_LETTER &&
-                type != Character.LOWERCASE_LETTER &&
-                type != Character.TITLECASE_LETTER &&
-                type != Character.MODIFIER_LETTER &&
-                type != Character.DECIMAL_DIGIT_NUMBER) {
-                break;
-            }
-        }
-
-        return end;
+    /**
+     * Defines the cursor controller.
+     *
+     * When set, this object can be used to handle events, that can be translated in cursor updates.
+     * @param cursorController A cursor controller implementation
+     */
+    public void setCursorController(CursorController cursorController) {
+        mCursorController = cursorController;
     }
 
     public boolean canSelectArbitrarily() {
@@ -525,8 +346,9 @@
     }
 
     public static MovementMethod getInstance() {
-        if (sInstance == null)
+        if (sInstance == null) {
             sInstance = new ArrowKeyMovementMethod();
+        }
 
         return sInstance;
     }
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index 42ad10e..a19a78e 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -17,14 +17,13 @@
 package android.text.method;
 
 import android.text.Layout;
-import android.text.NoCopySpan;
 import android.text.Layout.Alignment;
+import android.text.NoCopySpan;
 import android.text.Spannable;
-import android.util.Log;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 import android.widget.TextView;
-import android.view.KeyEvent;
 
 public class Touch {
     private Touch() { }
@@ -99,7 +98,7 @@
                                        MotionEvent event) {
         DragState[] ds;
 
-        switch (event.getAction()) {
+        switch (event.getActionMasked()) {
         case MotionEvent.ACTION_DOWN:
             ds = buffer.getSpans(0, buffer.length(), DragState.class);
 
diff --git a/core/java/android/view/AbsSavedState.java b/core/java/android/view/AbsSavedState.java
index 840d7c1..6ad33dd 100644
--- a/core/java/android/view/AbsSavedState.java
+++ b/core/java/android/view/AbsSavedState.java
@@ -54,7 +54,7 @@
      */
     protected AbsSavedState(Parcel source) {
         // FIXME need class loader
-        Parcelable superState = (Parcelable) source.readParcelable(null);
+        Parcelable superState = source.readParcelable(null);
          
         mSuperState = superState != null ? superState : EMPTY_STATE;
     }
@@ -75,7 +75,7 @@
         = new Parcelable.Creator<AbsSavedState>() {
         
         public AbsSavedState createFromParcel(Parcel in) {
-            Parcelable superState = (Parcelable) in.readParcelable(null);
+            Parcelable superState = in.readParcelable(null);
             if (superState != null) {
                 throw new IllegalStateException("superState must be null");
             }
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 47061c1..1328525 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -772,7 +772,7 @@
      * 
      * @param pointerId The identifier of the pointer to be found.
      * @return Returns either the index of the pointer (for use with
-     * {@link #getX(int) et al.), or -1 if there is no data available for
+     * {@link #getX(int)} et al.), or -1 if there is no data available for
      * that pointer identifier.
      */
     public final int findPointerIndex(int pointerId) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b8623e7..c9662ff 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2413,11 +2413,10 @@
     }
 
     /**
-     * Call this view's OnLongClickListener, if it is defined. Invokes the context menu
-     * if the OnLongClickListener did not consume the event.
+     * Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the
+     * OnLongClickListener did not consume the event.
      *
-     * @return True there was an assigned OnLongClickListener that was called, false
-     *         otherwise is returned.
+     * @return True if one of the above receivers consumed the event, false otherwise.
      */
     public boolean performLongClick() {
         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
@@ -4218,6 +4217,10 @@
      * Show the context menu for this view. It is not safe to hold on to the
      * menu after returning from this method.
      *
+     * You should normally not overload this method. Overload
+     * {@link #onCreateContextMenu(ContextMenu)} or define an
+     * {@link OnCreateContextMenuListener} to add items to the context menu.
+     *
      * @param menu The context menu to populate
      */
     public void createContextMenu(ContextMenu menu) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 27e0e94..73b1d6d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -23,6 +23,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -61,6 +62,7 @@
 import android.text.TextPaint;
 import android.text.TextUtils;
 import android.text.TextWatcher;
+import android.text.method.ArrowKeyMovementMethod;
 import android.text.method.DateKeyListener;
 import android.text.method.DateTimeKeyListener;
 import android.text.method.DialerKeyListener;
@@ -89,10 +91,11 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewDebug;
+import android.view.ViewGroup.LayoutParams;
 import android.view.ViewRoot;
 import android.view.ViewTreeObserver;
-import android.view.ViewGroup.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AnimationUtils;
@@ -185,7 +188,7 @@
  */
 @RemoteView
 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
-    static final String TAG = "TextView";
+    static final String LOG_TAG = "TextView";
     static final boolean DEBUG_EXTRACT = false;
     
     private static int PRIORITY = 100;
@@ -321,6 +324,7 @@
         this(context, attrs, com.android.internal.R.attr.textViewStyle);
     }
 
+    @SuppressWarnings("deprecation")
     public TextView(Context context,
                     AttributeSet attrs,
                     int defStyle) {
@@ -695,9 +699,9 @@
                 try {
                     setInputExtras(a.getResourceId(attr, 0));
                 } catch (XmlPullParserException e) {
-                    Log.w("TextView", "Failure reading input extras", e);
+                    Log.w(LOG_TAG, "Failure reading input extras", e);
                 } catch (IOException e) {
-                    Log.w("TextView", "Failure reading input extras", e);
+                    Log.w(LOG_TAG, "Failure reading input extras", e);
                 }
                 break;
             }
@@ -714,7 +718,7 @@
         }
 
         if (inputMethod != null) {
-            Class c;
+            Class<?> c;
 
             try {
                 c = Class.forName(inputMethod.toString());
@@ -923,6 +927,8 @@
         setFocusable(focusable);
         setClickable(clickable);
         setLongClickable(longClickable);
+
+        prepareCursorControllers();
     }
 
     private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
@@ -1128,6 +1134,9 @@
             setText(mText);
 
         fixFocusableAndClickableSettings();
+
+        // SelectionModifierCursorController depends on canSelectText, which depends on mMovement
+        prepareCursorControllers();
     }
 
     private void fixFocusableAndClickableSettings() {
@@ -2335,6 +2344,7 @@
             return str + "}";
         }
 
+        @SuppressWarnings("hiding")
         public static final Parcelable.Creator<SavedState> CREATOR
                 = new Parcelable.Creator<SavedState>() {
             public SavedState createFromParcel(Parcel in) {
@@ -2369,8 +2379,8 @@
         int end = 0;
 
         if (mText != null) {
-            start = Selection.getSelectionStart(mText);
-            end = Selection.getSelectionEnd(mText);
+            start = getSelectionStart();
+            end = getSelectionEnd();
             if (start >= 0 || end >= 0) {
                 // Or save state if there is a selection
                 save = true;
@@ -2442,7 +2452,7 @@
                         restored = "(restored) ";
                     }
 
-                    Log.e("TextView", "Saved cursor position " + ss.selStart +
+                    Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
                           "/" + ss.selEnd + " out of range for " + restored +
                           "text " + mText);
                 } else {
@@ -2694,6 +2704,9 @@
         if (needEditableForNotification) {
             sendAfterTextChanged((Editable) text);
         }
+
+        // SelectionModifierCursorController depends on canSelectText, which depends on text
+        prepareCursorControllers();
     }
 
     /**
@@ -2756,6 +2769,7 @@
             return mChars[off + mStart];
         }
 
+        @Override
         public String toString() {
             return new String(mChars, mStart, mLength);
         }
@@ -2981,7 +2995,7 @@
         } else {
             input = TextKeyListener.getInstance();
         }
-        mInputType = type;
+        setRawInputType(type);
         if (direct) mInput = input;
         else {
             setKeyListenerOnly(input);
@@ -3198,7 +3212,7 @@
      *
      * @param create If true, the extras will be created if they don't already
      * exist.  Otherwise, null will be returned if none have been created.
-     * @see #setInputExtras(int)View
+     * @see #setInputExtras(int)
      * @see EditorInfo#extras
      * @attr ref android.R.styleable#TextView_editorExtras
      */
@@ -3312,7 +3326,7 @@
 
     private static class ErrorPopup extends PopupWindow {
         private boolean mAbove = false;
-        private TextView mView;
+        private final TextView mView;
 
         ErrorPopup(TextView v, int width, int height) {
             super(v, width, height);
@@ -3585,7 +3599,7 @@
     }
 
     private void invalidateCursor() {
-        int where = Selection.getSelectionEnd(mText);
+        int where = getSelectionEnd();
 
         invalidateCursor(where, where, where);
     }
@@ -3661,7 +3675,18 @@
         boolean changed = false;
 
         if (mMovement != null) {
-            int curs = Selection.getSelectionEnd(mText);
+            /* This code also provides auto-scrolling when a cursor is moved using a
+             * CursorController (insertion point or selection limits).
+             * For selection, ensure start or end is visible depending on controller's state.
+             */
+            int curs = getSelectionEnd();
+            if (mSelectionModifierCursorController != null) {
+                SelectionModifierCursorController selectionController =
+                    (SelectionModifierCursorController) mSelectionModifierCursorController;
+                if (selectionController.isSelectionStartDragged()) {
+                    curs = getSelectionStart();
+                }
+            }
 
             /*
              * TODO: This should really only keep the end in view if
@@ -3680,6 +3705,10 @@
             changed = bringTextIntoView();
         }
 
+        if (mShouldStartTextSelectionMode) {
+            startTextSelectionMode();
+            mShouldStartTextSelectionMode = false;
+        }
         mPreDrawState = PREDRAW_DONE;
         return !changed;
     }
@@ -3954,8 +3983,8 @@
         //  XXX This is not strictly true -- a program could set the
         //  selection manually if it really wanted to.
         if (mMovement != null && (isFocused() || isPressed())) {
-            selStart = Selection.getSelectionStart(mText);
-            selEnd = Selection.getSelectionEnd(mText);
+            selStart = getSelectionStart();
+            selEnd = getSelectionEnd();
 
             if (mCursorVisible && selStart >= 0 && isEnabled()) {
                 if (mHighlightPath == null)
@@ -4061,6 +4090,13 @@
         */
 
         canvas.restore();
+
+        if (mInsertionPointCursorController != null) {
+            mInsertionPointCursorController.draw(canvas);
+        }
+        if (mSelectionModifierCursorController != null) {
+            mSelectionModifierCursorController.draw(canvas);
+        }
     }
 
     @Override
@@ -4345,6 +4381,8 @@
             return super.onKeyUp(keyCode, event);
         }
 
+        hideControllers();
+
         switch (keyCode) {
             case KeyEvent.KEYCODE_DPAD_CENTER:
                 /*
@@ -4475,8 +4513,8 @@
             outAttrs.hintText = mHint;
             if (mText instanceof Editable) {
                 InputConnection ic = new EditableInputConnection(this);
-                outAttrs.initialSelStart = Selection.getSelectionStart(mText);
-                outAttrs.initialSelEnd = Selection.getSelectionEnd(mText);
+                outAttrs.initialSelStart = getSelectionStart();
+                outAttrs.initialSelEnd = getSelectionEnd();
                 outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
                 return ic;
             }
@@ -4560,8 +4598,8 @@
                 outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
             }
             outText.startOffset = 0;
-            outText.selectionStart = Selection.getSelectionStart(content);
-            outText.selectionEnd = Selection.getSelectionEnd(content);
+            outText.selectionStart = getSelectionStart();
+            outText.selectionEnd = getSelectionEnd();
             return true;
         }
         return false;
@@ -4578,7 +4616,7 @@
                 if (req != null) {
                     InputMethodManager imm = InputMethodManager.peekInstance();
                     if (imm != null) {
-                        if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start="
+                        if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Retrieving extracted start="
                                 + ims.mChangedStart + " end=" + ims.mChangedEnd
                                 + " delta=" + ims.mChangedDelta);
                         if (ims.mChangedStart < 0 && !contentChanged) {
@@ -4586,7 +4624,7 @@
                         }
                         if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
                                 ims.mChangedDelta, ims.mTmpExtracted)) {
-                            if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start="
+                            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Reporting extracted start="
                                     + ims.mTmpExtracted.partialStartOffset
                                     + " end=" + ims.mTmpExtracted.partialEndOffset
                                     + ": " + ims.mTmpExtracted.text);
@@ -4736,7 +4774,7 @@
     
     void updateAfterEdit() {
         invalidate();
-        int curs = Selection.getSelectionStart(mText);
+        int curs = getSelectionStart();
 
         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
                              Gravity.BOTTOM) {
@@ -4878,7 +4916,6 @@
                                 w, alignment, mSpacingMult, mSpacingAdd,
                                 boring, mIncludePad);
                     }
-                    // Log.e("aaa", "Boring: " + mTransformed);
 
                     mSavedLayout = (BoringLayout) mLayout;
                 } else if (shouldEllipsize && boring.width <= w) {
@@ -4904,7 +4941,6 @@
                     mLayout = new StaticLayout(mTransformed, mTextPaint,
                             w, alignment, mSpacingMult, mSpacingAdd,
                             mIncludePad);
-                    // Log.e("aaa", "Boring but wide: " + mTransformed);
                 }
             } else if (shouldEllipsize) {
                 mLayout = new StaticLayout(mTransformed,
@@ -5002,6 +5038,9 @@
                 }
             }
         }
+
+        // CursorControllers need a non-null mLayout
+        prepareCursorControllers();
     }
 
     private boolean compressText(float width) {
@@ -5473,7 +5512,7 @@
         // FIXME: Is it okay to truncate this, or should we round?
         final int x = (int)mLayout.getPrimaryHorizontal(offset);
         final int top = mLayout.getLineTop(line);
-        final int bottom = mLayout.getLineTop(line+1);
+        final int bottom = mLayout.getLineTop(line + 1);
 
         int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
         int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
@@ -5610,8 +5649,8 @@
             // viewport coordinates, but requestRectangleOnScreen()
             // is in terms of content coordinates.
 
-            Rect r = new Rect();
-            getInterestingRect(r, x, top, bottom, line);
+            Rect r = new Rect(x, top, x + 1, bottom);
+            getInterestingRect(r, line);
             r.offset(mScrollX, mScrollY);
 
             if (requestRectangleOnScreen(r)) {
@@ -5627,13 +5666,15 @@
      * 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.
+     *
+     * @return True if the cursor was actually moved, false otherwise.
      */
     public boolean moveCursorToVisibleOffset() {
         if (!(mText instanceof Spannable)) {
             return false;
         }
-        int start = Selection.getSelectionStart(mText);
-        int end = Selection.getSelectionEnd(mText);
+        int start = getSelectionStart();
+        int end = getSelectionEnd();
         if (start != end) {
             return false;
         }
@@ -5643,7 +5684,7 @@
         int line = mLayout.getLineForOffset(start);
 
         final int top = mLayout.getLineTop(line);
-        final int bottom = mLayout.getLineTop(line+1);
+        final int bottom = mLayout.getLineTop(line + 1);
         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
         int vslack = (bottom - top) / 2;
         if (vslack > vspace / 4)
@@ -5689,22 +5730,28 @@
         }
     }
 
-    private void getInterestingRect(Rect r, int h, int top, int bottom,
-                                    int line) {
+    private void getInterestingRect(Rect r, int line) {
+        convertFromViewportToContentCoordinates(r);
+
+        // Rectangle can can be expanded on first and last line to take
+        // padding into account.
+        // TODO Take left/right padding into account too?
+        if (line == 0) r.top -= getExtendedPaddingTop();
+        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
+    }
+
+    private void convertFromViewportToContentCoordinates(Rect r) {
         int paddingTop = getExtendedPaddingTop();
         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
             paddingTop += getVerticalOffset(false);
         }
-        top += paddingTop;
-        bottom += paddingTop;
-        h += getCompoundPaddingLeft();
+        r.top += paddingTop;
+        r.bottom += paddingTop;
 
-        if (line == 0)
-            top -= getExtendedPaddingTop();
-        if (line == mLayout.getLineCount() - 1)
-            bottom += getExtendedPaddingBottom();
+        int paddingLeft = getCompoundPaddingLeft();
+        r.left += paddingLeft;
+        r.right += paddingLeft;
 
-        r.set(h, top, h+1, bottom);
         r.offset(-mScrollX, -mScrollY);
     }
 
@@ -5872,6 +5919,9 @@
         } else if (mBlink != null) {
             mBlink.removeCallbacks(mBlink);
         }
+
+        // InsertionPointCursorController depends on mCursorVisible
+        prepareCursorControllers();
     }
 
     private boolean canMarquee() {
@@ -5930,7 +5980,7 @@
         private final WeakReference<TextView> mView;
 
         private byte mStatus = MARQUEE_STOPPED;
-        private float mScrollUnit;
+        private final float mScrollUnit;
         private float mMaxScroll;
         float mMaxFadeScroll;
         private float mGhostStart;
@@ -5942,7 +5992,7 @@
 
         Marquee(TextView v) {
             final float density = v.getContext().getResources().getDisplayMetrics().density;
-            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / (float) MARQUEE_RESOLUTION;
+            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
             mView = new WeakReference<TextView>(v);
         }
 
@@ -6175,6 +6225,7 @@
         
         sendOnTextChanged(buffer, start, before, after);
         onTextChanged(buffer, start, before, after);
+        hideControllers();
     }
     
     /**
@@ -6286,7 +6337,7 @@
                         }
                     }
                 } else {
-                    if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: "
+                    if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
                             + oldStart + "-" + oldEnd + ","
                             + newStart + "-" + newEnd + what);
                     ims.mContentChanged = true;
@@ -6302,7 +6353,7 @@
 
         public void beforeTextChanged(CharSequence buffer, int start,
                                       int before, int after) {
-            if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
                     + " before=" + before + " after=" + after + ": " + buffer);
 
             if (AccessibilityManager.getInstance(mContext).isEnabled()
@@ -6315,7 +6366,7 @@
 
         public void onTextChanged(CharSequence buffer, int start,
                                   int before, int after) {
-            if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
                     + " before=" + before + " after=" + after + ": " + buffer);
             TextView.this.handleTextChanged(buffer, start, before, after);
 
@@ -6328,7 +6379,7 @@
         }
 
         public void afterTextChanged(Editable buffer) {
-            if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer);
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
             TextView.this.sendAfterTextChanged(buffer);
 
             if (MetaKeyKeyListener.getMetaState(buffer,
@@ -6339,19 +6390,19 @@
 
         public void onSpanChanged(Spannable buf,
                                   Object what, int s, int e, int st, int en) {
-            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
                     + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
             TextView.this.spanChange(buf, what, s, st, e, en);
         }
 
         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
-            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
                     + " what=" + what + ": " + buf);
             TextView.this.spanChange(buf, what, -1, s, -1, e);
         }
 
         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
-            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
                     + " what=" + what + ": " + buf);
             TextView.this.spanChange(buf, what, s, -1, e, -1);
         }
@@ -6455,12 +6506,22 @@
             if (mError != null) {
                 showError();
             }
+
+            // We cannot start the selection mode immediately. The layout may be null here and is
+            // needed by the cursor controller. Layout creation is deferred up to drawing. The
+            // selection action mode will be started in onPreDraw().
+            if (selStart != selEnd) {
+                mShouldStartTextSelectionMode = true;
+            }
         } else {
             if (mError != null) {
                 hideError();
             }
             // Don't leave us in the middle of a batch edit.
             onEndBatchEdit();
+
+            hideInsertionPointCursorController();
+            terminateTextSelectionMode();
         }
 
         startStopMarquee(focused);
@@ -6527,24 +6588,52 @@
     }
 
     class CommitSelectionReceiver extends ResultReceiver {
-        int mNewStart;
-        int mNewEnd;
+        private final int mPrevStart, mPrevEnd;
+        private final int mNewStart, mNewEnd;
         
-        CommitSelectionReceiver() {
+        public CommitSelectionReceiver(int mPrevStart, int mPrevEnd, int mNewStart, int mNewEnd) {
             super(getHandler());
+            this.mPrevStart = mPrevStart;
+            this.mPrevEnd = mPrevEnd;
+            this.mNewStart = mNewStart;
+            this.mNewEnd = mNewEnd;
         }
         
+        @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
-            if (resultCode != InputMethodManager.RESULT_SHOWN) {
-                final int len = mText.length();
-                if (mNewStart > len) {
-                    mNewStart = len;
+            int start = mNewStart;
+            int end = mNewEnd;
+
+            // Move the cursor to the new position, unless this tap was actually
+            // use to show the IMM. Leave cursor unchanged in that case.
+            if (resultCode == InputMethodManager.RESULT_SHOWN) {
+                start = mPrevStart;
+                end = mPrevEnd;
+            } else {
+                if ((mPrevStart != mPrevEnd) && (start == end)) {
+                    if ((start >= mPrevStart) && (start <= mPrevEnd)) {
+                        // Tapping inside the selection does nothing
+                        Selection.setSelection((Spannable) mText, mPrevStart, mPrevEnd);
+                        return;
+                    } else {
+                        // Tapping outside stops selection mode, if any
+                        stopTextSelectionMode();
+                    }
                 }
-                if (mNewEnd > len) {
-                    mNewEnd = len;
+
+                if (mInsertionPointCursorController != null) {
+                    mInsertionPointCursorController.show();
                 }
-                Selection.setSelection((Spannable)mText, mNewStart, mNewEnd);
             }
+
+            final int len = mText.length();
+            if (start > len) {
+                start = len;
+            }
+            if (end > len) {
+                end = len;
+            }
+            Selection.setSelection((Spannable)mText, start, end);
         }
     }
     
@@ -6557,7 +6646,7 @@
             mTouchFocusSelected = false;
             mScrolled = false;
         }
-        
+
         final boolean superResult = super.onTouchEvent(event);
 
         /*
@@ -6572,42 +6661,37 @@
 
         if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
             
-            boolean handled = false;
+            int oldSelStart = getSelectionStart();
+            int oldSelEnd = getSelectionEnd();
             
-            int oldSelStart = Selection.getSelectionStart(mText);
-            int oldSelEnd = Selection.getSelectionEnd(mText);
+            if (mInsertionPointCursorController != null) {
+                mInsertionPointCursorController.onTouchEvent(event);
+            }
+            if (mSelectionModifierCursorController != null) {
+                mSelectionModifierCursorController.onTouchEvent(event);
+            }
+
+            boolean handled = false;
             
             if (mMovement != null) {
                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
             }
 
-            if (mText instanceof Editable && onCheckIsTextEditor()) {
+            if (isTextEditable()) {
                 if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
                     InputMethodManager imm = (InputMethodManager)
                             getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                     
-                    // This is going to be gross...  if tapping on the text view
-                    // causes the IME to be displayed, we don't want the selection
-                    // to change.  But the selection has already changed, and
-                    // we won't know right away whether the IME is getting
-                    // displayed, so...
+                    final int newSelStart = getSelectionStart();
+                    final int newSelEnd = getSelectionEnd();
                     
-                    int newSelStart = Selection.getSelectionStart(mText);
-                    int newSelEnd = Selection.getSelectionEnd(mText);
                     CommitSelectionReceiver csr = null;
                     if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) {
-                        csr = new CommitSelectionReceiver();
-                        csr.mNewStart = newSelStart;
-                        csr.mNewEnd = newSelEnd;
+                        csr = new CommitSelectionReceiver(oldSelStart, oldSelEnd,
+                                newSelStart, newSelEnd);
                     }
                     
-                    if (imm.showSoftInput(this, 0, csr) && csr != null) {
-                        // The IME might get shown -- revert to the old
-                        // selection, and change to the new when we finally
-                        // find out of it is okay.
-                        Selection.setSelection((Spannable)mText, oldSelStart, oldSelEnd);
-                        handled = true;
-                    }
+                    handled |= imm.showSoftInput(this, 0, csr) && (csr != null);
                 }
             }
 
@@ -6619,6 +6703,34 @@
         return superResult;
     }
 
+    private void prepareCursorControllers() {
+        // TODO Add an extra android:cursorController flag to disable the controller?
+        if (mCursorVisible && mLayout != null) {
+            if (mInsertionPointCursorController == null) {
+                mInsertionPointCursorController = new InsertionPointCursorController();
+            }
+        } else {
+            mInsertionPointCursorController = null;
+        }
+
+        if (canSelectText() && mLayout != null) {
+            if (mSelectionModifierCursorController == null) {
+                mSelectionModifierCursorController = new SelectionModifierCursorController();
+            }
+        } else {
+            // Stop selection mode if the controller becomes unavailable.
+            stopTextSelectionMode();
+            mSelectionModifierCursorController = null;
+        }
+    }
+
+    /**
+     * @return True iff this TextView contains a text that can be edited.
+     */
+    private boolean isTextEditable() {
+        return mText instanceof Editable && onCheckIsTextEditor();
+    }
+
     /**
      * Returns true, only while processing a touch gesture, if the initial
      * touch down event caused focus to move to the text view and as a result
@@ -6652,7 +6764,7 @@
     }
 
     private static class Blink extends Handler implements Runnable {
-        private WeakReference<TextView> mView;
+        private final WeakReference<TextView> mView;
         private boolean mCancelled;
 
         public Blink(TextView v) {
@@ -6669,8 +6781,8 @@
             TextView tv = mView.get();
 
             if (tv != null && tv.isFocused()) {
-                int st = Selection.getSelectionStart(tv.mText);
-                int en = Selection.getSelectionEnd(tv.mText);
+                int st = tv.getSelectionStart();
+                int en = tv.getSelectionEnd();
 
                 if (st == en && st >= 0 && en >= 0) {
                     if (tv.mLayout != null) {
@@ -6851,21 +6963,16 @@
     }
 
     private boolean canSelectAll() {
-        if (mText instanceof Spannable && mText.length() != 0 &&
-            mMovement != null && mMovement.canSelectArbitrarily()) {
-            return true;
-        }
-
-        return false;
+        return canSelectText() && mText.length() != 0;
     }
 
     private boolean canSelectText() {
-        if (mText instanceof Spannable && mText.length() != 0 &&
-            mMovement != null && mMovement.canSelectArbitrarily()) {
-            return true;
-        }
-
-        return false;
+        // prepareCursorController() relies on this method.
+        // If you change this condition, make sure prepareCursorController is called anywhere
+        // the value of this condition might be changed.
+        return (mText instanceof Spannable &&
+                mMovement != null &&
+                mMovement.canSelectArbitrarily());
     }
 
     private boolean canCut() {
@@ -6895,23 +7002,23 @@
     }
 
     private boolean canPaste() {
-        if (mText instanceof Editable && mInput != null &&
-            getSelectionStart() >= 0 && getSelectionEnd() >= 0) {
-            ClipboardManager clip = (ClipboardManager)getContext()
-                    .getSystemService(Context.CLIPBOARD_SERVICE);
-            if (clip.hasText()) {
-                return true;
-            }
-        }
-
-        return false;
+        return (mText instanceof Editable &&
+                mInput != null &&
+                getSelectionStart() >= 0 &&
+                getSelectionEnd() >= 0 &&
+                ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
+                hasText());
     }
 
     /**
-     * Returns a word to add to the dictionary from the context menu,
-     * or null if there is no cursor or no word at the cursor.
+     * Returns the offsets delimiting the 'word' located at position offset.
+     *
+     * @param offset An offset in the text.
+     * @return The offsets for the start and end of the word located at <code>offset</code>.
+     * The two ints offsets are packed in a long, with the starting offset shifted by 32 bits.
+     * Returns a negative value if no valid word was found.
      */
-    private String getWordForDictionary() {
+    private long getWordLimitsAt(int offset) {
         /*
          * Quick return if the input type is one where adding words
          * to the dictionary doesn't make any sense.
@@ -6920,7 +7027,7 @@
         if (klass == InputType.TYPE_CLASS_NUMBER ||
             klass == InputType.TYPE_CLASS_PHONE ||
             klass == InputType.TYPE_CLASS_DATETIME) {
-            return null;
+            return -1;
         }
 
         int variation = mInputType & InputType.TYPE_MASK_VARIATION;
@@ -6929,13 +7036,13 @@
             variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ||
             variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
             variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
-            return null;
+            return -1;
         }
 
-        int end = getSelectionEnd();
+        int end = offset;
 
         if (end < 0) {
-            return null;
+            return -1;
         }
 
         int start = end;
@@ -6969,6 +7076,14 @@
             }
         }
 
+        if (start == end) {
+            return -1;
+        }
+
+        if (end - start > 48) {
+            return -1;
+        }
+
         boolean hasLetter = false;
         for (int i = start; i < end; i++) {
             if (Character.isLetter(mTransformed.charAt(i))) {
@@ -6976,21 +7091,61 @@
                 break;
             }
         }
+
         if (!hasLetter) {
-            return null;
+            return -1;
         }
 
-        if (start == end) {
-            return null;
-        }
-
-        if (end - start > 48) {
-            return null;
-        }
-
-        return TextUtils.substring(mTransformed, start, end);
+        // Two ints packed in a long
+        return (((long) start) << 32) | end;
     }
+    
+    private void selectCurrentWord() {
+        // In case selection mode is started after an orientation change or after a select all,
+        // use the current selection instead of creating one
+        if (hasSelection()) {
+            return;
+        }
 
+        int selectionStart, selectionEnd;
+
+        SelectionModifierCursorController selectionModifierCursorController =
+            ((SelectionModifierCursorController) mSelectionModifierCursorController);
+        int minOffset = selectionModifierCursorController.getMinTouchOffset();
+        int maxOffset = selectionModifierCursorController.getMaxTouchOffset();
+
+        long wordLimits = getWordLimitsAt(minOffset);
+        if (wordLimits >= 0) {
+            selectionStart = (int) (wordLimits >>> 32);
+        } else {
+            selectionStart = Math.max(minOffset - 5, 0);
+        }
+
+        wordLimits = getWordLimitsAt(maxOffset);
+        if (wordLimits >= 0) {
+            selectionEnd = (int) (wordLimits & 0x00000000FFFFFFFFL);
+        } else {
+            selectionEnd = Math.min(maxOffset + 5, mText.length());
+        }
+
+        Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+    }
+    
+    private String getWordForDictionary() {
+        int offset = ((SelectionModifierCursorController) mSelectionModifierCursorController).
+                     getMinTouchOffset();
+
+        long wordLimits = getWordLimitsAt(offset);
+        if (wordLimits >= 0) {
+            int start = (int) (wordLimits >>> 32);
+            int end = (int) (wordLimits & 0x00000000FFFFFFFFL);
+            return mTransformed.subSequence(start, end).toString();
+        } else {
+            return null;
+        }
+        
+    }
+    
     @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         if (!isShown()) {
@@ -7032,114 +7187,98 @@
         super.onCreateContextMenu(menu);
         boolean added = false;
 
-        if (!isFocused()) {
-            if (isFocusable() && mInput != null) {
-                if (canCopy()) {
-                    MenuHandler handler = new MenuHandler();
-                    int name = com.android.internal.R.string.copyAll;
+        if (mIsInTextSelectionMode) {
+            MenuHandler handler = new MenuHandler();
+            
+            if (canCut()) {
+                menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
+                     setOnMenuItemClickListener(handler).
+                     setAlphabeticShortcut('x');
+            }
 
-                    menu.add(0, ID_COPY, 0, name).
-                        setOnMenuItemClickListener(handler).
-                        setAlphabeticShortcut('c');
-                    menu.setHeaderTitle(com.android.internal.R.string.
-                        editTextMenuTitle);
+            if (canCopy()) {
+                menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
+                     setOnMenuItemClickListener(handler).
+                     setAlphabeticShortcut('c');
+            }
+
+            if (canPaste()) {
+                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
+                     setOnMenuItemClickListener(handler).
+                     setAlphabeticShortcut('v');
+            }
+
+            menu.add(0, ID_STOP_SELECTING_TEXT, 0, com.android.internal.R.string.stopSelectingText).
+                 setOnMenuItemClickListener(handler);
+            
+            added = true;
+        } else {
+            /*
+            if (!isFocused()) {
+                if (isFocusable() && mInput != null) {
+                    if (canCopy()) {
+                        MenuHandler handler = new MenuHandler();
+                        menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
+                             setOnMenuItemClickListener(handler).
+                             setAlphabeticShortcut('c');
+                        menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
+                    }
+                }
+
+                //return;
+            }
+             */
+            MenuHandler handler = new MenuHandler();
+
+            if (canSelectText()) {
+                menu.add(0, ID_START_SELECTING_TEXT, 0, com.android.internal.R.string.selectText).
+                setOnMenuItemClickListener(handler);
+                added = true;
+            }
+            
+            if (canSelectAll()) {
+                menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
+                     setOnMenuItemClickListener(handler).
+                     setAlphabeticShortcut('a');
+                added = true;
+            }
+
+            if (mText instanceof Spanned) {
+                int selStart = getSelectionStart();
+                int selEnd = getSelectionEnd();
+
+                int min = Math.min(selStart, selEnd);
+                int max = Math.max(selStart, selEnd);
+
+                URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
+                        URLSpan.class);
+                if (urls.length == 1) {
+                    menu.add(0, ID_COPY_URL, 0, com.android.internal.R.string.copyUrl).
+                         setOnMenuItemClickListener(handler);
+                    added = true;
                 }
             }
+            
+            if (canPaste()) {
+                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
+                     setOnMenuItemClickListener(handler).
+                     setAlphabeticShortcut('v');
+            }
 
-            return;
-        }
-
-        MenuHandler handler = new MenuHandler();
-
-        if (canSelectAll()) {
-            menu.add(0, ID_SELECT_ALL, 0,
-                    com.android.internal.R.string.selectAll).
-                setOnMenuItemClickListener(handler).
-                setAlphabeticShortcut('a');
-            added = true;
-        }
-
-        boolean selection = getSelectionStart() != getSelectionEnd();
-
-        if (canSelectText()) {
-            if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
-                menu.add(0, ID_STOP_SELECTING_TEXT, 0,
-                        com.android.internal.R.string.stopSelectingText).
-                    setOnMenuItemClickListener(handler);
-                added = true;
-            } else {
-                menu.add(0, ID_START_SELECTING_TEXT, 0,
-                        com.android.internal.R.string.selectText).
-                    setOnMenuItemClickListener(handler);
+            if (isInputMethodTarget()) {
+                menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
+                     setOnMenuItemClickListener(handler);
                 added = true;
             }
-        }
 
-        if (canCut()) {
-            int name;
-            if (selection) {
-                name = com.android.internal.R.string.cut;
-            } else {
-                name = com.android.internal.R.string.cutAll;
-            }
-
-            menu.add(0, ID_CUT, 0, name).
-                setOnMenuItemClickListener(handler).
-                setAlphabeticShortcut('x');
-            added = true;
-        }
-
-        if (canCopy()) {
-            int name;
-            if (selection) {
-                name = com.android.internal.R.string.copy;
-            } else {
-                name = com.android.internal.R.string.copyAll;
-            }
-
-            menu.add(0, ID_COPY, 0, name).
-                setOnMenuItemClickListener(handler).
-                setAlphabeticShortcut('c');
-            added = true;
-        }
-
-        if (canPaste()) {
-            menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
-                    setOnMenuItemClickListener(handler).
-                    setAlphabeticShortcut('v');
-            added = true;
-        }
-
-        if (mText instanceof Spanned) {
-            int selStart = getSelectionStart();
-            int selEnd = getSelectionEnd();
-
-            int min = Math.min(selStart, selEnd);
-            int max = Math.max(selStart, selEnd);
-
-            URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
-                                                        URLSpan.class);
-            if (urls.length == 1) {
-                menu.add(0, ID_COPY_URL, 0,
-                         com.android.internal.R.string.copyUrl).
-                            setOnMenuItemClickListener(handler);
-                added = true;
-            }
-        }
-
-        if (isInputMethodTarget()) {
-            menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
-                    setOnMenuItemClickListener(handler);
-            added = true;
-        }
-
-        String word = getWordForDictionary();
-        if (word != null) {
-            menu.add(1, ID_ADD_TO_DICTIONARY, 0,
+            String word = getWordForDictionary();
+            if (word != null) {
+                menu.add(1, ID_ADD_TO_DICTIONARY, 0,
                      getContext().getString(com.android.internal.R.string.addToDictionary, word)).
-                    setOnMenuItemClickListener(handler);
-            added = true;
+                     setOnMenuItemClickListener(handler);
+                added = true;
 
+            }
         }
 
         if (added) {
@@ -7156,6 +7295,7 @@
         return imm != null && imm.isActive(this);
     }
     
+    // Context menu entries
     private static final int ID_SELECT_ALL = android.R.id.selectAll;
     private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
     private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText;
@@ -7181,22 +7321,15 @@
      * or {@link android.R.id#switchInputMethod}.
      */
     public boolean onTextContextMenuItem(int id) {
-        int selStart = getSelectionStart();
-        int selEnd = getSelectionEnd();
+        int min = 0;
+        int max = mText.length();
 
-        if (!isFocused()) {
-            selStart = 0;
-            selEnd = mText.length();
-        }
+        if (isFocused()) {
+            final int selStart = getSelectionStart();
+            final int selEnd = getSelectionEnd();
 
-        int min = Math.min(selStart, selEnd);
-        int max = Math.max(selStart, selEnd);
-
-        if (min < 0) {
-            min = 0;
-        }
-        if (max < 0) {
-            max = 0;
+            min = Math.max(0, Math.min(selStart, selEnd));
+            max = Math.max(0, Math.max(selStart, selEnd));
         }
 
         ClipboardManager clip = (ClipboardManager)getContext()
@@ -7204,63 +7337,44 @@
 
         switch (id) {
             case ID_SELECT_ALL:
-                Selection.setSelection((Spannable) mText, 0,
-                        mText.length());
+                Selection.setSelection((Spannable) mText, 0, mText.length());
+                startTextSelectionMode();
                 return true;
 
             case ID_START_SELECTING_TEXT:
-                MetaKeyKeyListener.startSelecting(this, (Spannable) mText);
+                startTextSelectionMode();
                 return true;
 
             case ID_STOP_SELECTING_TEXT:
-                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-                Selection.setSelection((Spannable) mText, getSelectionEnd());
+                stopTextSelectionMode();
                 return true;
 
-            case ID_CUT:
-                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
-                if (min == max) {
-                    min = 0;
-                    max = mText.length();
-                }
-
+            case ID_CUT:                
                 clip.setText(mTransformed.subSequence(min, max));
                 ((Editable) mText).delete(min, max);
+                stopTextSelectionMode();
                 return true;
 
             case ID_COPY:
-                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
-                if (min == max) {
-                    min = 0;
-                    max = mText.length();
-                }
-
                 clip.setText(mTransformed.subSequence(min, max));
+                stopTextSelectionMode();
                 return true;
 
             case ID_PASTE:
-                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
                 CharSequence paste = clip.getText();
 
                 if (paste != null) {
                     Selection.setSelection((Spannable) mText, max);
                     ((Editable) mText).replace(min, max, paste);
+                    stopTextSelectionMode();
                 }
-
                 return true;
 
             case ID_COPY_URL:
-                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
-                URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
-                                                       URLSpan.class);
+                URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
                 if (urls.length == 1) {
                     clip.setText(urls[0].getURL());
                 }
-
                 return true;
 
             case ID_SWITCH_INPUT_METHOD:
@@ -7279,13 +7393,13 @@
                     i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
                     getContext().startActivity(i);
                 }
-
                 return true;
             }
 
         return false;
     }
 
+    @Override
     public boolean performLongClick() {
         if (super.performLongClick()) {
             mEatTouchRelease = true;
@@ -7295,7 +7409,572 @@
         return false;
     }
 
-    @ViewDebug.ExportedProperty(category = "text")
+    private void startTextSelectionMode() {
+        if (mSelectionModifierCursorController == null) {
+            Log.w(LOG_TAG, "TextView has no selection controller. Action mode cancelled.");
+            return;
+        }
+
+        if (!requestFocus()) {
+            return;
+        }
+
+        selectCurrentWord();
+        mSelectionModifierCursorController.show();
+        mIsInTextSelectionMode = true;
+    }
+    
+    /**
+     * Same as {@link #stopTextSelectionMode()}, except that there is no cursor controller
+     * fade out animation. Needed since the drawable and their alpha values are shared by all
+     * TextViews. Switching from one TextView to another would fade the cursor controllers in the
+     * new one otherwise.
+     */
+    private void terminateTextSelectionMode() {
+        stopTextSelectionMode();
+        if (mSelectionModifierCursorController != null) {
+            SelectionModifierCursorController selectionModifierCursorController =
+                (SelectionModifierCursorController) mSelectionModifierCursorController;
+            selectionModifierCursorController.cancelFadeOutAnimation();
+        }
+    }
+
+    private void stopTextSelectionMode() {
+        if (mIsInTextSelectionMode) {
+            Selection.setSelection((Spannable) mText, getSelectionEnd());
+            if (mSelectionModifierCursorController != null) {
+                mSelectionModifierCursorController.hide();
+            }
+
+            mIsInTextSelectionMode = false;
+        }
+    }
+
+    /**
+     * A CursorController instance can be used to control a cursor in the text.
+     *
+     * It can be passed to an {@link ArrowKeyMovementMethod} which can intercepts events
+     * and send them to this object instead of the cursor.
+     */
+    public interface CursorController {
+        /* Cursor fade-out animation duration, in milliseconds. */
+        static final int FADE_OUT_DURATION = 400;
+
+        /**
+         * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
+         * See also {@link #hide()}.
+         */
+        public void show();
+
+        /**
+         * Hide the cursor controller from screen.
+         * See also {@link #show()}.
+         */
+        public void hide();
+
+        /**
+         * Update the controller's position.
+         */
+        public void updatePosition(int offset);
+
+        /**
+         * The controller and the cursor's positions can be link by a fixed offset,
+         * computed when the controller is touched, and then maintained as it moves
+         * @return Horizontal offset between the controller and the cursor.
+         */
+        public float getOffsetX();
+
+        /**
+         * @return Vertical offset between the controller and the cursor.
+         */
+        public float getOffsetY();
+
+        /**
+         * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller
+         * a chance to become active and/or visible.
+         * @param event The touch event
+         */
+        public void onTouchEvent(MotionEvent event);
+
+        /**
+         * Draws a visual representation of the controller on the canvas.
+         *
+         * Called at the end of {@link #draw(Canvas)}, in the content coordinates system.
+         * @param canvas The Canvas used by this TextView.
+         */
+        public void draw(Canvas canvas);
+    }
+
+    private class Handle {
+        Drawable mDrawable;
+        // Vertical extension of the touch region
+        int mTopExtension, mBottomExtension;
+        // Position of the virtual finger position on screen
+        int mHopSpotVertcalPosition;
+
+        Handle(Drawable drawable) {
+            mDrawable = drawable;
+        }
+
+        void positionAtCursor(final int offset, boolean bottom) {
+            final int drawableWidth = mDrawable.getIntrinsicWidth();
+            final int drawableHeight = mDrawable.getIntrinsicHeight();
+            final int line = mLayout.getLineForOffset(offset);
+            final int lineTop = mLayout.getLineTop(line);
+            final int lineBottom = mLayout.getLineBottom(line);
+
+            mHopSpotVertcalPosition = lineTop + (bottom ? (3 * (lineBottom - lineTop)) / 4 :
+                (lineBottom - lineTop) / 4);
+
+            final Rect bounds = sCursorControllerTempRect;
+            bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0);
+            bounds.top = (bottom ? lineBottom : lineTop) - drawableHeight / 2;
+
+            mTopExtension = bottom ? 0 : drawableHeight / 2;
+            mBottomExtension = drawableHeight;
+
+            // Extend touch region up when editing the last line of text (or a single line) so that
+            // it is easier to grab.
+            if (line == mLayout.getLineCount() - 1) {
+                mTopExtension = (lineBottom - lineTop) - drawableHeight / 2;
+            }
+
+            bounds.right = bounds.left + drawableWidth;
+            bounds.bottom = bounds.top + drawableHeight;
+
+            int boundTopBefore = bounds.top;
+            convertFromViewportToContentCoordinates(bounds);
+            mHopSpotVertcalPosition += bounds.top - boundTopBefore;
+            mDrawable.setBounds(bounds);
+            postInvalidate();
+        }
+
+        boolean hasFingerOn(float x, float y) {
+            // Simulate a 'fat finger' to ease grabbing of the controller.
+            // Expands according to controller image size instead of using dip distance.
+            // Assumes controller imager has a sensible size, proportionnal to screen density.
+            final int drawableWidth = mDrawable.getIntrinsicWidth();
+            final Rect fingerRect = sCursorControllerTempRect;
+            fingerRect.set((int) (x - drawableWidth / 2.0),
+                           (int) (y - mBottomExtension),
+                           (int) (x + drawableWidth / 2.0),
+                           (int) (y + mTopExtension));
+            return Rect.intersects(mDrawable.getBounds(), fingerRect);
+        }
+
+        void postInvalidate() {
+            final Rect bounds = mDrawable.getBounds();
+            TextView.this.postInvalidate(bounds.left, bounds.top, bounds.right, bounds.bottom);
+        }
+
+        void postInvalidateDelayed(long delay) {
+            final Rect bounds = mDrawable.getBounds();
+            TextView.this.postInvalidateDelayed(delay, bounds.left, bounds.top,
+                    bounds.right, bounds.bottom);
+        }
+    }
+
+    class InsertionPointCursorController implements CursorController {
+        private static final int DELAY_BEFORE_FADE_OUT = 2100;
+
+        // Whether or not the cursor control is currently visible
+        private boolean mIsVisible = false;
+        // Starting time of the fade timer
+        private long mFadeOutTimerStart;
+        // The cursor controller image
+        private final Handle mHandle;
+        // Used to detect a tap (vs drag) on the controller
+        private long mOnDownTimerStart;
+        // Offset between finger hot point on cursor controller and actual cursor
+        private float mOffsetX, mOffsetY;
+
+        InsertionPointCursorController() {
+            Resources res = mContext.getResources();
+            mHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle));
+        }
+
+        public void show() {
+            updateDrawablePosition();
+            // Has to be done after updatePosition, so that previous position invalidate
+            // in only done if necessary.
+            mIsVisible = true;
+        }
+
+        public void hide() {
+            if (mIsVisible) {
+                long time = System.currentTimeMillis();
+                // Start fading out, only if not already in progress
+                if (time - mFadeOutTimerStart < DELAY_BEFORE_FADE_OUT) {
+                    mFadeOutTimerStart = time - DELAY_BEFORE_FADE_OUT;
+                    mHandle.postInvalidate();
+                }
+            }
+        }
+
+        public void draw(Canvas canvas) {
+            if (mIsVisible) {
+                int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart);
+                if (time <= DELAY_BEFORE_FADE_OUT) {
+                    mHandle.postInvalidateDelayed(DELAY_BEFORE_FADE_OUT - time);
+                } else {
+                    time -= DELAY_BEFORE_FADE_OUT;
+                    if (time <= FADE_OUT_DURATION) {
+                        final int alpha = (int)
+                        ((255.0 * (FADE_OUT_DURATION - time)) / FADE_OUT_DURATION);
+                        mHandle.mDrawable.setAlpha(alpha);
+                        mHandle.postInvalidateDelayed(30);
+                    } else {
+                        mHandle.mDrawable.setAlpha(0);
+                        mIsVisible = false;
+                    }
+                }
+                mHandle.mDrawable.draw(canvas);
+            }
+        }
+
+        public void updatePosition(int offset) {
+            if (offset == getSelectionStart()) {
+                return; // No change, no need to redraw
+            }
+            Selection.setSelection((Spannable) mText, offset);
+            updateDrawablePosition();
+        }
+
+        private void updateDrawablePosition() {
+            if (mIsVisible) {
+                // Clear previous cursor controller before bounds are updated
+                mHandle.postInvalidate();
+            }
+
+            final int offset = getSelectionStart();
+
+            if (offset < 0) {
+                // Should never happen, safety check.
+                Log.w(LOG_TAG, "Update cursor controller position called with no cursor");
+                mIsVisible = false;
+                return;
+            }
+
+            mHandle.positionAtCursor(offset, true);
+
+            mFadeOutTimerStart = System.currentTimeMillis();
+            mHandle.mDrawable.setAlpha(255);
+        }
+
+        public void onTouchEvent(MotionEvent event) {
+            if (isFocused() && isTextEditable() && mIsVisible) {
+                switch (event.getActionMasked()) {
+                    case MotionEvent.ACTION_DOWN : {
+                        final float x = event.getX();
+                        final float y = event.getY();
+
+                        if (mHandle.hasFingerOn(x, y)) {
+                            show();
+
+                            if (mMovement instanceof ArrowKeyMovementMethod) {
+                                ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
+                            }
+
+                            if (mParent != null) {
+                                // Prevent possible scrollView parent from scrolling, so that
+                                // we can use auto-scrolling.
+                                mParent.requestDisallowInterceptTouchEvent(true);
+                            }
+
+                            final Rect bounds = mHandle.mDrawable.getBounds();
+                            mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
+                            mOffsetY = mHandle.mHopSpotVertcalPosition - y;
+
+                            mOnDownTimerStart = event.getEventTime();
+                        }
+                        break;
+                    }
+
+                    case MotionEvent.ACTION_UP : {
+                        int time = (int) (event.getEventTime() - mOnDownTimerStart);
+
+                        if (time <= ViewConfiguration.getTapTimeout()) {
+                            // A tap on the controller (not a drag) will move the cursor
+                            int offset = getOffset((int) event.getX(), (int) event.getY());
+                            Selection.setSelection((Spannable) mText, offset);
+
+                            // Modified by cancelLongPress and prevents the cursor from changing
+                            mScrolled = false;
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+
+        public float getOffsetX() {
+            return mOffsetX;
+        }
+
+        public float getOffsetY() {
+            return mOffsetY;
+        }
+    }
+
+    class SelectionModifierCursorController implements CursorController {
+        // Whether or not the selection controls are currently visible
+        private boolean mIsVisible = false;
+        // Whether that start or the end of selection controller is dragged
+        private boolean mStartIsDragged = false;
+        // Starting time of the fade timer
+        private long mFadeOutTimerStart;
+        // The cursor controller images
+        private final Handle mStartHandle, mEndHandle;
+        // Offset between finger hot point on active cursor controller and actual cursor
+        private float mOffsetX, mOffsetY;
+        // The offsets of that last touch down event. Remembered to start selection there.
+        private int mMinTouchOffset, mMaxTouchOffset;
+
+        SelectionModifierCursorController() {
+            Resources res = mContext.getResources();
+            mStartHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle));
+            mEndHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle));
+        }
+
+        public void show() {
+            updateDrawablesPositions();
+            // Has to be done after updatePosition, so that previous position invalidate
+            // in only done if necessary.
+            mIsVisible = true;
+            mFadeOutTimerStart = -1;
+            hideInsertionPointCursorController();
+        }
+
+        public void hide() {
+            if (mIsVisible && (mFadeOutTimerStart < 0)) {
+                mFadeOutTimerStart = System.currentTimeMillis();
+                mStartHandle.postInvalidate();
+                mEndHandle.postInvalidate();
+            }
+        }
+
+        public void cancelFadeOutAnimation() {
+            mIsVisible = false;
+            mStartHandle.postInvalidate();
+            mEndHandle.postInvalidate();
+        }
+
+        public void draw(Canvas canvas) {
+            if (mIsVisible) {
+                if (mFadeOutTimerStart >= 0) {
+                    int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart);
+                    if (time <= FADE_OUT_DURATION) {
+                        final int alpha = 255 * (FADE_OUT_DURATION - time) / FADE_OUT_DURATION;
+                        mStartHandle.mDrawable.setAlpha(alpha);
+                        mEndHandle.mDrawable.setAlpha(alpha);
+                        mStartHandle.postInvalidateDelayed(30);
+                        mEndHandle.postInvalidateDelayed(30);
+                    } else {
+                        mStartHandle.mDrawable.setAlpha(0);
+                        mEndHandle.mDrawable.setAlpha(0);
+                        mIsVisible = false;
+                    }
+                }
+                mStartHandle.mDrawable.draw(canvas);
+                mEndHandle.mDrawable.draw(canvas);
+            }
+        }
+
+        public void updatePosition(int offset) {
+            int selectionStart = getSelectionStart();
+            int selectionEnd = getSelectionEnd();
+
+            // Handle the case where start and end are swapped, making sure start <= end
+            if (mStartIsDragged) {
+                if (offset <= selectionEnd) {
+                    if (selectionStart == offset) {
+                        return; // no change, no need to redraw;
+                    }
+                    selectionStart = offset;
+                } else {
+                    selectionStart = selectionEnd;
+                    selectionEnd = offset;
+                    mStartIsDragged = false;
+                }
+            } else {
+                if (offset >= selectionStart) {
+                    if (selectionEnd == offset) {
+                        return; // no change, no need to redraw;
+                    }
+                    selectionEnd = offset;
+                } else {
+                    selectionEnd = selectionStart;
+                    selectionStart = offset;
+                    mStartIsDragged = true;
+                }
+            }
+
+            Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+            updateDrawablesPositions();
+        }
+
+        private void updateDrawablesPositions() {
+            if (mIsVisible) {
+                // Clear previous cursor controller before bounds are updated
+                mStartHandle.postInvalidate();
+                mEndHandle.postInvalidate();
+            }
+
+            final int selectionStart = getSelectionStart();
+            final int selectionEnd = getSelectionEnd();
+
+            if ((selectionStart < 0) || (selectionEnd < 0)) {
+                // Should never happen, safety check.
+                Log.w(LOG_TAG, "Update selection controller position called with no cursor");
+                mIsVisible = false;
+                return;
+            }
+
+            boolean oneLineSelection = mLayout.getLineForOffset(selectionStart) == mLayout.getLineForOffset(selectionEnd); 
+            mStartHandle.positionAtCursor(selectionStart, oneLineSelection);
+            mEndHandle.positionAtCursor(selectionEnd, true);
+
+            mStartHandle.mDrawable.setAlpha(255);
+            mEndHandle.mDrawable.setAlpha(255);
+        }
+
+        public void onTouchEvent(MotionEvent event) {
+            if (isTextEditable()) {
+                switch (event.getActionMasked()) {
+                    case MotionEvent.ACTION_DOWN:
+                        final int x = (int) event.getX();
+                        final int y = (int) event.getY();
+
+                        // Remember finger down position, to be able to start selection from there
+                        mMinTouchOffset = mMaxTouchOffset = getOffset(x, y);
+
+                        if (mIsVisible) {
+                            if (mMovement instanceof ArrowKeyMovementMethod) {
+                                boolean isOnStart = mStartHandle.hasFingerOn(x, y);
+                                boolean isOnEnd = mEndHandle.hasFingerOn(x, y);
+                                if (isOnStart || isOnEnd) {
+                                    if (mParent != null) {
+                                        // Prevent possible scrollView parent from scrolling, so
+                                        // that we can use auto-scrolling.
+                                        mParent.requestDisallowInterceptTouchEvent(true);
+                                    }
+
+                                    // In case both controllers are under finger (very small
+                                    // selection region), arbitrarily pick end controller.
+                                    mStartIsDragged = !isOnEnd;
+                                    final Handle draggedHandle = mStartIsDragged ? mStartHandle : mEndHandle;
+                                    final Rect bounds = draggedHandle.mDrawable.getBounds();
+                                    mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
+                                    mOffsetY = draggedHandle.mHopSpotVertcalPosition - y;
+
+                                    ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
+                                }
+                            }
+                        }
+                        break;
+
+                    case MotionEvent.ACTION_POINTER_DOWN:
+                    case MotionEvent.ACTION_POINTER_UP:
+                        // Handle multi-point gestures. Keep min and max offset positions.
+                        // Only activated for devices that correctly handle multi-touch.
+                        if (mContext.getPackageManager().hasSystemFeature(
+                                PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
+                            updateMinAndMaxOffsets(event);
+                        }
+                        break;
+                }
+            }
+        }
+
+        /**
+         * @param event
+         */
+        private void updateMinAndMaxOffsets(MotionEvent event) {
+            int pointerCount = event.getPointerCount();
+            for (int index = 0; index < pointerCount; index++) {
+                final int x = (int) event.getX(index);
+                final int y = (int) event.getY(index);
+                int offset = getOffset(x, y);
+                if (offset < mMinTouchOffset) mMinTouchOffset = offset;
+                if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
+            }
+        }
+
+        public int getMinTouchOffset() {
+            return mMinTouchOffset;
+        }
+
+        public int getMaxTouchOffset() {
+            return mMaxTouchOffset;
+        }
+
+        public float getOffsetX() {
+            return mOffsetX;
+        }
+
+        public float getOffsetY() {
+            return mOffsetY;
+        }
+
+        /**
+         * @return true iff this controller is currently used to move the selection start.
+         */
+        public boolean isSelectionStartDragged() {
+            return mIsVisible && mStartIsDragged;
+        }
+    }
+
+    private void hideInsertionPointCursorController() {
+        if (mInsertionPointCursorController != null) {
+            mInsertionPointCursorController.hide();
+        }
+    }
+
+    private void hideControllers() {
+        hideInsertionPointCursorController();
+        stopTextSelectionMode();
+    }
+
+    /**
+     * Get the offset character closest to the specified absolute position.
+     *
+     * @param x The horizontal absolute position of a point on screen
+     * @param y The vertical absolute position of a point on screen
+     * @return the character offset for the character whose position is closest to the specified
+     *  position. Returns -1 if there is no layout.
+     *
+     * @hide
+     */
+    public int getOffset(int x, int y) {
+        x -= getTotalPaddingLeft();
+        y -= getTotalPaddingTop();
+
+        // Clamp the position to inside of the view.
+        if (x < 0) {
+            x = 0;
+        } else if (x >= (getWidth() - getTotalPaddingRight())) {
+            x = getWidth()-getTotalPaddingRight() - 1;
+        }
+        if (y < 0) {
+            y = 0;
+        } else if (y >= (getHeight() - getTotalPaddingBottom())) {
+            y = getHeight()-getTotalPaddingBottom() - 1;
+        }
+
+        x += getScrollX();
+        y += getScrollY();
+
+        Layout layout = getLayout();
+        if (layout != null) {
+            final int line = layout.getLineForVertical(y);
+            final int offset = layout.getOffsetForHorizontal(line, x);
+            return offset;
+        } else {
+            return -1;
+        }
+    }
+
+    @ViewDebug.ExportedProperty
     private CharSequence            mText;
     private CharSequence            mTransformed;
     private BufferType              mBufferType = BufferType.NORMAL;
@@ -7313,16 +7992,25 @@
     private ArrayList<TextWatcher>  mListeners = null;
 
     // display attributes
-    private TextPaint               mTextPaint;
+    private final TextPaint         mTextPaint;
     private boolean                 mUserSetTextScaleX;
-    private Paint                   mHighlightPaint;
-    private int                     mHighlightColor = 0xFFBBDDFF;
+    private final Paint             mHighlightPaint;
+    private int                     mHighlightColor = 0xCC475925;
     private Layout                  mLayout;
 
     private long                    mShowCursor;
     private Blink                   mBlink;
     private boolean                 mCursorVisible = true;
 
+    // Cursor Controllers. Null when disabled.
+    private CursorController        mInsertionPointCursorController;
+    private CursorController        mSelectionModifierCursorController;
+    private boolean                 mShouldStartTextSelectionMode = false;
+    private boolean                 mIsInTextSelectionMode = false;
+    // Created once and shared by different CursorController helper methods.
+    // Only one cursor controller is active at any time which prevent race conditions.
+    private static Rect             sCursorControllerTempRect = new Rect();
+
     private boolean                 mSelectAllOnFocus = false;
 
     private int                     mGravity = Gravity.TOP | Gravity.LEFT;
diff --git a/core/res/res/drawable-hdpi/text_select_handle.png b/core/res/res/drawable-hdpi/text_select_handle.png
new file mode 100644
index 0000000..80d48ab
--- /dev/null
+++ b/core/res/res/drawable-hdpi/text_select_handle.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/text_select_handle.png b/core/res/res/drawable-mdpi/text_select_handle.png
new file mode 100644
index 0000000..93a5a15
--- /dev/null
+++ b/core/res/res/drawable-mdpi/text_select_handle.png
Binary files differ
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index c442fee..245643b 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Označit text"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Zastavit označování textu"</string>
     <string name="cut" msgid="3092569408438626261">"Vyjmout"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Vyjmout vše"</string>
     <string name="copy" msgid="2681946229533511987">"Kopírovat"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Kopírovat vše"</string>
     <string name="paste" msgid="5629880836805036433">"Vložit"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopírovat adresu URL"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Metoda zadávání dat"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 3ea8266..e7e8019 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Marker tekst"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Hold op med at markere tekst"</string>
     <string name="cut" msgid="3092569408438626261">"Klip"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Klip alle"</string>
     <string name="copy" msgid="2681946229533511987">"Kopier"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Kopier alle"</string>
     <string name="paste" msgid="5629880836805036433">"Indsæt"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopier webadresse"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Inputmetode"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index bf3efd6..edbe4c4 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Text auswählen"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Textauswahl beenden"</string>
     <string name="cut" msgid="3092569408438626261">"Ausschneiden"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Alles ausschneiden"</string>
     <string name="copy" msgid="2681946229533511987">"Kopieren"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Alles kopieren"</string>
     <string name="paste" msgid="5629880836805036433">"Einfügen"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URL kopieren"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Eingabemethode"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 2c61e1f..cfa36d2 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Επιλογή κειμένου"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Διακοπή επιλογής κειμένου"</string>
     <string name="cut" msgid="3092569408438626261">"Αποκοπή"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Αποκοπή όλων"</string>
     <string name="copy" msgid="2681946229533511987">"Αντιγραφή"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Αντιγραφή όλων"</string>
     <string name="paste" msgid="5629880836805036433">"Επικόλληση"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Αντιγραφή διεύθυνσης URL"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Μέθοδος εισόδου"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 3de378b..1cbfbba 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1079,11 +1079,9 @@
     <skip />
     <!-- no translation found for cut (5845613239192595662) -->
     <skip />
-    <!-- no translation found for cutAll (4474519683293791451) -->
     <skip />
     <!-- no translation found for copy (8603721575469529820) -->
     <skip />
-    <!-- no translation found for copyAll (4777548804630476932) -->
     <skip />
     <!-- no translation found for paste (6458036735811828538) -->
     <skip />
diff --git a/core/res/res/values-en-rSG/strings.xml b/core/res/res/values-en-rSG/strings.xml
index 2ec6b0b..09a8490 100644
--- a/core/res/res/values-en-rSG/strings.xml
+++ b/core/res/res/values-en-rSG/strings.xml
@@ -1074,11 +1074,9 @@
     <skip />
     <!-- no translation found for cut (5845613239192595662) -->
     <skip />
-    <!-- no translation found for cutAll (4474519683293791451) -->
     <skip />
     <!-- no translation found for copy (8603721575469529820) -->
     <skip />
-    <!-- no translation found for copyAll (4777548804630476932) -->
     <skip />
     <!-- no translation found for paste (6458036735811828538) -->
     <skip />
diff --git a/core/res/res/values-en-rUS/strings.xml b/core/res/res/values-en-rUS/strings.xml
index 05f30fc..fdc0d69 100644
--- a/core/res/res/values-en-rUS/strings.xml
+++ b/core/res/res/values-en-rUS/strings.xml
@@ -1115,11 +1115,9 @@
     <skip />
     <!-- no translation found for cut (5845613239192595662) -->
     <skip />
-    <!-- no translation found for cutAll (4474519683293791451) -->
     <skip />
     <!-- no translation found for copy (8603721575469529820) -->
     <skip />
-    <!-- no translation found for copyAll (4777548804630476932) -->
     <skip />
     <!-- no translation found for paste (6458036735811828538) -->
     <skip />
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index afc2862..dbf32b6 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Seleccionar texto"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Detener la selección de texto"</string>
     <string name="cut" msgid="3092569408438626261">"Cortar"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Cortar llamada"</string>
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Copiar todo"</string>
     <string name="paste" msgid="5629880836805036433">"Pegar"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 045b6a1..425fca3 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Seleccionar texto"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Detener selección de texto"</string>
     <string name="cut" msgid="3092569408438626261">"Cortar"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Cortar todo"</string>
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Copiar todo"</string>
     <string name="paste" msgid="5629880836805036433">"Pegar"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Método de introducción de texto"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 3ab7814..2dacb30 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Sélectionner le texte"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Arrêter sélection de texte"</string>
     <string name="cut" msgid="3092569408438626261">"Couper"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Tout couper"</string>
     <string name="copy" msgid="2681946229533511987">"Copier"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Tout copier"</string>
     <string name="paste" msgid="5629880836805036433">"Coller"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copier l\'URL"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Mode de saisie"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 6e50d21..c7aac82 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Seleziona testo"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Termina selezione testo"</string>
     <string name="cut" msgid="3092569408438626261">"Taglia"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Taglia tutto"</string>
     <string name="copy" msgid="2681946229533511987">"Copia"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Copia tutto"</string>
     <string name="paste" msgid="5629880836805036433">"Incolla"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copia URL"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Metodo inserimento"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index f1e98da..25e977d 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"テキストを選択"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"テキストの選択を終了"</string>
     <string name="cut" msgid="3092569408438626261">"切り取り"</string>
-    <string name="cutAll" msgid="2436383270024931639">"すべて切り取り"</string>
     <string name="copy" msgid="2681946229533511987">"コピー"</string>
-    <string name="copyAll" msgid="2590829068100113057">"すべてコピー"</string>
     <string name="paste" msgid="5629880836805036433">"貼り付け"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URLをコピー"</string>
     <string name="inputMethod" msgid="1653630062304567879">"入力方法"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 712100e..faf589e 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"텍스트 선택"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"텍스트 선택 중지"</string>
     <string name="cut" msgid="3092569408438626261">"잘라내기"</string>
-    <string name="cutAll" msgid="2436383270024931639">"모두 잘라내기"</string>
     <string name="copy" msgid="2681946229533511987">"복사"</string>
-    <string name="copyAll" msgid="2590829068100113057">"모두 복사"</string>
     <string name="paste" msgid="5629880836805036433">"붙여넣기"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URL 복사"</string>
     <string name="inputMethod" msgid="1653630062304567879">"입력 방법"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index f09039d..2c23d53 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Merk tekst"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Slutt å merke tekst"</string>
     <string name="cut" msgid="3092569408438626261">"Klipp ut"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Klipp ut alt"</string>
     <string name="copy" msgid="2681946229533511987">"Kopier"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Kopier alt"</string>
     <string name="paste" msgid="5629880836805036433">"Lim inn"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopier URL"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Inndatametode"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 17a2fe4..10a8e74 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Tekst selecteren"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Stoppen met tekst selecteren"</string>
     <string name="cut" msgid="3092569408438626261">"Knippen"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Alles knippen"</string>
     <string name="copy" msgid="2681946229533511987">"Kopiëren"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Alles kopiëren"</string>
     <string name="paste" msgid="5629880836805036433">"Plakken"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URL kopiëren"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Invoermethode"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 87e045df3..70c3170 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Zaznacz tekst"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Zatrzymaj wybieranie tekstu"</string>
     <string name="cut" msgid="3092569408438626261">"Wytnij"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Wytnij wszystko"</string>
     <string name="copy" msgid="2681946229533511987">"Kopiuj"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Kopiuj wszystko"</string>
     <string name="paste" msgid="5629880836805036433">"Wklej"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopiuj adres URL"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Metoda wprowadzania"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 3fb8d55..9a8c6e9 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Seleccionar texto"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Parar selecção de texto"</string>
     <string name="cut" msgid="3092569408438626261">"Cortar"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Cortar tudo"</string>
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Copiar tudo"</string>
     <string name="paste" msgid="5629880836805036433">"Colar"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 81ceaf3..2119539 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Selecionar texto"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Parar seleção de texto"</string>
     <string name="cut" msgid="3092569408438626261">"Recortar"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Recortar tudo"</string>
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Copiar tudo"</string>
     <string name="paste" msgid="5629880836805036433">"Colar"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 4176886..a99107e 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Выбрать текст"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Остановить выделение текста"</string>
     <string name="cut" msgid="3092569408438626261">"Вырезать"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Вырезать все"</string>
     <string name="copy" msgid="2681946229533511987">"Копировать"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Копировать все"</string>
     <string name="paste" msgid="5629880836805036433">"Вставить"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Копировать URL"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Способ ввода"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 4c78f4f..b3e415d 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Markera text"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Sluta välja text"</string>
     <string name="cut" msgid="3092569408438626261">"Klipp ut"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Klipp ut alla"</string>
     <string name="copy" msgid="2681946229533511987">"Kopiera"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Kopiera alla"</string>
     <string name="paste" msgid="5629880836805036433">"Klistra in"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopiera webbadress"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Indatametod"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index f2fd734..a6bc41e 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"Metin seç"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Metin seçmeyi durdur"</string>
     <string name="cut" msgid="3092569408438626261">"Kes"</string>
-    <string name="cutAll" msgid="2436383270024931639">"Tümünü kes"</string>
     <string name="copy" msgid="2681946229533511987">"Kopyala"</string>
-    <string name="copyAll" msgid="2590829068100113057">"Tümünü kopyala"</string>
     <string name="paste" msgid="5629880836805036433">"Yapıştır"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URL\'yi kopyala"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Giriş yöntemi"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index a0fb721..aa3f12d 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"选择文字"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"停止选择文字"</string>
     <string name="cut" msgid="3092569408438626261">"剪切"</string>
-    <string name="cutAll" msgid="2436383270024931639">"全部剪切"</string>
     <string name="copy" msgid="2681946229533511987">"复制"</string>
-    <string name="copyAll" msgid="2590829068100113057">"全部复制"</string>
     <string name="paste" msgid="5629880836805036433">"粘贴"</string>
     <string name="copyUrl" msgid="2538211579596067402">"复制网址"</string>
     <string name="inputMethod" msgid="1653630062304567879">"输入法"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index d5dd857..5bf0342 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -706,9 +706,7 @@
     <string name="selectText" msgid="3889149123626888637">"選取文字"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"停止選取文字"</string>
     <string name="cut" msgid="3092569408438626261">"剪下"</string>
-    <string name="cutAll" msgid="2436383270024931639">"全部剪下"</string>
     <string name="copy" msgid="2681946229533511987">"複製"</string>
-    <string name="copyAll" msgid="2590829068100113057">"全部複製"</string>
     <string name="paste" msgid="5629880836805036433">"貼上"</string>
     <string name="copyUrl" msgid="2538211579596067402">"複製網址"</string>
     <string name="inputMethod" msgid="1653630062304567879">"輸入方式"</string>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index bd65fee..1df424b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1245,7 +1245,7 @@
   <public type="anim" name="cycle_interpolator" id="0x010a000c" />
 
 <!-- ===============================================================
-     Resources introduced in kraken.
+     Resources introduced in Gingerbread.
      =============================================================== -->
      
   <public type="attr" name="logo" id="0x010102be" />
@@ -1273,5 +1273,4 @@
   <public-padding type="dimen" name="kraken_resource_pad" end="0x01050010" />
   <public-padding type="color" name="kraken_resource_pad" end="0x01060020" />
   <public-padding type="array" name="kraken_resource_pad" end="0x01070010" />
-
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 62fd169..52abe45 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1854,23 +1854,17 @@
     <string name="selectAll">Select all</string>
 
     <!-- Item on EditText context menu. This action is used to start selecting text in the edit field. -->
-    <string name="selectText">Select text</string>
+    <string name="selectText">Select word</string>
 
-    <!-- Item on EditText context menu. This action is used to start selecting text in the edit field. -->
+    <!-- Item on EditText context menu. This action is used to stop selecting text in the edit field. -->
     <string name="stopSelectingText">Stop selecting text</string>
 
     <!-- Item on EditText context menu.  This action is used to cut selected the text into the clipboard.  -->
     <string name="cut">Cut</string>
 
-    <!-- Item on EditText context menu. This action is used to cut all the text into the clipboard. -->
-    <string name="cutAll">Cut all</string>
-
     <!-- Item on EditText context menu. This action is used to cut selected the text into the clipboard. -->
     <string name="copy">Copy</string>
 
-    <!-- Item on EditText context menu. This action is used to copy all the text into the clipboard. -->
-    <string name="copyAll">Copy all</string>
-
     <!-- Item on EditText context menu. This action is used t o paste from the clipboard into the eidt field -->
     <string name="paste">Paste</string>
 
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 98ffb8b..7830224 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -75,6 +75,7 @@
         bottom = r.bottom;
     }
 
+    @Override
     public boolean equals(Object obj) {
         Rect r = (Rect) obj;
         if (r != null) {
@@ -84,6 +85,7 @@
         return false;
     }
 
+    @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(32);
         sb.append("Rect("); sb.append(left); sb.append(", ");
@@ -351,7 +353,7 @@
      * rectangle, return true and set this rectangle to that intersection,
      * otherwise return false and do not change this rectangle. No check is
      * performed to see if either rectangle is empty. Note: To just test for
-     * intersection, use intersects()
+     * intersection, use {@link #intersects(Rect, Rect)}.
      *
      * @param left The left side of the rectangle being intersected with this
      *             rectangle
@@ -445,7 +447,7 @@
     /**
      * Returns true iff the two specified rectangles intersect. In no event are
      * either of the rectangles modified. To record the intersection,
-     * use intersect() or setIntersect().
+     * use {@link #intersect(Rect)} or {@link #setIntersect(Rect, Rect)}.
      *
      * @param a The first rectangle being tested for intersection
      * @param b The second rectangle being tested for intersection