Added overlay support for drawing/responding to text anchors.

Overlays let views draw and respond to touch events outside of their
bounds. This allows selection anchors to be friendlier and easier to
grab. This is currently private API, pending further evaluation.

Added themes/styles for text selection anchors.

Added assets for text selection anchors as provided by UX. The
left/right anchors are currently not suitable for use. They are here
for bookkeeping and replacement later. The theme currently uses the
'middle' anchor asset for all three. This will be changed once assets
are ready.

Change-Id: I01b21e5ae90cab201f86f38f2f5eeaf2bd7f6bcd
diff --git a/api/current.xml b/api/current.xml
index aff615c..8651c4d 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -5828,28 +5828,6 @@
  visibility="public"
 >
 </field>
-<field name="kraken_resource_pad58"
- type="int"
- transient="false"
- volatile="false"
- value="16843463"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="kraken_resource_pad59"
- type="int"
- transient="false"
- volatile="false"
- value="16843462"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="kraken_resource_pad6"
  type="int"
  transient="false"
@@ -5861,17 +5839,6 @@
  visibility="public"
 >
 </field>
-<field name="kraken_resource_pad60"
- type="int"
- transient="false"
- volatile="false"
- value="16843461"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="kraken_resource_pad7"
  type="int"
  transient="false"
@@ -9513,6 +9480,39 @@
  visibility="public"
 >
 </field>
+<field name="textSelectHandle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843463"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="textSelectHandleLeft"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843461"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="textSelectHandleRight"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843462"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="textSize"
  type="int"
  transient="false"
@@ -223937,8 +223937,19 @@
  visibility="public"
 >
 </method>
