Text selection anchors changed to use windows
Manually cherry-picked
Change-Id: Id080dfad0e2f324fef3a5175abc78f76c8bad4c8
diff --git a/api/current.xml b/api/current.xml
index 5ff91c0b..6ec46a6 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -6268,17 +6268,6 @@
visibility="public"
>
</field>
-<field name="kraken_resource_pad57"
- type="int"
- transient="false"
- volatile="false"
- value="16843464"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="kraken_resource_pad6"
type="int"
transient="false"
@@ -10129,6 +10118,17 @@
visibility="public"
>
</field>
+<field name="textSelectHandleWindowStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843464"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="textSize"
type="int"
transient="false"
@@ -178473,19 +178473,6 @@
<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>
</class>
<class name="BaseKeyListener"
extends="android.text.method.MetaKeyKeyListener"
@@ -239255,121 +239242,6 @@
>
</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="isShowing"
- return="boolean"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="onTouchEvent"
- return="boolean"
- 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="x" type="int">
-</parameter>
-<parameter name="y" 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/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 0408673..733b535 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -314,6 +314,8 @@
* {@link MotionEvent#ACTION_CANCEL} event), the controller is reset to null.
*
* @param cursorController A cursor controller implementation
+ *
+ * @hide
*/
public void setCursorController(CursorController cursorController) {
mCursorController = cursorController;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 97d58d2..3d5aff7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1613,12 +1613,6 @@
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;
-
- /**
* Indicates that pivotX or pivotY were explicitly set and we should not assume the center
* for transform operations
*
@@ -3040,57 +3034,6 @@
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 3db4857..2b92e87 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -245,11 +245,6 @@
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;
-
- /**
* When set, this ViewGroup will split MotionEvents to multiple child Views when appropriate.
*/
private static final int FLAG_SPLIT_MOTION_EVENTS = 0x200000;
@@ -910,33 +905,6 @@
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
@@ -2774,8 +2742,6 @@
if (clearChildFocus != null) {
clearChildFocus(clearChildFocus);
}
-
- mGroupFlags &= ~FLAG_CHILD_HAS_OVERLAY;
}
/**
@@ -3024,8 +2990,7 @@
final int left = mLeft;
final int top = mTop;
- if ((mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY ||
- dirty.intersect(0, 0, mRight - left, mBottom - top) ||
+ if (dirty.intersect(0, 0, mRight - left, mBottom - top) ||
(mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) {
mPrivateFlags &= ~DRAWING_CACHE_VALID;
@@ -4013,69 +3978,6 @@
}
/**
- * 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 d2907da..d7d4c3f 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -222,11 +222,4 @@
*/
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 cb9f84d..e71b5b6 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -206,8 +206,6 @@
AudioManager mAudioManager;
private final int mDensity;
-
- private boolean mHasOverlay;
public static IWindowSession getWindowSession(Looper mainLooper) {
synchronized (mStaticInit) {
@@ -1359,9 +1357,6 @@
canvas.setScreenDensity(scalingRequired
? DisplayMetrics.DENSITY_DEVICE : 0);
mView.draw(canvas);
- if (mHasOverlay) {
- mView.onDrawOverlay(canvas);
- }
} finally {
mAttachInfo.mIgnoreDirtyState = false;
}
@@ -2744,19 +2739,6 @@
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/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 4a3e2a9..71f4f03 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -176,6 +176,7 @@
attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes);
mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
+ mAnimationStyle = a.getResourceId(R.styleable.PopupWindow_windowAnimationStyle, -1);
// If this is a StateListDrawable, try to find and store the drawable to be
// used when the drop-down is placed above its anchor view, and the one to be
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3e5ddfc..948a8b6 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -199,6 +199,9 @@
private static final int PRIORITY = 100;
private int mCurrentAlpha = 255;
+
+ private final int[] mTempCoords = new int[2];
+
private ColorStateList mTextColor;
private int mCurTextColor;
private ColorStateList mHintTextColor;
@@ -3779,8 +3782,6 @@
showError();
mShowErrorAfterAttach = false;
}
-
- updateOverlay();
}
@Override
@@ -3798,8 +3799,6 @@
if (mError != null) {
hideError();
}
-
- setOverlayEnabled(false);
}
@Override
@@ -4169,19 +4168,6 @@
canvas.restore();
}
- /**
- * @hide
- */
- @Override
- public void onDrawOverlay(Canvas canvas) {
- if (mInsertionPointCursorController != null) {
- mInsertionPointCursorController.draw(canvas);
- }
- if (mSelectionModifierCursorController != null) {
- mSelectionModifierCursorController.draw(canvas);
- }
- }
-
@Override
public void getFocusedRect(Rect r) {
if (mLayout == null) {
@@ -6768,31 +6754,17 @@
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 < 0 || x >= mRight - mLeft || y < 0 || y >= mBottom - mTop) {
- if (mInsertionPointCursorController != null) {
- handled |= mInsertionPointCursorController.onTouchEvent(event);
- }
- if (mSelectionModifierCursorController != null) {
- handled |= mSelectionModifierCursorController.onTouchEvent(event);
- }
-
- if (!handled) {
- return false;
- }
+ if (mInsertionPointCursorController != null) {
+ mInsertionPointCursorController.onTouchEvent(event);
+ }
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.onTouchEvent(event);
}
// 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);
@@ -6808,7 +6780,6 @@
}
if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
-
if (mInsertionPointCursorController != null) {
mInsertionPointCursorController.onTouchEvent(event);
}
@@ -6822,11 +6793,22 @@
int oldSelStart = getSelectionStart();
int oldSelEnd = getSelectionEnd();
+ final int oldScrollX = mScrollX;
+ final int oldScrollY = mScrollY;
+
if (mMovement != null) {
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
if (isTextEditable()) {
+ if (mScrollX != oldScrollX || mScrollY != oldScrollY) {
+ // Hide insertion anchor while scrolling. Leave selection.
+ hideInsertionPointCursorController();
+ if (mSelectionModifierCursorController != null &&
+ mSelectionModifierCursorController.isShowing()) {
+ mSelectionModifierCursorController.updatePosition();
+ }
+ }
if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -7705,22 +7687,13 @@
}
}
- 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.
*
* It can be passed to an {@link ArrowKeyMovementMethod} which can intercepts events
* and send them to this object instead of the cursor.
+ *
+ * @hide
*/
public interface CursorController {
/* Cursor fade-out animation duration, in milliseconds. */
@@ -7748,6 +7721,8 @@
*/
public void updatePosition(int x, int y);
+ public void updatePosition();
+
/**
* 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
@@ -7766,154 +7741,192 @@
* @param event The touch event
*/
public boolean 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 mHotSpotVerticalPosition;
+ private class HandleView extends View {
+ private boolean mPositionOnTop = false;
+ private Drawable mDrawable;
+ private PopupWindow mContainer;
+ private int mPositionX;
+ private int mPositionY;
+ private CursorController mController;
+ private boolean mIsDragging;
- Handle(Drawable drawable) {
- mDrawable = drawable;
+ public HandleView(CursorController controller, Drawable handle) {
+ super(TextView.this.mContext);
+ mController = controller;
+ mDrawable = handle;
+ mContainer = new PopupWindow(TextView.this.mContext, null,
+ com.android.internal.R.attr.textSelectHandleWindowStyle);
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(mDrawable.getIntrinsicWidth(),
+ mDrawable.getIntrinsicHeight());
+ }
+
+ public void show() {
+ if (!isPositionInBounds()) {
+ hide();
+ return;
+ }
+ mContainer.setContentView(this);
+ final int[] coords = mTempCoords;
+ TextView.this.getLocationOnScreen(coords);
+ coords[0] += mPositionX;
+ coords[1] += mPositionY;
+ mContainer.showAtLocation(TextView.this, 0, coords[0], coords[1]);
+ }
+
+ public void hide() {
+ mIsDragging = false;
+ mContainer.dismiss();
+ }
+
+ public boolean isShowing() {
+ return mContainer.isShowing();
+ }
+
+ private boolean isPositionInBounds() {
+ final int extendedPaddingTop = getExtendedPaddingTop();
+ final int extendedPaddingBottom = getExtendedPaddingBottom();
+ final int compoundPaddingLeft = getCompoundPaddingLeft();
+ final int compoundPaddingRight = getCompoundPaddingRight();
+
+ final TextView hostView = TextView.this;
+ final int right = hostView.mRight;
+ final int left = hostView.mLeft;
+ final int bottom = hostView.mBottom;
+ final int top = hostView.mTop;
+
+ final int clipLeft = left + compoundPaddingLeft;
+ final int clipTop = top + extendedPaddingTop;
+ final int clipRight = right - compoundPaddingRight;
+ final int clipBottom = bottom - extendedPaddingBottom;
+
+ final int handleWidth = mDrawable.getIntrinsicWidth();
+ return mPositionX >= clipLeft - handleWidth * 0.75f &&
+ mPositionX <= clipRight + handleWidth * 0.25f &&
+ mPositionY >= clipTop && mPositionY <= clipBottom;
+ }
+
+ private void moveTo(int x, int y) {
+ mPositionX = x - TextView.this.mScrollX;
+ mPositionY = y - TextView.this.mScrollY;
+ if (isPositionInBounds()) {
+ if (mContainer.isShowing()){
+ final int[] coords = mTempCoords;
+ TextView.this.getLocationOnScreen(coords);
+ coords[0] += mPositionX;
+ coords[1] += mPositionY;
+ mContainer.update(coords[0], coords[1], mRight - mLeft, mBottom - mTop);
+ } else {
+ show();
+ }
+ } else {
+ hide();
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
+ if (mPositionOnTop) {
+ c.save();
+ c.rotate(180, (mRight - mLeft) / 2, (mBottom - mTop) / 2);
+ mDrawable.draw(c);
+ c.restore();
+ } else {
+ mDrawable.draw(c);
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mIsDragging = true;
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ final float rawX = ev.getRawX();
+ final float rawY = ev.getRawY();
+ final int[] coords = mTempCoords;
+ TextView.this.getLocationOnScreen(coords);
+ final int x = (int) (rawX - coords[0] + 0.5f);
+ final int y = (int) (rawY - coords[1] + 0.5f);
+ mController.updatePosition(x, y);
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mIsDragging = false;
+ }
+ return true;
+ }
+
+ public boolean isDragging() {
+ return mIsDragging;
}
void positionAtCursor(final int offset, boolean bottom) {
- final int drawableWidth = mDrawable.getIntrinsicWidth();
- final int drawableHeight = mDrawable.getIntrinsicHeight();
+ final int width = mDrawable.getIntrinsicWidth();
+ final int height = mDrawable.getIntrinsicHeight();
final int line = mLayout.getLineForOffset(offset);
final int lineTop = mLayout.getLineTop(line);
final int lineBottom = mLayout.getLineBottom(line);
- mHotSpotVerticalPosition = lineTop;
-
final Rect bounds = sCursorControllerTempRect;
- bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0)
- + mScrollX;
- bounds.top = (bottom ? lineBottom : lineTop) + mScrollY;
+ bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - width / 2.0)
+ + TextView.this.mScrollX;
+ bounds.top = (bottom ? lineBottom : lineTop) + TextView.this.mScrollY;
- mTopExtension = bottom ? 0 : drawableHeight / 2;
- 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.
- if (line == mLayout.getLineCount() - 1) {
- mTopExtension = (lineBottom - lineTop) - drawableHeight / 2;
- }
-
- bounds.right = bounds.left + drawableWidth;
- bounds.bottom = bounds.top + drawableHeight;
+ bounds.right = bounds.left + width;
+ bounds.bottom = bounds.top + height;
convertFromViewportToContentCoordinates(bounds);
- invalidate();
- mDrawable.setBounds(bounds);
- invalidate();
- }
-
- 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));
- fingerRect.offset(mScrollX, mScrollY);
- return Rect.intersects(mDrawable.getBounds(), fingerRect);
- }
-
- void invalidate() {
- final Rect bounds = mDrawable.getBounds();
- TextView.this.invalidate(bounds.left, bounds.top,
- bounds.right, bounds.bottom);
- }
-
- 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);
+ moveTo(bounds.left, bounds.top);
}
}
class InsertionPointCursorController implements CursorController {
- private static final int DELAY_BEFORE_FADE_OUT = 2100;
+ private static final int DELAY_BEFORE_FADE_OUT = 4100;
- // 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;
+ private final HandleView mHandle;
// Offset between finger hot point on cursor controller and actual cursor
private float mOffsetX, mOffsetY;
+ private final Runnable mHider = new Runnable() {
+ public void run() {
+ hide();
+ }
+ };
+
InsertionPointCursorController() {
Resources res = mContext.getResources();
- mHandle = new Handle(res.getDrawable(mTextSelectHandleRes));
+ mHandle = new HandleView(this, res.getDrawable(mTextSelectHandleRes));
}
public void show() {
- updateDrawablePosition();
- // Has to be done after updateDrawablePosition, so that previous position invalidate
- // in only done if necessary.
- mIsVisible = true;
- updateOverlay();
+ updatePosition();
+ mHandle.show();
+ hideDelayed(DELAY_BEFORE_FADE_OUT);
}
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();
- }
- }
+ mHandle.hide();
+ TextView.this.removeCallbacks(mHider);
+ }
+
+ private void hideDelayed(int msec) {
+ TextView.this.removeCallbacks(mHider);
+ TextView.this.postDelayed(mHider, msec);
}
public boolean isShowing() {
- return mIsVisible;
- }
-
- 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;
- updateOverlay();
- }
- }
- mHandle.mDrawable.draw(canvas);
- }
+ return mHandle.isShowing();
}
public void updatePosition(int x, int y) {
@@ -7922,81 +7935,22 @@
if (offset != previousOffset) {
Selection.setSelection((Spannable) mText, offset);
- updateDrawablePosition();
+ updatePosition();
}
+ hideDelayed(DELAY_BEFORE_FADE_OUT);
}
- private void updateDrawablePosition() {
- if (mIsVisible) {
- // Clear previous cursor controller before bounds are updated
- mHandle.postInvalidate();
- }
-
+ public void updatePosition() {
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;
- updateOverlay();
+ hide();
return;
}
mHandle.positionAtCursor(offset, true);
-
- mFadeOutTimerStart = System.currentTimeMillis();
- mHandle.mDrawable.setAlpha(255);
- }
-
- public boolean 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.mHotSpotVerticalPosition - y;
-
- mOffsetX += viewportToContentHorizontalOffset();
- mOffsetY += viewportToContentVerticalOffset();
-
- mOnDownTimerStart = event.getEventTime();
- return true;
- }
- return false;
- }
-
- 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;
- }
- }
- }
- return false;
}
public float getOffsetX() {
@@ -8006,88 +7960,59 @@
public float getOffsetY() {
return mOffsetY;
}
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ return false;
+ }
}
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;
+ private HandleView 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;
+ // Whether selection anchors are active
+ private boolean mIsShowing;
SelectionModifierCursorController() {
Resources res = mContext.getResources();
- mStartHandle = new Handle(res.getDrawable(mTextSelectHandleLeftRes));
- mEndHandle = new Handle(res.getDrawable(mTextSelectHandleRightRes));
+ mStartHandle = new HandleView(this, res.getDrawable(mTextSelectHandleLeftRes));
+ mEndHandle = new HandleView(this, res.getDrawable(mTextSelectHandleRightRes));
}
public void show() {
- updateDrawablesPositions();
- // Has to be done after updateDrawablePositions, so that previous position invalidate
- // in only done if necessary.
- mIsVisible = true;
- updateOverlay();
- mFadeOutTimerStart = -1;
+ mIsShowing = true;
+ updatePosition();
+ mStartHandle.show();
+ mEndHandle.show();
hideInsertionPointCursorController();
}
public void hide() {
- if (mIsVisible && (mFadeOutTimerStart < 0)) {
- mFadeOutTimerStart = System.currentTimeMillis();
- mStartHandle.postInvalidate();
- mEndHandle.postInvalidate();
- }
+ mStartHandle.hide();
+ mEndHandle.hide();
+ mIsShowing = false;
}
public boolean isShowing() {
- return mIsVisible;
+ return mIsShowing;
}
public void cancelFadeOutAnimation() {
- mIsVisible = false;
- updateOverlay();
- 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;
- updateOverlay();
- }
- }
- mStartHandle.mDrawable.draw(canvas);
- mEndHandle.mDrawable.draw(canvas);
- }
+ hide();
}
public void updatePosition(int x, int y) {
int selectionStart = getSelectionStart();
int selectionEnd = getSelectionEnd();
- final int previousOffset = mStartIsDragged ? selectionStart : selectionEnd;
+ final int previousOffset = mStartHandle.isDragging() ? selectionStart : selectionEnd;
int offset = getHysteresisOffset(x, y, previousOffset);
// Handle the case where start and end are swapped, making sure start <= end
- if (mStartIsDragged) {
+ if (mStartHandle.isDragging()) {
if (offset <= selectionEnd) {
if (selectionStart == offset) {
return; // no change, no need to redraw;
@@ -8096,7 +8021,9 @@
} else {
selectionStart = selectionEnd;
selectionEnd = offset;
- mStartIsDragged = false;
+ HandleView temp = mStartHandle;
+ mStartHandle = mEndHandle;
+ mEndHandle = temp;
}
} else {
if (offset >= selectionStart) {
@@ -8107,43 +8034,35 @@
} else {
selectionEnd = selectionStart;
selectionStart = offset;
- mStartIsDragged = true;
+ HandleView temp = mStartHandle;
+ mStartHandle = mEndHandle;
+ mEndHandle = temp;
}
}
Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
- updateDrawablesPositions();
+ updatePosition();
}
- private void updateDrawablesPositions() {
- if (mIsVisible) {
- // Clear previous cursor controller before bounds are updated
- mStartHandle.postInvalidate();
- mEndHandle.postInvalidate();
- }
-
+ public void updatePosition() {
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;
- updateOverlay();
+ hide();
return;
}
- boolean oneLineSelection =
- mLayout.getLineForOffset(selectionStart) == mLayout.getLineForOffset(selectionEnd);
+ 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 boolean onTouchEvent(MotionEvent event) {
- if (isTextEditable()) {
+ if (isFocused() && isTextEditable()) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
final int x = (int) event.getX();
@@ -8152,34 +8071,6 @@
// Remember finger down position, to be able to start selection from there
mMinTouchOffset = mMaxTouchOffset = mLastTouchOffset = 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.mHotSpotVerticalPosition - y;
-
- mOffsetX += viewportToContentHorizontalOffset();
- mOffsetY += viewportToContentVerticalOffset();
-
- ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
- return true;
- }
- }
- }
break;
case MotionEvent.ACTION_POINTER_DOWN:
@@ -8230,7 +8121,7 @@
* @return true iff this controller is currently used to move the selection start.
*/
public boolean isSelectionStartDragged() {
- return mIsVisible && mStartIsDragged;
+ return mStartHandle.isDragging();
}
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 779a33f..19b76ab 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -466,6 +466,9 @@
<attr name="quickContactBadgeStyleSmallWindowMedium" format="reference" />
<!-- Default quickcontact badge style with large quickcontact window. -->
<attr name="quickContactBadgeStyleSmallWindowLarge" format="reference" />
+ <!-- Reference to a style that will be used for the window containing a text
+ selection anchor. -->
+ <attr name="textSelectHandleWindowStyle" format="reference" />
<!-- Default ListPopupWindow style. -->
<attr name="listPopupWindowStyle" format="reference" />
<!-- Default PopupMenu style. -->
@@ -2355,6 +2358,7 @@
</declare-styleable>
<declare-styleable name="PopupWindow">
<attr name="popupBackground" format="reference|color" />
+ <attr name="windowAnimationStyle" />
</declare-styleable>
<declare-styleable name="ViewAnimator">
<!-- Identifier for the animation to use when a view is shown. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6f53677..d892d54 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1255,6 +1255,7 @@
<public type="attr" name="textSelectHandleLeft" id="0x010102c5" />
<public type="attr" name="textSelectHandleRight" id="0x010102c6" />
<public type="attr" name="textSelectHandle" id="0x010102c7" />
+ <public type="attr" name="textSelectHandleWindowStyle" id="0x010102c8" />
<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 e8343f1..c37bb66 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -881,6 +881,17 @@
<item name="android:background">@android:drawable/bottom_bar</item>
</style>
+ <!-- Style for the small popup windows that contain text selection anchors. -->
+ <style name="Widget.TextSelectHandle">
+ <item name="android:windowAnimationStyle">@android:style/Animation.TextSelectHandle</item>
+ </style>
+
+ <!-- Style for animating text selection handles. -->
+ <style name="Animation.TextSelectHandle">
+ <item name="windowEnterAnimation">@android:anim/fade_in</item>
+ <item name="windowExitAnimation">@android:anim/fade_out</item>
+ </style>
+
<style name="Widget.ActionBar">
<item name="android:background">@android:drawable/action_bar_background</item>
<item name="android:displayOptions">useLogo</item>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index fd39179..9425fc4 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -156,6 +156,7 @@
<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>
+ <item name="textSelectHandleWindowStyle">@android:style/Widget.TextSelectHandle</item>
<!-- Widget styles -->
<item name="absListViewStyle">@android:style/Widget.AbsListView</item>