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

Commit a842d143 authored by Gilles Debunne's avatar Gilles Debunne Committed by Android Git Automerger
Browse files

am d018a0ce: Merge "Text selection without trackball." into gingerbread

Merge commit 'd018a0ce' into gingerbread-plus-aosp

* commit 'd018a0ce':
  Text selection without trackball.
parents bb8d314b d018a0ce
Loading
Loading
Loading
Loading
+125 −0
Original line number Diff line number Diff line
@@ -166353,6 +166353,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"
@@ -222863,6 +222886,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