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.
          */