Add suuport for splitting touch events across windows.
This feature is currently used to enable dragging the start and end
selection handles of a TextView at the same time. Could be used for
other things later.
Deleted some dead code in ArrowKeyMovementMethod and CursorControllers.
Change-Id: I930accd97ca1ca1917aab8a807db2c950fc7b409
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 733b535..220e023 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -23,19 +23,12 @@
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 {
- /**
- * An optional controller for the cursor.
- * Use {@link #setCursorController(CursorController)} to set this field.
- */
- private CursorController mCursorController;
-
private boolean isCap(Spannable buffer) {
return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) ||
(MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
@@ -192,21 +185,10 @@
}
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) {
- 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);
@@ -278,49 +260,6 @@
return handled;
}
- private boolean onTouchEventCursor(TextView widget, Spannable buffer, MotionEvent event) {
- if (widget.isFocused() && !widget.didTouchFocusSelect()) {
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_MOVE:
- widget.cancelLongPress();
-
- // Offset the current touch position (from controller to cursor)
- final float x = event.getX() + mCursorController.getOffsetX();
- final float y = event.getY() + mCursorController.getOffsetY();
- mCursorController.updatePosition((int) x, (int) y);
- return true;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- mCursorController = null;
- return true;
- }
- }
- return false;
- }
-
- /**
- * Defines the cursor controller.
- *
- * When set, this object can be used to handle touch events, that can be translated into cursor
- * updates.
- *
- * {@link MotionEvent#ACTION_MOVE} events will call back the
- * {@link CursorController#updatePosition(int, int)} controller's method, passing the current
- * finger coordinates (offset by {@link CursorController#getOffsetX()} and
- * {@link CursorController#getOffsetY()}) as parameters.
- *
- * When the gesture is finished (on a {@link MotionEvent#ACTION_UP} or
- * {@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;
- }
-
public boolean canSelectArbitrarily() {
return true;
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index eebbc93..9c4aefe 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -583,6 +583,19 @@
* also been set.
*/
public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
+
+ /** Window flag: when set the window will accept for touch events
+ * outside of its bounds to be sent to other windows that also
+ * support split touch. When this flag is not set, the first pointer
+ * that goes down determines the window to which all subsequent touches
+ * go until all pointers go up. When this flag is set, each pointer
+ * (not necessarily the first) that goes down determines the window
+ * to which all subsequent touches of that pointer will go until that
+ * pointer goes up thereby enabling touches with multiple pointers
+ * to be split across multiple windows.
+ *
+ * {@hide} */
+ public static final int FLAG_SPLIT_TOUCH = 0x00800000;
/** Window flag: *sigh* The lock screen wants to continue running its
* animation while it is fading. A kind-of hack to allow this. Maybe
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index a10d647..7fe6190 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -85,6 +85,7 @@
private boolean mTouchable = true;
private boolean mOutsideTouchable = false;
private boolean mClippingEnabled = true;
+ private boolean mSplitTouchEnabled;
private OnTouchListener mTouchInterceptor;
@@ -565,6 +566,36 @@
}
/**
+ * <p>Indicates whether the popup window supports splitting touches.</p>
+ *
+ * @return true if the touch splitting is enabled, false otherwise
+ *
+ * @see #setSplitTouchEnabled(boolean)
+ * @hide
+ */
+ public boolean isSplitTouchEnabled() {
+ return mSplitTouchEnabled;
+ }
+
+ /**
+ * <p>Allows the popup window to split touches across other windows that also
+ * support split touch. When this flag is not set, the first pointer
+ * that goes down determines the window to which all subsequent touches
+ * go until all pointers go up. When this flag is set, each pointer
+ * (not necessarily the first) that goes down determines the window
+ * to which all subsequent touches of that pointer will go until that
+ * pointer goes up thereby enabling touches with multiple pointers
+ * to be split across multiple windows.</p>
+ *
+ * @param enabled true if the split touches should be enabled, false otherwise
+ * @see #isSplitTouchEnabled()
+ * @hide
+ */
+ public void setSplitTouchEnabled(boolean enabled) {
+ mSplitTouchEnabled = enabled;
+ }
+
+ /**
* <p>Change the width and height measure specs that are given to the
* window manager by the popup. By default these are 0, meaning that
* the current width or height is requested as an explicit size from
@@ -889,6 +920,9 @@
if (!mClippingEnabled) {
curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
}
+ if (mSplitTouchEnabled) {
+ curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+ }
return curFlags;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 034c617..6278192 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7601,16 +7601,10 @@
/**
* 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.
- *
+ * It is not used outside of {@link TextView}.
* @hide
*/
- public interface CursorController {
- /* Cursor fade-out animation duration, in milliseconds. */
- static final int FADE_OUT_DURATION = 400;
-
+ private interface CursorController {
/**
* Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
* See also {@link #hide()}.
@@ -7631,23 +7625,11 @@
/**
* Update the controller's position.
*/
- public void updatePosition(int x, int y);
+ public void updatePosition(HandleView handle, 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
- * @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
@@ -7670,6 +7652,7 @@
mDrawable = handle;
mContainer = new PopupWindow(TextView.this.mContext, null,
com.android.internal.R.attr.textSelectHandleWindowStyle);
+ mContainer.setSplitTouchEnabled(true);
}
@Override
@@ -7768,7 +7751,7 @@
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);
+ mController.updatePosition(this, x, y);
break;
case MotionEvent.ACTION_UP:
@@ -7802,13 +7785,11 @@
}
}
- class InsertionPointCursorController implements CursorController {
+ private class InsertionPointCursorController implements CursorController {
private static final int DELAY_BEFORE_FADE_OUT = 4100;
// The cursor controller image
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() {
@@ -7841,7 +7822,7 @@
return mHandle.isShowing();
}
- public void updatePosition(int x, int y) {
+ public void updatePosition(HandleView handle, int x, int y) {
final int previousOffset = getSelectionStart();
int offset = getHysteresisOffset(x, y, previousOffset);
@@ -7865,24 +7846,14 @@
mHandle.positionAtCursor(offset, true);
}
- public float getOffsetX() {
- return mOffsetX;
- }
-
- public float getOffsetY() {
- return mOffsetY;
- }
-
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
}
- class SelectionModifierCursorController implements CursorController {
+ private class SelectionModifierCursorController implements CursorController {
// The cursor controller images
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
@@ -7916,15 +7887,15 @@
hide();
}
- public void updatePosition(int x, int y) {
+ public void updatePosition(HandleView handle, int x, int y) {
int selectionStart = getSelectionStart();
int selectionEnd = getSelectionEnd();
- final int previousOffset = mStartHandle.isDragging() ? selectionStart : selectionEnd;
+ final int previousOffset = handle == mStartHandle ? selectionStart : selectionEnd;
int offset = getHysteresisOffset(x, y, previousOffset);
// Handle the case where start and end are swapped, making sure start <= end
- if (mStartHandle.isDragging()) {
+ if (handle == mStartHandle) {
if (offset <= selectionEnd) {
if (selectionStart == offset) {
return; // no change, no need to redraw;
@@ -8021,14 +7992,6 @@
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.
*/