Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d018a0ce authored by Gilles Debunne's avatar Gilles Debunne Committed by Android (Google) Code Review
Browse files

Merge "Text selection without trackball." into gingerbread

parents 70c9ffbc b0d6ba1e
Loading
Loading
Loading
Loading
+125 −0
Original line number Diff line number Diff line
@@ -166341,6 +166341,29 @@
<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>
<field name="mCursorController"
 type="android.widget.TextView.CursorController"
 transient="false"
 volatile="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="protected"
>
</field>
</class>
<class name="BaseKeyListener"
 extends="android.text.method.MetaKeyKeyListener"
@@ -222825,6 +222848,108 @@
>
</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="onTouchEvent"
 return="void"
 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="offset" 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"
+2 −2
Original line number Diff line number Diff line
@@ -417,8 +417,8 @@ public class Selection {
        }
    }

    private static final class START implements NoCopySpan { };
    private static final class END implements NoCopySpan { };
    private static final class START implements NoCopySpan { }
    private static final class END implements NoCopySpan { }
    
    /*
     * Public constants
+86 −264
Original line number Diff line number Diff line
@@ -16,30 +16,38 @@

package android.text.method;

import android.util.Log;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.view.KeyEvent;
import android.graphics.Rect;
import android.text.*;
import android.widget.TextView;
import android.view.View;
import android.view.ViewConfiguration;
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
{
public class ArrowKeyMovementMethod implements MovementMethod {
    /**
     * An optional controller for the cursor.
     * Use {@link #setCursorController(CursorController)} to set this field.
     */
    protected CursorController mCursorController;

    private boolean isCap(Spannable buffer) {
        return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) ||
                (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
    }

    private boolean isAlt(Spannable buffer) {
        return MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1;
    }

    private boolean up(TextView widget, Spannable buffer) {
        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
                        KeyEvent.META_SHIFT_ON) == 1) ||
                      (MetaKeyKeyListener.getMetaState(buffer,
                        MetaKeyKeyListener.META_SELECTING) != 0);
        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
                        KeyEvent.META_ALT_ON) == 1;
        boolean cap = isCap(buffer);
        boolean alt = isAlt(buffer);
        Layout layout = widget.getLayout();

        if (cap) {
@@ -60,12 +68,8 @@ implements MovementMethod
    }

    private boolean down(TextView widget, Spannable buffer) {
        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
                        KeyEvent.META_SHIFT_ON) == 1) ||
                      (MetaKeyKeyListener.getMetaState(buffer,
                        MetaKeyKeyListener.META_SELECTING) != 0);
        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
                        KeyEvent.META_ALT_ON) == 1;
        boolean cap = isCap(buffer);
        boolean alt = isAlt(buffer);
        Layout layout = widget.getLayout();

        if (cap) {
@@ -86,12 +90,8 @@ implements MovementMethod
    }

    private boolean left(TextView widget, Spannable buffer) {
        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
                        KeyEvent.META_SHIFT_ON) == 1) ||
                      (MetaKeyKeyListener.getMetaState(buffer,
                        MetaKeyKeyListener.META_SELECTING) != 0);
        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
                        KeyEvent.META_ALT_ON) == 1;
        boolean cap = isCap(buffer);
        boolean alt = isAlt(buffer);
        Layout layout = widget.getLayout();

        if (cap) {
@@ -110,12 +110,8 @@ implements MovementMethod
    }

    private boolean right(TextView widget, Spannable buffer) {
        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
                        KeyEvent.META_SHIFT_ON) == 1) ||
                      (MetaKeyKeyListener.getMetaState(buffer,
                        MetaKeyKeyListener.META_SELECTING) != 0);
        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
                        KeyEvent.META_ALT_ON) == 1;
        boolean cap = isCap(buffer);
        boolean alt = isAlt(buffer);
        Layout layout = widget.getLayout();

        if (cap) {
@@ -133,35 +129,6 @@ implements MovementMethod
        }
    }

    private int getOffset(int x, int y, TextView widget){
      // Converts the absolute X,Y coordinates to the character offset for the
      // character whose position is closest to the specified
      // horizontal position.
      x -= widget.getTotalPaddingLeft();
      y -= widget.getTotalPaddingTop();

      // Clamp the position to inside of the view.
      if (x < 0) {
          x = 0;
      } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) {
          x = widget.getWidth()-widget.getTotalPaddingRight() - 1;
      }
      if (y < 0) {
          y = 0;
      } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) {
          y = widget.getHeight()-widget.getTotalPaddingBottom() - 1;
      }

      x += widget.getScrollX();
      y += widget.getScrollY();

      Layout layout = widget.getLayout();
      int line = layout.getLineForVertical(y);

      int offset = layout.getOffsetForHorizontal(line, x);
      return offset;
    }

    public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
        if (executeDown(widget, buffer, keyCode)) {
            MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
@@ -193,12 +160,11 @@ implements MovementMethod
            break;

        case KeyEvent.KEYCODE_DPAD_CENTER:
            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
                if (widget.showContextMenu()) {
            if ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) &&
                (widget.showContextMenu())) {
                    handled = true;
            }
        }
        }

        if (handled) {
            MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
@@ -214,8 +180,7 @@ implements MovementMethod

    public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
        int code = event.getKeyCode();
        if (code != KeyEvent.KEYCODE_UNKNOWN
                && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
        if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
            int repeat = event.getRepeatCount();
            boolean handled = false;
            while ((--repeat) > 0) {
@@ -226,13 +191,22 @@ implements MovementMethod
        return false;
    }

    public boolean onTrackballEvent(TextView widget, Spannable text,
            MotionEvent event) {
    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) {
    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);
@@ -243,53 +217,20 @@ implements MovementMethod

        if (widget.isFocused() && !widget.didTouchFocusSelect()) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
              boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
                              KeyEvent.META_SHIFT_ON) == 1) ||
                            (MetaKeyKeyListener.getMetaState(buffer,
                              MetaKeyKeyListener.META_SELECTING) != 0);
              int x = (int) event.getX();
              int y = (int) event.getY();
              int offset = getOffset(x, y, widget);

              boolean cap = isCap(buffer);
              if (cap) {
                  buffer.setSpan(LAST_TAP_DOWN, offset, offset,
                                 Spannable.SPAN_POINT_POINT);
                  int offset = widget.getOffset((int) event.getX(), (int) event.getY());
                  
                  buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);

                  // Disallow intercepting of the touch events, so that
                  // users can scroll and select at the same time.
                  // without this, users would get booted out of select
                  // mode once the view detected it needed to scroll.
                  widget.getParent().requestDisallowInterceptTouchEvent(true);
              } else {
                  OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(),
                      OnePointFiveTapState.class);

                  if (tap.length > 0) {
                      if (event.getEventTime() - tap[0].mWhen <=
                          ViewConfiguration.getDoubleTapTimeout() &&
                          sameWord(buffer, offset, Selection.getSelectionEnd(buffer))) {

                          tap[0].active = true;
                          MetaKeyKeyListener.startSelecting(widget, buffer);
                          widget.getParent().requestDisallowInterceptTouchEvent(true);
                          buffer.setSpan(LAST_TAP_DOWN, offset, offset,
                              Spannable.SPAN_POINT_POINT);
                      }

                      tap[0].mWhen = event.getEventTime();
                  } else {
                      OnePointFiveTapState newtap = new OnePointFiveTapState();
                      newtap.mWhen = event.getEventTime();
                      newtap.active = false;
                      buffer.setSpan(newtap, 0, buffer.length(),
                          Spannable.SPAN_INCLUSIVE_INCLUSIVE);
                  }
              }
            } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
                boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
                                KeyEvent.META_SHIFT_ON) == 1) ||
                              (MetaKeyKeyListener.getMetaState(buffer,
                                MetaKeyKeyListener.META_SELECTING) != 0);
                boolean cap = isCap(buffer);

                if (cap && handled) {
                    // Before selecting, make sure we've moved out of the "slop".
@@ -303,39 +244,9 @@ implements MovementMethod
                    // Update selection as we're moving the selection area.

                    // Get the current touch position
                    int x = (int) event.getX();
                    int y = (int) event.getY();
                    int offset = getOffset(x, y, widget);

                    final OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(),
                            OnePointFiveTapState.class);

                    if (tap.length > 0 && tap[0].active) {
                        // Get the last down touch position (the position at which the
                        // user started the selection)
                        int lastDownOffset = buffer.getSpanStart(LAST_TAP_DOWN);

                        // Compute the selection boundaries
                        int spanstart;
                        int spanend;
                        if (offset >= lastDownOffset) {
                            // Expand from word start of the original tap to new word
                            // end, since we are selecting "forwards"
                            spanstart = findWordStart(buffer, lastDownOffset);
                            spanend = findWordEnd(buffer, offset);
                        } else {
                            // Expand to from new word start to word end of the original
                            // tap since we are selecting "backwards".
                            // The spanend will always need to be associated with the touch
                            // up position, so that refining the selection with the
                            // trackball will work as expected.
                            spanstart = findWordEnd(buffer, lastDownOffset);
                            spanend = findWordStart(buffer, offset);
                        }
                        Selection.setSelection(buffer, spanstart, spanend);
                    } else {
                    int offset = widget.getOffset((int) event.getX(), (int) event.getY());

                    Selection.extendSelection(buffer, offset);
                    }
                    return true;
                }
            } else if (event.getAction() == MotionEvent.ACTION_UP) {
@@ -349,65 +260,12 @@ implements MovementMethod
                    return true;
                }

                int x = (int) event.getX();
                int y = (int) event.getY();
                int off = getOffset(x, y, widget);

                // XXX should do the same adjust for x as we do for the line.

                OnePointFiveTapState[] onepointfivetap = buffer.getSpans(0, buffer.length(),
                    OnePointFiveTapState.class);
                if (onepointfivetap.length > 0 && onepointfivetap[0].active &&
                    Selection.getSelectionStart(buffer) == Selection.getSelectionEnd(buffer)) {
                    // If we've set select mode, because there was a onepointfivetap,
                    // but there was no ensuing swipe gesture, undo the select mode
                    // and remove reference to the last onepointfivetap.
                    MetaKeyKeyListener.stopSelecting(widget, buffer);
                    for (int i=0; i < onepointfivetap.length; i++) {
                        buffer.removeSpan(onepointfivetap[i]);
                    }
                    buffer.removeSpan(LAST_TAP_DOWN);
                }
                boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
                                KeyEvent.META_SHIFT_ON) == 1) ||
                              (MetaKeyKeyListener.getMetaState(buffer,
                                MetaKeyKeyListener.META_SELECTING) != 0);

                DoubleTapState[] tap = buffer.getSpans(0, buffer.length(),
                                                       DoubleTapState.class);
                boolean doubletap = false;

                if (tap.length > 0) {
                    if (event.getEventTime() - tap[0].mWhen <=
                        ViewConfiguration.getDoubleTapTimeout() &&
                        sameWord(buffer, off, Selection.getSelectionEnd(buffer))) {

                        doubletap = true;
                    }

                    tap[0].mWhen = event.getEventTime();
                } else {
                    DoubleTapState newtap = new DoubleTapState();
                    newtap.mWhen = event.getEventTime();
                    buffer.setSpan(newtap, 0, buffer.length(),
                                   Spannable.SPAN_INCLUSIVE_INCLUSIVE);
                }

                if (cap) {
                int offset = widget.getOffset((int) event.getX(), (int) event.getY());
                if (isCap(buffer)) {
                    buffer.removeSpan(LAST_TAP_DOWN);
                    if (onepointfivetap.length > 0 && onepointfivetap[0].active) {
                        // If we selecting something with the onepointfivetap-and
                        // swipe gesture, stop it on finger up.
                        MetaKeyKeyListener.stopSelecting(widget, buffer);
                    } else {
                        Selection.extendSelection(buffer, off);
                    }
                } else if (doubletap) {
                    Selection.setSelection(buffer,
                                           findWordStart(buffer, off),
                                           findWordEnd(buffer, off));
                    Selection.extendSelection(buffer, offset);
                } else {
                    Selection.setSelection(buffer, off);
                    Selection.setSelection(buffer, offset);
                }

                MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
@@ -420,73 +278,36 @@ implements MovementMethod
        return handled;
    }

    private static class DoubleTapState implements NoCopySpan {
        long mWhen;
    }

    /* We check for a onepointfive tap. This is similar to
    *  doubletap gesture (where a finger goes down, up, down, up, in a short
    *  time period), except in the onepointfive tap, a users finger only needs
    *  to go down, up, down in a short time period. We detect this type of tap
    *  to implement the onepointfivetap-and-swipe selection gesture.
    *  This gesture allows users to select a segment of text without going
    *  through the "select text" option in the context menu.
    */
    private static class OnePointFiveTapState implements NoCopySpan {
        long mWhen;
        boolean active;
    }

    private static boolean sameWord(CharSequence text, int one, int two) {
        int start = findWordStart(text, one);
        int end = findWordEnd(text, one);

        if (end == start) {
            return false;
        }

        return start == findWordStart(text, two) &&
               end == findWordEnd(text, two);
    }

    // TODO: Unify with TextView.getWordForDictionary()
    private static int findWordStart(CharSequence text, int start) {
        for (; start > 0; start--) {
            char c = text.charAt(start - 1);
            int type = Character.getType(c);
    private boolean onTouchEventCursor(TextView widget, Spannable buffer, MotionEvent event) {
        if (widget.isFocused() && !widget.didTouchFocusSelect()) {
            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_MOVE:
                    widget.cancelLongPress();

            if (c != '\'' &&
                type != Character.UPPERCASE_LETTER &&
                type != Character.LOWERCASE_LETTER &&
                type != Character.TITLECASE_LETTER &&
                type != Character.MODIFIER_LETTER &&
                type != Character.DECIMAL_DIGIT_NUMBER) {
                break;
            }
        }
                    // Offset the current touch position (from controller to cursor)
                    final float x = event.getX() + mCursorController.getOffsetX();
                    final float y = event.getY() + mCursorController.getOffsetY();
                    int offset = widget.getOffset((int) x, (int) y);
                    mCursorController.updatePosition(offset);
                    return true;

        return start;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    mCursorController = null;
                    return true;
            }

    // TODO: Unify with TextView.getWordForDictionary()
    private static int findWordEnd(CharSequence text, int end) {
        int len = text.length();

        for (; end < len; end++) {
            char c = text.charAt(end);
            int type = Character.getType(c);

            if (c != '\'' &&
                type != Character.UPPERCASE_LETTER &&
                type != Character.LOWERCASE_LETTER &&
                type != Character.TITLECASE_LETTER &&
                type != Character.MODIFIER_LETTER &&
                type != Character.DECIMAL_DIGIT_NUMBER) {
                break;
        }
        return false;
    }

        return end;
    /**
     * Defines the cursor controller.
     *
     * When set, this object can be used to handle events, that can be translated in cursor updates.
     * @param cursorController A cursor controller implementation
     */
    public void setCursorController(CursorController cursorController) {
        mCursorController = cursorController;
    }

    public boolean canSelectArbitrarily() {
@@ -525,8 +346,9 @@ implements MovementMethod
    }

    public static MovementMethod getInstance() {
        if (sInstance == null)
        if (sInstance == null) {
            sInstance = new ArrowKeyMovementMethod();
        }

        return sInstance;
    }
+3 −4
Original line number Diff line number Diff line
@@ -17,14 +17,13 @@
package android.text.method;

import android.text.Layout;
import android.text.NoCopySpan;
import android.text.Layout.Alignment;
import android.text.NoCopySpan;
import android.text.Spannable;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.TextView;
import android.view.KeyEvent;

public class Touch {
    private Touch() { }
@@ -99,7 +98,7 @@ public class Touch {
                                       MotionEvent event) {
        DragState[] ds;

        switch (event.getAction()) {
        switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            ds = buffer.getSpans(0, buffer.length(), DragState.class);

+2 −2
Original line number Diff line number Diff line
@@ -54,7 +54,7 @@ public abstract class AbsSavedState implements Parcelable {
     */
    protected AbsSavedState(Parcel source) {
        // FIXME need class loader
        Parcelable superState = (Parcelable) source.readParcelable(null);
        Parcelable superState = source.readParcelable(null);
         
        mSuperState = superState != null ? superState : EMPTY_STATE;
    }
@@ -75,7 +75,7 @@ public abstract class AbsSavedState implements Parcelable {
        = new Parcelable.Creator<AbsSavedState>() {
        
        public AbsSavedState createFromParcel(Parcel in) {
            Parcelable superState = (Parcelable) in.readParcelable(null);
            Parcelable superState = in.readParcelable(null);
            if (superState != null) {
                throw new IllegalStateException("superState must be null");
            }
Loading