am b08013c3: Added overlay support for drawing/responding to text anchors.
Merge commit 'b08013c312e3d849029a2f4c11889274c00f438d' into gingerbread-plus-aosp
* commit 'b08013c312e3d849029a2f4c11889274c00f438d':
Added overlay support for drawing/responding to text anchors.
diff --git a/api/current.xml b/api/current.xml
index 9fd12da..d98365a 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"
@@ -225620,8 +225620,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 219bdf9..199343a 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);
}
@@ -6684,10 +6710,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);
@@ -7576,6 +7623,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.
*
@@ -7599,6 +7657,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);
@@ -7620,7 +7683,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.
@@ -7654,10 +7717,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.
@@ -7715,7 +7778,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() {
@@ -7723,6 +7786,7 @@
// Has to be done after updateDrawablePosition, so that previous position invalidate
// in only done if necessary.
mIsVisible = true;
+ updateOverlay();
}
public void hide() {
@@ -7736,6 +7800,10 @@
}
}
+ public boolean isShowing() {
+ return mIsVisible;
+ }
+
public void draw(Canvas canvas) {
if (mIsVisible) {
int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart);
@@ -7751,6 +7819,7 @@
} else {
mHandle.mDrawable.setAlpha(0);
mIsVisible = false;
+ updateOverlay();
}
}
mHandle.mDrawable.draw(canvas);
@@ -7779,6 +7848,7 @@
// Should never happen, safety check.
Log.w(LOG_TAG, "Update cursor controller position called with no cursor");
mIsVisible = false;
+ updateOverlay();
return;
}
@@ -7788,7 +7858,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 : {
@@ -7816,8 +7886,9 @@
mOffsetY += viewportToContentVerticalOffset();
mOnDownTimerStart = event.getEventTime();
+ return true;
}
- break;
+ return false;
}
case MotionEvent.ACTION_UP : {
@@ -7835,6 +7906,7 @@
}
}
}
+ return false;
}
public float getOffsetX() {
@@ -7864,8 +7936,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() {
@@ -7873,6 +7945,7 @@
// Has to be done after updateDrawablePositions, so that previous position invalidate
// in only done if necessary.
mIsVisible = true;
+ updateOverlay();
mFadeOutTimerStart = -1;
hideInsertionPointCursorController();
}
@@ -7885,8 +7958,13 @@
}
}
+ public boolean isShowing() {
+ return mIsVisible;
+ }
+
public void cancelFadeOutAnimation() {
mIsVisible = false;
+ updateOverlay();
mStartHandle.postInvalidate();
mEndHandle.postInvalidate();
}
@@ -7905,6 +7983,7 @@
mStartHandle.mDrawable.setAlpha(0);
mEndHandle.mDrawable.setAlpha(0);
mIsVisible = false;
+ updateOverlay();
}
}
mStartHandle.mDrawable.draw(canvas);
@@ -7962,6 +8041,7 @@
// Should never happen, safety check.
Log.w(LOG_TAG, "Update selection controller position called with no cursor");
mIsVisible = false;
+ updateOverlay();
return;
}
@@ -7974,7 +8054,7 @@
mEndHandle.mDrawable.setAlpha(255);
}
- public void onTouchEvent(MotionEvent event) {
+ public boolean onTouchEvent(MotionEvent event) {
if (isTextEditable()) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
@@ -8009,6 +8089,7 @@
mOnDownTimerStart = event.getEventTime();
((ArrowKeyMovementMethod)mMovement).setCursorController(this);
+ return true;
}
}
}
@@ -8034,6 +8115,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>