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

Commit 0b01e7fc authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Polish new IME API for L: CursorAnchorInfo

This CL allows application authors and input method authors to
communicate with each other more precisely on the visibility of
insertion marker and composing characters. Now we can describe
the situation where the coordinates of them are available but
they are overlapped by other UI elements.

This change is based on feedbacks from internal customers of
this preview API.

Change-Id: I82eba0e844a6f8b99ba11a68fad272399034cc24
BUG: 16118303
parent 8ecc8e19
Loading
Loading
Loading
Loading
+10 −2
Original line number Diff line number Diff line
@@ -34936,6 +34936,7 @@ package android.view.inputmethod {
    ctor public CursorAnchorInfo(android.os.Parcel);
    method public int describeContents();
    method public android.graphics.RectF getCharacterRect(int);
    method public int getCharacterRectFlags(int);
    method public java.lang.CharSequence getComposingText();
    method public int getComposingTextStart();
    method public float getInsertionMarkerBaseline();
@@ -34945,17 +34946,24 @@ package android.view.inputmethod {
    method public android.graphics.Matrix getMatrix();
    method public int getSelectionEnd();
    method public int getSelectionStart();
    method public boolean isInsertionMarkerClipped();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final int CHARACTER_RECT_TYPE_FULLY_VISIBLE = 1; // 0x1
    field public static final int CHARACTER_RECT_TYPE_INVISIBLE = 3; // 0x3
    field public static final int CHARACTER_RECT_TYPE_MASK = 15; // 0xf
    field public static final int CHARACTER_RECT_TYPE_NOT_FEASIBLE = 4; // 0x4
    field public static final int CHARACTER_RECT_TYPE_PARTIALLY_VISIBLE = 2; // 0x2
    field public static final int CHARACTER_RECT_TYPE_UNSPECIFIED = 0; // 0x0
    field public static final android.os.Parcelable.Creator CREATOR;
  }
  public static final class CursorAnchorInfo.Builder {
    ctor public CursorAnchorInfo.Builder();
    method public android.view.inputmethod.CursorAnchorInfo.Builder addCharacterRect(int, float, float, float, float);
    method public android.view.inputmethod.CursorAnchorInfo.Builder addCharacterRect(int, float, float, float, float, int);
    method public android.view.inputmethod.CursorAnchorInfo build();
    method public void reset();
    method public android.view.inputmethod.CursorAnchorInfo.Builder setComposingText(int, java.lang.CharSequence);
    method public android.view.inputmethod.CursorAnchorInfo.Builder setInsertionMarkerLocation(float, float, float, float);
    method public android.view.inputmethod.CursorAnchorInfo.Builder setInsertionMarkerLocation(float, float, float, float, boolean);
    method public android.view.inputmethod.CursorAnchorInfo.Builder setMatrix(android.graphics.Matrix);
    method public android.view.inputmethod.CursorAnchorInfo.Builder setSelectionRange(int, int);
  }
+78 −7
Original line number Diff line number Diff line
@@ -44,6 +44,10 @@ public final class CursorAnchorInfo implements Parcelable {
     */
    private final CharSequence mComposingText;

    /**
     * {@code True} if the insertion marker is partially or entirely clipped by other UI elements.
     */
    private final boolean mInsertionMarkerClipped;
    /**
     * Horizontal position of the insertion marker, in the local coordinates that will be
     * transformed with the transformation matrix when rendered on the screen. This should be
@@ -86,11 +90,36 @@ public final class CursorAnchorInfo implements Parcelable {
     */
    private final Matrix mMatrix;

    public static final int CHARACTER_RECT_TYPE_MASK = 0x0f;
    /**
     * Type for {@link #CHARACTER_RECT_TYPE_MASK}: the editor did not specify any type of this
     * character. Editor authors should not use this flag.
     */
    public static final int CHARACTER_RECT_TYPE_UNSPECIFIED = 0;
    /**
     * Type for {@link #CHARACTER_RECT_TYPE_MASK}: the character is entirely visible.
     */
    public static final int CHARACTER_RECT_TYPE_FULLY_VISIBLE = 1;
    /**
     * Type for {@link #CHARACTER_RECT_TYPE_MASK}: some area of the character is invisible.
     */
    public static final int CHARACTER_RECT_TYPE_PARTIALLY_VISIBLE = 2;
    /**
     * Type for {@link #CHARACTER_RECT_TYPE_MASK}: the character is entirely invisible.
     */
    public static final int CHARACTER_RECT_TYPE_INVISIBLE = 3;
    /**
     * Type for {@link #CHARACTER_RECT_TYPE_MASK}: the editor gave up to calculate the rectangle
     * for this character. Input method authors should ignore the returned rectangle.
     */
    public static final int CHARACTER_RECT_TYPE_NOT_FEASIBLE = 4;

    public CursorAnchorInfo(final Parcel source) {
        mSelectionStart = source.readInt();
        mSelectionEnd = source.readInt();
        mComposingTextStart = source.readInt();
        mComposingText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
        mInsertionMarkerClipped = (source.readInt() != 0);
        mInsertionMarkerHorizontal = source.readFloat();
        mInsertionMarkerTop = source.readFloat();
        mInsertionMarkerBaseline = source.readFloat();
@@ -112,6 +141,7 @@ public final class CursorAnchorInfo implements Parcelable {
        dest.writeInt(mSelectionEnd);
        dest.writeInt(mComposingTextStart);
        TextUtils.writeToParcel(mComposingText, dest, flags);
        dest.writeInt(mInsertionMarkerClipped ? 1 : 0);
        dest.writeFloat(mInsertionMarkerHorizontal);
        dest.writeFloat(mInsertionMarkerTop);
        dest.writeFloat(mInsertionMarkerBaseline);
@@ -129,6 +159,8 @@ public final class CursorAnchorInfo implements Parcelable {
                + mInsertionMarkerBaseline + mInsertionMarkerBottom;
        int hash = floatHash > 0 ? (int) floatHash : (int)(-floatHash);
        hash *= 31;
        hash += (mInsertionMarkerClipped ? 2 : 1);
        hash *= 31;
        hash += mSelectionStart + mSelectionEnd + mComposingTextStart;
        hash *= 31;
        hash += Objects.hashCode(mComposingText);
@@ -172,7 +204,8 @@ public final class CursorAnchorInfo implements Parcelable {
                || !Objects.equals(mComposingText, that.mComposingText)) {
            return false;
        }
        if (!areSameFloatImpl(mInsertionMarkerHorizontal, that.mInsertionMarkerHorizontal)
        if (mInsertionMarkerClipped != that.mInsertionMarkerClipped
                || !areSameFloatImpl(mInsertionMarkerHorizontal, that.mInsertionMarkerHorizontal)
                || !areSameFloatImpl(mInsertionMarkerTop, that.mInsertionMarkerTop)
                || !areSameFloatImpl(mInsertionMarkerBaseline, that.mInsertionMarkerBaseline)
                || !areSameFloatImpl(mInsertionMarkerBottom, that.mInsertionMarkerBottom)) {
@@ -195,6 +228,7 @@ public final class CursorAnchorInfo implements Parcelable {
        return "SelectionInfo{mSelection=" + mSelectionStart + "," + mSelectionEnd
                + " mComposingTextStart=" + mComposingTextStart
                + " mComposingText=" + Objects.toString(mComposingText)
                + " mInsertionMarkerClipped=" + mInsertionMarkerClipped
                + " mInsertionMarkerHorizontal=" + mInsertionMarkerHorizontal
                + " mInsertionMarkerTop=" + mInsertionMarkerTop
                + " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline
@@ -256,25 +290,27 @@ public final class CursorAnchorInfo implements Parcelable {
         * @param lineBottom vertical position of the insertion marker, in the local coordinates
         * that will be transformed with the transformation matrix when rendered on the screen. This
         * should be calculated or compatible with {@link Layout#getLineBottom(int)}.
         * @param clipped {@code true} is the insertion marker is partially or entierly clipped by
         * other UI elements.
         */
        public Builder setInsertionMarkerLocation(final float horizontalPosition,
                final float lineTop, final float lineBaseline, final float lineBottom){
                final float lineTop, final float lineBaseline, final float lineBottom,
                final boolean clipped){
            mInsertionMarkerHorizontal = horizontalPosition;
            mInsertionMarkerTop = lineTop;
            mInsertionMarkerBaseline = lineBaseline;
            mInsertionMarkerBottom = lineBottom;
            mInsertionMarkerClipped = clipped;
            return this;
        }
        private float mInsertionMarkerHorizontal = Float.NaN;
        private float mInsertionMarkerTop = Float.NaN;
        private float mInsertionMarkerBaseline = Float.NaN;
        private float mInsertionMarkerBottom = Float.NaN;
        private boolean mInsertionMarkerClipped = false;

        /**
         * Adds the bounding box of the character specified with the index.
         * <p>
         * Editor authors should not call this method for characters that are invisible.
         * </p>
         *
         * @param index index of the character in Java chars units. Must be specified in
         * ascending order across successive calls.
@@ -286,19 +322,27 @@ public final class CursorAnchorInfo implements Parcelable {
         * coordinates, that is, right edge for LTR text and left edge for RTL text.
         * @param trailingEdgeY y coordinate of the trailing edge of the character in local
         * coordinates.
         * @param flags type and flags for this character. See
         * {@link #CHARACTER_RECT_TYPE_FULLY_VISIBLE} for example.
         * @throws IllegalArgumentException If the index is a negative value, or not greater than
         * all of the previously called indices.
         */
        public Builder addCharacterRect(final int index, final float leadingEdgeX,
                final float leadingEdgeY, final float trailingEdgeX, final float trailingEdgeY) {
                final float leadingEdgeY, final float trailingEdgeX, final float trailingEdgeY,
                final int flags) {
            if (index < 0) {
                throw new IllegalArgumentException("index must not be a negative integer.");
            }
            final int type = flags & CHARACTER_RECT_TYPE_MASK;
            if (type == CHARACTER_RECT_TYPE_UNSPECIFIED) {
                throw new IllegalArgumentException("Type except for "
                        + "CHARACTER_RECT_TYPE_UNSPECIFIED must be specified.");
            }
            if (mCharacterRectBuilder == null) {
                mCharacterRectBuilder = new SparseRectFArrayBuilder();
            }
            mCharacterRectBuilder.append(index, leadingEdgeX, leadingEdgeY, trailingEdgeX,
                    trailingEdgeY);
                    trailingEdgeY, flags);
            return this;
        }
        private SparseRectFArrayBuilder mCharacterRectBuilder = null;
@@ -346,6 +390,7 @@ public final class CursorAnchorInfo implements Parcelable {
            mSelectionEnd = -1;
            mComposingTextStart = -1;
            mComposingText = null;
            mInsertionMarkerClipped = false;
            mInsertionMarkerHorizontal = Float.NaN;
            mInsertionMarkerTop = Float.NaN;
            mInsertionMarkerBaseline = Float.NaN;
@@ -363,6 +408,7 @@ public final class CursorAnchorInfo implements Parcelable {
        mSelectionEnd = builder.mSelectionEnd;
        mComposingTextStart = builder.mComposingTextStart;
        mComposingText = builder.mComposingText;
        mInsertionMarkerClipped = builder.mInsertionMarkerClipped;
        mInsertionMarkerHorizontal = builder.mInsertionMarkerHorizontal;
        mInsertionMarkerTop = builder.mInsertionMarkerTop;
        mInsertionMarkerBaseline = builder.mInsertionMarkerBaseline;
@@ -404,6 +450,14 @@ public final class CursorAnchorInfo implements Parcelable {
        return mComposingText;
    }

    /**
     * Returns the visibility of the insertion marker.
     * @return {@code true} if the insertion marker is partially or entirely clipped.
     */
    public boolean isInsertionMarkerClipped() {
        return mInsertionMarkerClipped;
    }

    /**
     * Returns the horizontal start of the insertion marker, in the local coordinates that will
     * be transformed with {@link #getMatrix()} when rendered on the screen.
@@ -415,6 +469,7 @@ public final class CursorAnchorInfo implements Parcelable {
    public float getInsertionMarkerHorizontal() {
        return mInsertionMarkerHorizontal;
    }

    /**
     * Returns the vertical top position of the insertion marker, in the local coordinates that
     * will be transformed with {@link #getMatrix()} when rendered on the screen.
@@ -424,6 +479,7 @@ public final class CursorAnchorInfo implements Parcelable {
    public float getInsertionMarkerTop() {
        return mInsertionMarkerTop;
    }

    /**
     * Returns the vertical baseline position of the insertion marker, in the local coordinates
     * that will be transformed with {@link #getMatrix()} when rendered on the screen.
@@ -433,6 +489,7 @@ public final class CursorAnchorInfo implements Parcelable {
    public float getInsertionMarkerBaseline() {
        return mInsertionMarkerBaseline;
    }

    /**
     * Returns the vertical bottom position of the insertion marker, in the local coordinates
     * that will be transformed with {@link #getMatrix()} when rendered on the screen.
@@ -466,6 +523,20 @@ public final class CursorAnchorInfo implements Parcelable {
        return mCharacterRects.get(index);
    }

    /**
     * Returns the flags associated with the character specified with the index.
     * @param index index of the character in a Java chars.
     * @return {@link #CHARACTER_RECT_TYPE_UNSPECIFIED} if no flag is specified.
     */
    // TODO: Prepare a document about the expected behavior for surrogate pairs, combining
    // characters, and non-graphical chars.
    public int getCharacterRectFlags(final int index) {
        if (mCharacterRects == null) {
            return CHARACTER_RECT_TYPE_UNSPECIFIED;
        }
        return mCharacterRects.getFlags(index, CHARACTER_RECT_TYPE_UNSPECIFIED);
    }

    /**
     * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation
     * matrix that is to be applied other positional data in this class.
+47 −4
Original line number Diff line number Diff line
@@ -50,9 +50,15 @@ public final class SparseRectFArray implements Parcelable {
     */
    private final float[] mCoordinates;

    /**
     * Stores visibility information.
     */
    private final int[] mFlagsArray;

    public SparseRectFArray(final Parcel source) {
        mKeys = source.createIntArray();
        mCoordinates = source.createFloatArray();
        mFlagsArray = source.createIntArray();
    }

    /**
@@ -65,6 +71,7 @@ public final class SparseRectFArray implements Parcelable {
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeIntArray(mKeys);
        dest.writeFloatArray(mCoordinates);
        dest.writeIntArray(mFlagsArray);
    }

    @Override
@@ -79,6 +86,8 @@ public final class SparseRectFArray implements Parcelable {
            hash *= 31;
            hash += mCoordinates[i];
        }
        hash *= 31;
        hash += mFlagsArray[0];
        return hash;
    }

@@ -95,12 +104,13 @@ public final class SparseRectFArray implements Parcelable {
        }
        final SparseRectFArray that = (SparseRectFArray) obj;

        return Arrays.equals(mKeys, that.mKeys) && Arrays.equals(mCoordinates, that.mCoordinates);
        return Arrays.equals(mKeys, that.mKeys) && Arrays.equals(mCoordinates, that.mCoordinates)
                && Arrays.equals(mFlagsArray, that.mFlagsArray);
    }

    @Override
    public String toString() {
        if (mKeys == null || mCoordinates == null) {
        if (mKeys == null || mCoordinates == null || mFlagsArray == null) {
            return "SparseRectFArray{}";
        }
        final StringBuilder sb = new StringBuilder();
@@ -119,7 +129,8 @@ public final class SparseRectFArray implements Parcelable {
            sb.append(mCoordinates[baseIndex + 2]);
            sb.append(",");
            sb.append(mCoordinates[baseIndex + 3]);
            sb.append("]");
            sb.append("]:flagsArray=");
            sb.append(mFlagsArray[i]);
        }
        sb.append("}");
        return sb.toString();
@@ -153,6 +164,9 @@ public final class SparseRectFArray implements Parcelable {
            if (mCoordinates == null) {
                mCoordinates = new float[INITIAL_SIZE * 4];
            }
            if (mFlagsArray == null) {
                mFlagsArray = new int[INITIAL_SIZE];
            }
            final int requiredIndexArraySize = mCount + 1;
            if (mKeys.length <= requiredIndexArraySize) {
                final int[] newArray = new int[requiredIndexArraySize * 2];
@@ -165,6 +179,12 @@ public final class SparseRectFArray implements Parcelable {
                System.arraycopy(mCoordinates, 0, newArray, 0, mCount * 4);
                mCoordinates = newArray;
            }
            final int requiredFlagsArraySize = requiredIndexArraySize;
            if (mFlagsArray.length <= requiredFlagsArraySize) {
                final int[] newArray = new int[requiredFlagsArraySize * 2];
                System.arraycopy(mFlagsArray, 0, newArray, 0, mCount);
                mFlagsArray = newArray;
            }
        }

        /**
@@ -175,11 +195,13 @@ public final class SparseRectFArray implements Parcelable {
         * @param top top of the rectangle.
         * @param right right of the rectangle.
         * @param bottom bottom of the rectangle.
         * @param flags an arbitrary integer value to be associated with this rectangle.
         * @return the receiver object itself for chaining method calls.
         * @throws IllegalArgumentException If the index is not greater than all of existing keys.
         */
        public SparseRectFArrayBuilder append(final int key,
                final float left, final float top, final float right, final float bottom) {
                final float left, final float top, final float right, final float bottom,
                final int flags) {
            checkIndex(key);
            ensureBufferSize();
            final int baseCoordinatesIndex = mCount * 4;
@@ -187,6 +209,8 @@ public final class SparseRectFArray implements Parcelable {
            mCoordinates[baseCoordinatesIndex + 1] = top;
            mCoordinates[baseCoordinatesIndex + 2] = right;
            mCoordinates[baseCoordinatesIndex + 3] = bottom;
            final int flagsIndex = mCount;
            mFlagsArray[flagsIndex] = flags;
            mKeys[mCount] = key;
            ++mCount;
            return this;
@@ -194,6 +218,7 @@ public final class SparseRectFArray implements Parcelable {
        private int mCount = 0;
        private int[] mKeys = null;
        private float[] mCoordinates = null;
        private int[] mFlagsArray = null;
        private static int INITIAL_SIZE = 16;

        public boolean isEmpty() {
@@ -211,6 +236,7 @@ public final class SparseRectFArray implements Parcelable {
            if (mCount == 0) {
                mKeys = null;
                mCoordinates = null;
                mFlagsArray = null;
            }
            mCount = 0;
        }
@@ -220,11 +246,14 @@ public final class SparseRectFArray implements Parcelable {
        if (builder.mCount == 0) {
            mKeys = null;
            mCoordinates = null;
            mFlagsArray = null;
        } else {
            mKeys = new int[builder.mCount];
            mCoordinates = new float[builder.mCount * 4];
            mFlagsArray = new int[builder.mCount];
            System.arraycopy(builder.mKeys, 0, mKeys, 0, builder.mCount);
            System.arraycopy(builder.mCoordinates, 0, mCoordinates, 0, builder.mCount * 4);
            System.arraycopy(builder.mFlagsArray, 0, mFlagsArray, 0, builder.mCount);
        }
    }

@@ -246,6 +275,20 @@ public final class SparseRectFArray implements Parcelable {
                mCoordinates[baseCoordIndex + 3]);
    }

    public int getFlags(final int index, final int valueIfKeyNotFound) {
        if (mKeys == null) {
            return valueIfKeyNotFound;
        }
        if (index < 0) {
            return valueIfKeyNotFound;
        }
        final int arrayIndex = Arrays.binarySearch(mKeys, index);
        if (arrayIndex < 0) {
            return valueIfKeyNotFound;
        }
        return mFlagsArray[arrayIndex];
    }

    /**
     * Used to make this class parcelable.
     */
+19 −11
Original line number Diff line number Diff line
@@ -3088,13 +3088,22 @@ public class Editor {
                    final float bottom = layout.getLineBottom(line)
                            + viewportToContentVerticalOffset;
                    // Take TextView's padding and scroll into account.
                    if (isPositionVisible(left, top) && isPositionVisible(right, bottom)) {
                    // TODO: Check right-top and left-bottom as well.
                    final boolean leftTopVisible = isPositionVisible(left, top);
                    final boolean rightBottomVisible = isPositionVisible(right, bottom);
                    final int characterRectFlags;
                    if (leftTopVisible && rightBottomVisible) {
                        characterRectFlags = CursorAnchorInfo.CHARACTER_RECT_TYPE_FULLY_VISIBLE;
                    } else if (leftTopVisible || rightBottomVisible) {
                        characterRectFlags = CursorAnchorInfo.CHARACTER_RECT_TYPE_PARTIALLY_VISIBLE;
                    } else {
                        characterRectFlags = CursorAnchorInfo.CHARACTER_RECT_TYPE_INVISIBLE;
                    }
                    // Here offset is the index in Java chars.
                    // TODO: We must have a well-defined specification. For example, how
                    // RTL, surrogate pairs, and composition letters are handled must be
                    // documented.
                        builder.addCharacterRect(offset, left, top, right, bottom);
                    }
                    builder.addCharacterRect(offset, left, top, right, bottom, characterRectFlags);
                }
            }

@@ -3111,11 +3120,10 @@ public class Editor {
                final float insertionMarkerBottom = layout.getLineBottom(line)
                        + viewportToContentVerticalOffset;
                // Take TextView's padding and scroll into account.
                if (isPositionVisible(insertionMarkerX, insertionMarkerTop) &&
                        isPositionVisible(insertionMarkerX, insertionMarkerBottom)) {
                final boolean isClipped = !isPositionVisible(insertionMarkerX, insertionMarkerTop)
                        || !isPositionVisible(insertionMarkerX, insertionMarkerBottom);
                builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
                            insertionMarkerBaseline, insertionMarkerBottom);
                }
                        insertionMarkerBaseline, insertionMarkerBottom, isClipped);
            }

            imm.updateCursorAnchorInfo(mTextView, builder.build());
+123 −34

File changed.

Preview size limit exceeded, changes collapsed.

Loading