+<method name="isShowing"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="onTouchEvent"
- return="void"
+ return="boolean"
  abstract="true"
  native="false"
  synchronized="false"
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b794a6a..6e395c8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1551,6 +1551,12 @@
     private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
 
     /**
+     * Indicates that this view has a visible/touchable overlay.
+     * @hide
+     */
+    static final int HAS_OVERLAY = 0x10000000;
+
+    /**
      * Always allow a user to overscroll this view, provided it is a
      * view that can scroll.
      *
@@ -2837,6 +2843,57 @@
         resetPressedState();
     }
 
+    /**
+     * Enable or disable drawing overlays after a full drawing pass. This enables a view to
+     * draw on a topmost overlay layer after normal drawing completes and get right of first
+     * refusal for touch events in the window.
+     * 
+     * <em>Warning:</em> Views that use this feature should take care to disable/enable overlay
+     * appropriately when they are attached/detached from their window. All overlays should be
+     * disabled when detached.
+     * 
+     * @param enabled true if overlay drawing should be enabled for this view, false otherwise
+     * 
+     * @see #onDrawOverlay(Canvas)
+     * 
+     * @hide
+     */
+    protected void setOverlayEnabled(boolean enabled) {
+        final boolean oldValue = (mPrivateFlags & HAS_OVERLAY) == HAS_OVERLAY;
+        mPrivateFlags = (mPrivateFlags & ~HAS_OVERLAY) | (enabled ? HAS_OVERLAY : 0);
+        if (enabled != oldValue) {
+            final ViewParent parent = getParent();
+            if (parent != null) {
+                try {
+                    parent.childOverlayStateChanged(this);
+                } catch (AbstractMethodError e) {
+                    Log.e(VIEW_LOG_TAG, "Could not propagate hasOverlay state", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * @return true if this View has an overlay enabled.
+     * 
+     * @see #setOverlayEnabled(boolean)
+     * @see #onDrawOverlay(Canvas)
+     * 
+     * @hide
+     */
+    public boolean isOverlayEnabled() {
+        return (mPrivateFlags & HAS_OVERLAY) == HAS_OVERLAY;
+    }
+
+    /**
+     * Override this method to draw on an overlay layer above all other views in the window
+     * after the standard drawing pass is complete. This allows a view to draw outside its
+     * normal boundaries.
+     * @hide
+     */
+    public void onDrawOverlay(Canvas canvas) {
+    }
+
     private void resetPressedState() {
         if ((mViewFlags & ENABLED_MASK) == DISABLED) {
             return;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 28bed3a..fd6769c 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -228,6 +228,11 @@
     protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
 
     /**
+     * When set, at least one child of this ViewGroup will return true from hasOverlay.
+     */
+    private static final int FLAG_CHILD_HAS_OVERLAY = 0x100000;
+
+    /**
      * Indicates which types of drawing caches are to be kept in memory.
      * This field should be made private, so it is hidden from the SDK.
      * {@hide}
@@ -854,6 +859,34 @@
                 final int scrolledYInt = (int) scrolledYFloat;
                 final View[] children = mChildren;
                 final int count = mChildrenCount;
+
+                // Check for children with overlays first. They don't rely on hit rects to determine
+                // if they can accept a new touch event.
+                if ((mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY) {
+                    for (int i = count - 1; i >= 0; i--) {
+                        final View child = children[i];
+                        // Don't let children respond to events as an overlay during an animation.
+                        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
+                                && child.getAnimation() == null
+                                && child.isOverlayEnabled()) {
+                            // offset the event to the view's coordinate system
+                            final float xc = scrolledXFloat - child.mLeft;
+                            final float yc = scrolledYFloat - child.mTop;
+                            ev.setLocation(xc, yc);
+                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
+                            if (child.dispatchTouchEvent(ev))  {
+                                // Event handled, we have a target now.
+                                mMotionTarget = child;
+                                return true;
+                            }
+                            // The event didn't get handled, try the next view.
+                            // Don't reset the event's location, it's not
+                            // necessary here.
+                        }
+                    }
+                }
+
+                // Now check views normally.
                 for (int i = count - 1; i >= 0; i--) {
                     final View child = children[i];
                     if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
@@ -2312,6 +2345,8 @@
         if (clearChildFocus != null) {
             clearChildFocus(clearChildFocus);
         }
+
+        mGroupFlags &= ~FLAG_CHILD_HAS_OVERLAY;
     }
 
     /**
@@ -2534,7 +2569,8 @@
                 final int left = mLeft;
                 final int top = mTop;
 
-                if (dirty.intersect(0, 0, mRight - left, mBottom - top) ||
+                if ((mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY ||
+                        dirty.intersect(0, 0, mRight - left, mBottom - top) ||
                         (mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) {
                     mPrivateFlags &= ~DRAWING_CACHE_VALID;
 
@@ -3453,6 +3489,69 @@
     }
 
     /**
+     * Called when a child's overlay state changes between enabled/disabled.
+     * @param child Child view whose state has changed or null
+     * @hide
+     */
+    public void childOverlayStateChanged(View child) {
+        boolean childHasOverlay = false;
+        if (child != null) {
+            childHasOverlay = child.isOverlayEnabled();
+        } else {
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                if (childHasOverlay |= getChildAt(i).isOverlayEnabled()) {
+                    break;
+                }
+            }
+        }
+        
+        final boolean hasChildWithOverlay = childHasOverlay ||
+                (mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY;
+
+        final boolean oldValue = isOverlayEnabled();
+        mGroupFlags = (mGroupFlags & ~FLAG_CHILD_HAS_OVERLAY) |
+                (hasChildWithOverlay ? FLAG_CHILD_HAS_OVERLAY : 0);
+        if (isOverlayEnabled() != oldValue) {
+            final ViewParent parent = getParent();
+            if (parent != null) {
+                try {
+                    parent.childOverlayStateChanged(this);
+                } catch (AbstractMethodError e) {
+                    Log.e("ViewGroup", "Could not propagate hasOverlay state", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isOverlayEnabled() {
+        return super.isOverlayEnabled() ||
+                ((mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void onDrawOverlay(Canvas canvas) {
+        if ((mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY) {
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                if (child.isOverlayEnabled()) {
+                    canvas.translate(child.mLeft + child.mScrollX, child.mTop + child.mScrollY);
+                    child.onDrawOverlay(canvas);
+                    canvas.translate(-(child.mLeft + child.mScrollX),
+                            -(child.mTop + child.mScrollY));
+                }
+            }
+        }
+    }
+
+    /**
      * LayoutParams are used by views to tell their parents how they want to be
      * laid out. See
      * {@link android.R.styleable#ViewGroup_Layout ViewGroup Layout Attributes}
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index b456c5d..a0d3618 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -208,4 +208,11 @@
      */
     public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
             boolean immediate);
+
+    /**
+     * Called when a child view's overlay state changes between enabled/disabled.
+     * @param child Child view whose state changed or null.
+     * @hide
+     */
+    public void childOverlayStateChanged(View child);
 }
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 57c9055..acec476 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -222,6 +222,8 @@
 
     private final int mDensity;
     
+    private boolean mHasOverlay;
+
     public static IWindowSession getWindowSession(Looper mainLooper) {
         synchronized (mStaticInit) {
             if (!mInitialized) {
@@ -1518,6 +1520,9 @@
                         canvas.setScreenDensity(scalingRequired
                                 ? DisplayMetrics.DENSITY_DEVICE : 0);
                         mView.draw(canvas);
+                        if (mHasOverlay) {
+                            mView.onDrawOverlay(canvas);
+                        }
                     } finally {
                         mAttachInfo.mIgnoreDirtyState = false;
                         canvas.restoreToCount(saveCount);
@@ -2914,6 +2919,19 @@
         return scrollToRectOrFocus(rectangle, immediate);
     }
 
+    /**
+     * @hide
+     */
+    public void childOverlayStateChanged(View child) {
+        final boolean oldState = mHasOverlay;
+        mHasOverlay = child.isOverlayEnabled();
+        // Invalidate the whole thing when we change overlay states just in case
+        // something left chunks of data drawn someplace it shouldn't have.
+        if (mHasOverlay != oldState) {
+            child.invalidate();
+        }
+    }
+
     class TakenSurfaceHolder extends BaseSurfaceHolder {
         @Override
         public boolean onAllowLockCanvas() {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index e97bbfb..cc8c1fd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -285,6 +285,10 @@
     }
     InputMethodState mInputMethodState;
 
+    private int mTextSelectHandleLeftRes;
+    private int mTextSelectHandleRightRes;
+    private int mTextSelectHandleRes;
+
     /*
      * Kick-start the font cache for the zygote process (to pay the cost of
      * initializing freetype for our default font only once).
@@ -705,6 +709,18 @@
                     Log.w(LOG_TAG, "Failure reading input extras", e);
                 }
                 break;
+
+            case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
+                mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
+                break;
+
+            case com.android.internal.R.styleable.TextView_textSelectHandleRight:
+                mTextSelectHandleRightRes = a.getResourceId(attr, 0);
+                break;
+
+            case com.android.internal.R.styleable.TextView_textSelectHandle:
+                mTextSelectHandleRes = a.getResourceId(attr, 0);
+                break;
             }
         }
         a.recycle();
@@ -3733,6 +3749,8 @@
             showError();
             mShowErrorAfterAttach = false;
         }
+        
+        updateOverlay();
     }
 
     @Override
@@ -3750,6 +3768,8 @@
         if (mError != null) {
             hideError();
         }
+        
+        setOverlayEnabled(false);
     }
 
     @Override
@@ -4100,7 +4120,13 @@
         */
 
         canvas.restore();
+    }
 
+    /**
+     * @hide
+     */
+    @Override
+    public void onDrawOverlay(Canvas canvas) {
         if (mInsertionPointCursorController != null) {
             mInsertionPointCursorController.draw(canvas);
         }
@@ -6679,10 +6705,31 @@
     public boolean onTouchEvent(MotionEvent event) {
         final int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN) {
+            // Check to see if we're testing for our anchor overlay.
+            boolean handled = false;
+            final float x = event.getX();
+            final float y = event.getY();
+            if (x < mLeft || x > mRight || y < mTop || y > mBottom) {
+                if (mInsertionPointCursorController != null) {
+                    handled |= mInsertionPointCursorController.onTouchEvent(event);
+                }
+                if (mSelectionModifierCursorController != null) {
+                    handled |= mSelectionModifierCursorController.onTouchEvent(event);
+                }
+
+                if (!handled) {
+                    return false;
+                }
+            }
+
             // Reset this state; it will be re-set if super.onTouchEvent
             // causes focus to move to the view.
             mTouchFocusSelected = false;
             mScrolled = false;
+
+            if (handled) {
+                return true;
+            }
         }
 
         final boolean superResult = super.onTouchEvent(event);
@@ -7571,6 +7618,17 @@
         }
     }
 
+    private void updateOverlay() {
+        boolean enableOverlay = false;
+        if (mSelectionModifierCursorController != null) {
+            enableOverlay |= mSelectionModifierCursorController.isShowing();
+        }
+        if (mInsertionPointCursorController != null) {
+            enableOverlay |= mInsertionPointCursorController.isShowing();
+        }
+        setOverlayEnabled(enableOverlay);
+    }
+
     /**
      * A CursorController instance can be used to control a cursor in the text.
      *
@@ -7594,6 +7652,11 @@
         public void hide();
 
         /**
+         * @return true if the CursorController is currently visible
+         */
+        public boolean isShowing();
+
+        /**
          * Update the controller's position.
          */
         public void updatePosition(int x, int y);
@@ -7615,7 +7678,7 @@
          * a chance to become active and/or visible.
          * @param event The touch event
          */
-        public void onTouchEvent(MotionEvent event);
+        public boolean onTouchEvent(MotionEvent event);
 
         /**
          * Draws a visual representation of the controller on the canvas.
@@ -7649,10 +7712,10 @@
             final Rect bounds = sCursorControllerTempRect;
             bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0)
                 + mScrollX;
-            bounds.top = (bottom ? lineBottom : lineTop) - drawableHeight / 2 + mScrollY;
+            bounds.top = (bottom ? lineBottom : lineTop) + mScrollY;
 
             mTopExtension = bottom ? 0 : drawableHeight / 2;
-            mBottomExtension = drawableHeight;
+            mBottomExtension = 0; //drawableHeight / 4;
 
             // Extend touch region up when editing the last line of text (or a single line) so that
             // it is easier to grab.
@@ -7710,7 +7773,7 @@
 
         InsertionPointCursorController() {
             Resources res = mContext.getResources();
-            mHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle));
+            mHandle = new Handle(res.getDrawable(mTextSelectHandleRes));
         }
 
         public void show() {
@@ -7718,6 +7781,7 @@
             // Has to be done after updateDrawablePosition, so that previous position invalidate
             // in only done if necessary.
             mIsVisible = true;
+            updateOverlay();
         }
 
         public void hide() {
@@ -7731,6 +7795,10 @@
             }
         }
 
+        public boolean isShowing() {
+            return mIsVisible;
+        }
+
         public void draw(Canvas canvas) {
             if (mIsVisible) {
                 int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart);
@@ -7746,6 +7814,7 @@
                     } else {
                         mHandle.mDrawable.setAlpha(0);
                         mIsVisible = false;
+                        updateOverlay();
                     }
                 }
                 mHandle.mDrawable.draw(canvas);
@@ -7774,6 +7843,7 @@
                 // Should never happen, safety check.
                 Log.w(LOG_TAG, "Update cursor controller position called with no cursor");
                 mIsVisible = false;
+                updateOverlay();
                 return;
             }
 
@@ -7783,7 +7853,7 @@
             mHandle.mDrawable.setAlpha(255);
         }
 
-        public void onTouchEvent(MotionEvent event) {
+        public boolean onTouchEvent(MotionEvent event) {
             if (isFocused() && isTextEditable() && mIsVisible) {
                 switch (event.getActionMasked()) {
                     case MotionEvent.ACTION_DOWN : {
@@ -7811,8 +7881,9 @@
                             mOffsetY += viewportToContentVerticalOffset();
 
                             mOnDownTimerStart = event.getEventTime();
+                            return true;
                         }
-                        break;
+                        return false;
                     }
 
                     case MotionEvent.ACTION_UP : {
@@ -7830,6 +7901,7 @@
                     }
                 }
             }
+            return false;
         }
 
         public float getOffsetX() {
@@ -7859,8 +7931,8 @@
 
         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));
+            mStartHandle = new Handle(res.getDrawable(mTextSelectHandleLeftRes));
+            mEndHandle = new Handle(res.getDrawable(mTextSelectHandleRightRes));
         }
 
         public void show() {
@@ -7868,6 +7940,7 @@
             // Has to be done after updateDrawablePositions, so that previous position invalidate
             // in only done if necessary.
             mIsVisible = true;
+            updateOverlay();
             mFadeOutTimerStart = -1;
             hideInsertionPointCursorController();
         }
@@ -7880,8 +7953,13 @@
             }
         }
 
+        public boolean isShowing() {
+            return mIsVisible;
+        }
+
         public void cancelFadeOutAnimation() {
             mIsVisible = false;
+            updateOverlay();
             mStartHandle.postInvalidate();
             mEndHandle.postInvalidate();
         }
@@ -7900,6 +7978,7 @@
                         mStartHandle.mDrawable.setAlpha(0);
                         mEndHandle.mDrawable.setAlpha(0);
                         mIsVisible = false;
+                        updateOverlay();
                     }
                 }
                 mStartHandle.mDrawable.draw(canvas);
@@ -7957,6 +8036,7 @@
                 // Should never happen, safety check.
                 Log.w(LOG_TAG, "Update selection controller position called with no cursor");
                 mIsVisible = false;
+                updateOverlay();
                 return;
             }
 
@@ -7969,7 +8049,7 @@
             mEndHandle.mDrawable.setAlpha(255);
         }
 
-        public void onTouchEvent(MotionEvent event) {
+        public boolean onTouchEvent(MotionEvent event) {
             if (isTextEditable()) {
                 switch (event.getActionMasked()) {
                     case MotionEvent.ACTION_DOWN:
@@ -8004,6 +8084,7 @@
 
                                     mOnDownTimerStart = event.getEventTime();
                                     ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
+                                    return true;
                                 }
                             }
                         }
@@ -8029,6 +8110,7 @@
                         break;
                 }
             }
+            return false;
         }
 
         /**
diff --git a/core/res/res/drawable-hdpi/text_select_handle_left.png b/core/res/res/drawable-hdpi/text_select_handle_left.png
new file mode 100644
index 0000000..271a6d0
--- /dev/null
+++ b/core/res/res/drawable-hdpi/text_select_handle_left.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_select_handle_middle.png b/core/res/res/drawable-hdpi/text_select_handle_middle.png
new file mode 100644
index 0000000..5a83c6c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/text_select_handle_middle.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_select_handle_right.png b/core/res/res/drawable-hdpi/text_select_handle_right.png
new file mode 100644
index 0000000..dfdf899
--- /dev/null
+++ b/core/res/res/drawable-hdpi/text_select_handle_right.png
Binary files differ
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 13c3e7e..ac1c0dd 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -451,6 +451,21 @@
         <!-- The preference layout that has the child/tabbed effect. -->
         <attr name="preferenceLayoutChild" format="reference" />
 
+        <!-- ============================ -->
+        <!-- Text selection handle styles -->
+        <!-- ============================ -->
+        <eat-comment />
+
+        <!-- Reference to a drawable that will be used to display a text selection
+             anchor on the left side of a selection region. -->
+        <attr name="textSelectHandleLeft" format="reference" />
+        <!-- Reference to a drawable that will be used to display a text selection
+             anchor on the right side of a selection region. -->
+        <attr name="textSelectHandleRight" format="reference" />
+        <!-- Reference to a drawable that will be used to display a text selection
+             anchor for positioning the cursor within text. -->
+        <attr name="textSelectHandle" format="reference" />
+
     </declare-styleable>
 
     <!-- **************************************************************** -->
@@ -2152,6 +2167,16 @@
              EditorInfo.extras} field when the input
              method is connected. -->
         <attr name="editorExtras" format="reference" />
+
+        <!-- Reference to a drawable that will be used to display a text selection
+             anchor on the left side of a selection region. -->
+        <attr name="textSelectHandleLeft" />
+        <!-- Reference to a drawable that will be used to display a text selection
+             anchor on the right side of a selection region. -->
+        <attr name="textSelectHandleRight" />
+        <!-- Reference to a drawable that will be used to display a text selection
+             anchor for positioning the cursor within text. -->
+        <attr name="textSelectHandle" />
     </declare-styleable>
     <!-- An <code>input-extras</code> is a container for extra data to supply to
          an input method.  Contains
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 28a7cca..2c1e91d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1255,6 +1255,9 @@
   <public type="attr" name="overscrollHeader" id="0x010102c2" />
   <public type="attr" name="overscrollFooter" id="0x010102c3" />
   <public type="attr" name="filterTouchesWhenObscured" id="0x010102c4" />
+  <public type="attr" name="textSelectHandleLeft" id="0x010102c5" />
+  <public type="attr" name="textSelectHandleRight" id="0x010102c6" />
+  <public type="attr" name="textSelectHandle" id="0x010102c7" />
 
   <public-padding type="attr" name="kraken_resource_pad" end="0x01010300" />
   
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 1c60ba0..e93b570 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -382,6 +382,9 @@
 
     <style name="Widget.TextView">
         <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+        <item name="android:textSelectHandleLeft">?android:attr/textSelectHandleLeft</item>
+        <item name="android:textSelectHandleRight">?android:attr/textSelectHandleRight</item>
+        <item name="android:textSelectHandle">?android:attr/textSelectHandle</item>
     </style>
     
     <style name="Widget.TextView.ListSeparator">
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 2311bdd..7d6ca06 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -136,6 +136,11 @@
         <item name="scrollbarTrackHorizontal">@null</item>
         <item name="scrollbarTrackVertical">@null</item>
 
+        <!-- Text selection handle attributes -->
+        <item name="textSelectHandleLeft">@android:drawable/text_select_handle_middle</item>
+        <item name="textSelectHandleRight">@android:drawable/text_select_handle_middle</item>
+        <item name="textSelectHandle">@android:drawable/text_select_handle_middle</item>
+
         <!-- Widget styles -->
         <item name="absListViewStyle">@android:style/Widget.AbsListView</item>
         <item name="autoCompleteTextViewStyle">@android:style/Widget.AutoCompleteTextView</item>