1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package android.view.inputmethod;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.graphics.Matrix;
22 import android.graphics.RectF;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.text.Layout;
26 import android.text.SpannedString;
27 import android.text.TextUtils;
28 import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
29 
30 import java.util.Arrays;
31 import java.util.Objects;
32 
33 /**
34  * Positional information about the text insertion point and characters in the composition string.
35  *
36  * <p>This class encapsulates locations of the text insertion point and the composition string in
37  * the screen coordinates so that IMEs can render their UI components near where the text is
38  * actually inserted.</p>
39  */
40 public final class CursorAnchorInfo implements Parcelable {
41     /**
42      * The pre-computed hash code.
43      */
44     private final int mHashCode;
45 
46     /**
47      * The index of the first character of the selected text (inclusive). {@code -1} when there is
48      * no text selection.
49      */
50     private final int mSelectionStart;
51     /**
52      * The index of the first character of the selected text (exclusive). {@code -1} when there is
53      * no text selection.
54      */
55     private final int mSelectionEnd;
56 
57     /**
58      * The index of the first character of the composing text (inclusive). {@code -1} when there is
59      * no composing text.
60      */
61     private final int mComposingTextStart;
62     /**
63      * The text, tracked as a composing region.
64      */
65     private final CharSequence mComposingText;
66 
67     /**
68      * Flags of the insertion marker. See {@link #FLAG_HAS_VISIBLE_REGION} for example.
69      */
70     private final int mInsertionMarkerFlags;
71     /**
72      * Horizontal position of the insertion marker, in the local coordinates that will be
73      * transformed with the transformation matrix when rendered on the screen. This should be
74      * calculated or compatible with {@link Layout#getPrimaryHorizontal(int)}. This can be
75      * {@code java.lang.Float.NaN} when no value is specified.
76      */
77     private final float mInsertionMarkerHorizontal;
78     /**
79      * Vertical position of the insertion marker, in the local coordinates that will be
80      * transformed with the transformation matrix when rendered on the screen. This should be
81      * calculated or compatible with {@link Layout#getLineTop(int)}. This can be
82      * {@code java.lang.Float.NaN} when no value is specified.
83      */
84     private final float mInsertionMarkerTop;
85     /**
86      * Vertical position of the insertion marker, in the local coordinates that will be
87      * transformed with the transformation matrix when rendered on the screen. This should be
88      * calculated or compatible with {@link Layout#getLineBaseline(int)}. This can be
89      * {@code java.lang.Float.NaN} when no value is specified.
90      */
91     private final float mInsertionMarkerBaseline;
92     /**
93      * Vertical position of the insertion marker, in the local coordinates that will be
94      * transformed with the transformation matrix when rendered on the screen. This should be
95      * calculated or compatible with {@link Layout#getLineBottom(int)}. This can be
96      * {@code java.lang.Float.NaN} when no value is specified.
97      */
98     private final float mInsertionMarkerBottom;
99 
100     /**
101      * Container of rectangular position of characters, keyed with character index in a unit of
102      * Java chars, in the local coordinates that will be transformed with the transformation matrix
103      * when rendered on the screen.
104      */
105     private final SparseRectFArray mCharacterBoundsArray;
106 
107     /**
108      * Transformation matrix that is applied to any positional information of this class to
109      * transform local coordinates into screen coordinates.
110      */
111     @NonNull
112     private final float[] mMatrixValues;
113 
114     /**
115      * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the
116      * insertion marker or character bounds have at least one visible region.
117      */
118     public static final int FLAG_HAS_VISIBLE_REGION = 0x01;
119 
120     /**
121      * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the
122      * insertion marker or character bounds have at least one invisible (clipped) region.
123      */
124     public static final int FLAG_HAS_INVISIBLE_REGION = 0x02;
125 
126     /**
127      * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the
128      * insertion marker or character bounds is placed at right-to-left (RTL) character.
129      */
130     public static final int FLAG_IS_RTL = 0x04;
131 
CursorAnchorInfo(final Parcel source)132     public CursorAnchorInfo(final Parcel source) {
133         mHashCode = source.readInt();
134         mSelectionStart = source.readInt();
135         mSelectionEnd = source.readInt();
136         mComposingTextStart = source.readInt();
137         mComposingText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
138         mInsertionMarkerFlags = source.readInt();
139         mInsertionMarkerHorizontal = source.readFloat();
140         mInsertionMarkerTop = source.readFloat();
141         mInsertionMarkerBaseline = source.readFloat();
142         mInsertionMarkerBottom = source.readFloat();
143         mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader());
144         mMatrixValues = source.createFloatArray();
145     }
146 
147     /**
148      * Used to package this object into a {@link Parcel}.
149      *
150      * @param dest The {@link Parcel} to be written.
151      * @param flags The flags used for parceling.
152      */
153     @Override
writeToParcel(Parcel dest, int flags)154     public void writeToParcel(Parcel dest, int flags) {
155         dest.writeInt(mHashCode);
156         dest.writeInt(mSelectionStart);
157         dest.writeInt(mSelectionEnd);
158         dest.writeInt(mComposingTextStart);
159         TextUtils.writeToParcel(mComposingText, dest, flags);
160         dest.writeInt(mInsertionMarkerFlags);
161         dest.writeFloat(mInsertionMarkerHorizontal);
162         dest.writeFloat(mInsertionMarkerTop);
163         dest.writeFloat(mInsertionMarkerBaseline);
164         dest.writeFloat(mInsertionMarkerBottom);
165         dest.writeParcelable(mCharacterBoundsArray, flags);
166         dest.writeFloatArray(mMatrixValues);
167     }
168 
169     @Override
hashCode()170     public int hashCode(){
171         return mHashCode;
172     }
173 
174     /**
175      * Compares two float values. Returns {@code true} if {@code a} and {@code b} are
176      * {@link Float#NaN} at the same time.
177      */
areSameFloatImpl(final float a, final float b)178     private static boolean areSameFloatImpl(final float a, final float b) {
179         if (Float.isNaN(a) && Float.isNaN(b)) {
180             return true;
181         }
182         return a == b;
183     }
184 
185     @Override
equals(Object obj)186     public boolean equals(Object obj){
187         if (obj == null) {
188             return false;
189         }
190         if (this == obj) {
191             return true;
192         }
193         if (!(obj instanceof CursorAnchorInfo)) {
194             return false;
195         }
196         final CursorAnchorInfo that = (CursorAnchorInfo) obj;
197         if (hashCode() != that.hashCode()) {
198             return false;
199         }
200 
201         // Check fields that are not covered by hashCode() first.
202 
203         if (mSelectionStart != that.mSelectionStart || mSelectionEnd != that.mSelectionEnd) {
204             return false;
205         }
206 
207         if (mInsertionMarkerFlags != that.mInsertionMarkerFlags
208                 || !areSameFloatImpl(mInsertionMarkerHorizontal, that.mInsertionMarkerHorizontal)
209                 || !areSameFloatImpl(mInsertionMarkerTop, that.mInsertionMarkerTop)
210                 || !areSameFloatImpl(mInsertionMarkerBaseline, that.mInsertionMarkerBaseline)
211                 || !areSameFloatImpl(mInsertionMarkerBottom, that.mInsertionMarkerBottom)) {
212             return false;
213         }
214 
215         if (!Objects.equals(mCharacterBoundsArray, that.mCharacterBoundsArray)) {
216             return false;
217         }
218 
219         // Following fields are (partially) covered by hashCode().
220 
221         if (mComposingTextStart != that.mComposingTextStart
222                 || !Objects.equals(mComposingText, that.mComposingText)) {
223             return false;
224         }
225 
226         // We do not use Arrays.equals(float[], float[]) to keep the previous behavior regarding
227         // NaN, 0.0f, and -0.0f.
228         if (mMatrixValues.length != that.mMatrixValues.length) {
229             return false;
230         }
231         for (int i = 0; i < mMatrixValues.length; ++i) {
232             if (mMatrixValues[i] != that.mMatrixValues[i]) {
233                 return false;
234             }
235         }
236         return true;
237     }
238 
239     @Override
toString()240     public String toString() {
241         return "CursorAnchorInfo{mHashCode=" + mHashCode
242                 + " mSelection=" + mSelectionStart + "," + mSelectionEnd
243                 + " mComposingTextStart=" + mComposingTextStart
244                 + " mComposingText=" + Objects.toString(mComposingText)
245                 + " mInsertionMarkerFlags=" + mInsertionMarkerFlags
246                 + " mInsertionMarkerHorizontal=" + mInsertionMarkerHorizontal
247                 + " mInsertionMarkerTop=" + mInsertionMarkerTop
248                 + " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline
249                 + " mInsertionMarkerBottom=" + mInsertionMarkerBottom
250                 + " mCharacterBoundsArray=" + Objects.toString(mCharacterBoundsArray)
251                 + " mMatrix=" + Arrays.toString(mMatrixValues)
252                 + "}";
253     }
254 
255     /**
256      * Builder for {@link CursorAnchorInfo}. This class is not designed to be thread-safe.
257      */
258     public static final class Builder {
259         private int mSelectionStart = -1;
260         private int mSelectionEnd = -1;
261         private int mComposingTextStart = -1;
262         private CharSequence mComposingText = null;
263         private float mInsertionMarkerHorizontal = Float.NaN;
264         private float mInsertionMarkerTop = Float.NaN;
265         private float mInsertionMarkerBaseline = Float.NaN;
266         private float mInsertionMarkerBottom = Float.NaN;
267         private int mInsertionMarkerFlags = 0;
268         private SparseRectFArrayBuilder mCharacterBoundsArrayBuilder = null;
269         private float[] mMatrixValues = null;
270         private boolean mMatrixInitialized = false;
271 
272         /**
273          * Sets the text range of the selection. Calling this can be skipped if there is no
274          * selection.
275          */
setSelectionRange(final int newStart, final int newEnd)276         public Builder setSelectionRange(final int newStart, final int newEnd) {
277             mSelectionStart = newStart;
278             mSelectionEnd = newEnd;
279             return this;
280         }
281 
282         /**
283          * Sets the text range of the composing text. Calling this can be skipped if there is
284          * no composing text.
285          * @param composingTextStart index where the composing text starts.
286          * @param composingText the entire composing text.
287          */
setComposingText(final int composingTextStart, final CharSequence composingText)288         public Builder setComposingText(final int composingTextStart,
289             final CharSequence composingText) {
290             mComposingTextStart = composingTextStart;
291             if (composingText == null) {
292                 mComposingText = null;
293             } else {
294                 // Make a snapshot of the given char sequence.
295                 mComposingText = new SpannedString(composingText);
296             }
297             return this;
298         }
299 
300         /**
301          * Sets the location of the text insertion point (zero width cursor) as a rectangle in
302          * local coordinates. Calling this can be skipped when there is no text insertion point;
303          * however if there is an insertion point, editors must call this method.
304          * @param horizontalPosition horizontal position of the insertion marker, in the local
305          * coordinates that will be transformed with the transformation matrix when rendered on the
306          * screen. This should be calculated or compatible with
307          * {@link Layout#getPrimaryHorizontal(int)}.
308          * @param lineTop vertical position of the insertion marker, in the local coordinates that
309          * will be transformed with the transformation matrix when rendered on the screen. This
310          * should be calculated or compatible with {@link Layout#getLineTop(int)}.
311          * @param lineBaseline vertical position of the insertion marker, in the local coordinates
312          * that will be transformed with the transformation matrix when rendered on the screen. This
313          * should be calculated or compatible with {@link Layout#getLineBaseline(int)}.
314          * @param lineBottom vertical position of the insertion marker, in the local coordinates
315          * that will be transformed with the transformation matrix when rendered on the screen. This
316          * should be calculated or compatible with {@link Layout#getLineBottom(int)}.
317          * @param flags flags of the insertion marker. See {@link #FLAG_HAS_VISIBLE_REGION} for
318          * example.
319          */
setInsertionMarkerLocation(final float horizontalPosition, final float lineTop, final float lineBaseline, final float lineBottom, final int flags)320         public Builder setInsertionMarkerLocation(final float horizontalPosition,
321                 final float lineTop, final float lineBaseline, final float lineBottom,
322                 final int flags){
323             mInsertionMarkerHorizontal = horizontalPosition;
324             mInsertionMarkerTop = lineTop;
325             mInsertionMarkerBaseline = lineBaseline;
326             mInsertionMarkerBottom = lineBottom;
327             mInsertionMarkerFlags = flags;
328             return this;
329         }
330 
331         /**
332          * Adds the bounding box of the character specified with the index.
333          *
334          * @param index index of the character in Java chars units. Must be specified in
335          * ascending order across successive calls.
336          * @param left x coordinate of the left edge of the character in local coordinates.
337          * @param top y coordinate of the top edge of the character in local coordinates.
338          * @param right x coordinate of the right edge of the character in local coordinates.
339          * @param bottom y coordinate of the bottom edge of the character in local coordinates.
340          * @param flags flags for this character bounds. See {@link #FLAG_HAS_VISIBLE_REGION},
341          * {@link #FLAG_HAS_INVISIBLE_REGION} and {@link #FLAG_IS_RTL}. These flags must be
342          * specified when necessary.
343          * @throws IllegalArgumentException If the index is a negative value, or not greater than
344          * all of the previously called indices.
345          */
addCharacterBounds(final int index, final float left, final float top, final float right, final float bottom, final int flags)346         public Builder addCharacterBounds(final int index, final float left, final float top,
347                 final float right, final float bottom, final int flags) {
348             if (index < 0) {
349                 throw new IllegalArgumentException("index must not be a negative integer.");
350             }
351             if (mCharacterBoundsArrayBuilder == null) {
352                 mCharacterBoundsArrayBuilder = new SparseRectFArrayBuilder();
353             }
354             mCharacterBoundsArrayBuilder.append(index, left, top, right, bottom, flags);
355             return this;
356         }
357 
358         /**
359          * Sets the matrix that transforms local coordinates into screen coordinates.
360          * @param matrix transformation matrix from local coordinates into screen coordinates. null
361          * is interpreted as an identity matrix.
362          */
setMatrix(final Matrix matrix)363         public Builder setMatrix(final Matrix matrix) {
364             if (mMatrixValues == null) {
365                 mMatrixValues = new float[9];
366             }
367             (matrix != null ? matrix : Matrix.IDENTITY_MATRIX).getValues(mMatrixValues);
368             mMatrixInitialized = true;
369             return this;
370         }
371 
372         /**
373          * @return {@link CursorAnchorInfo} using parameters in this {@link Builder}.
374          * @throws IllegalArgumentException if one or more positional parameters are specified but
375          * the coordinate transformation matrix is not provided via {@link #setMatrix(Matrix)}.
376          */
build()377         public CursorAnchorInfo build() {
378             if (!mMatrixInitialized) {
379                 // Coordinate transformation matrix is mandatory when at least one positional
380                 // parameter is specified.
381                 final boolean hasCharacterBounds = (mCharacterBoundsArrayBuilder != null
382                         && !mCharacterBoundsArrayBuilder.isEmpty());
383                 if (hasCharacterBounds
384                         || !Float.isNaN(mInsertionMarkerHorizontal)
385                         || !Float.isNaN(mInsertionMarkerTop)
386                         || !Float.isNaN(mInsertionMarkerBaseline)
387                         || !Float.isNaN(mInsertionMarkerBottom)) {
388                     throw new IllegalArgumentException("Coordinate transformation matrix is " +
389                             "required when positional parameters are specified.");
390                 }
391             }
392             return CursorAnchorInfo.create(this);
393         }
394 
395         /**
396          * Resets the internal state so that this instance can be reused to build another
397          * instance of {@link CursorAnchorInfo}.
398          */
reset()399         public void reset() {
400             mSelectionStart = -1;
401             mSelectionEnd = -1;
402             mComposingTextStart = -1;
403             mComposingText = null;
404             mInsertionMarkerFlags = 0;
405             mInsertionMarkerHorizontal = Float.NaN;
406             mInsertionMarkerTop = Float.NaN;
407             mInsertionMarkerBaseline = Float.NaN;
408             mInsertionMarkerBottom = Float.NaN;
409             mMatrixInitialized = false;
410             if (mCharacterBoundsArrayBuilder != null) {
411                 mCharacterBoundsArrayBuilder.reset();
412             }
413         }
414     }
415 
create(Builder builder)416     private static CursorAnchorInfo create(Builder builder) {
417         final SparseRectFArray characterBoundsArray =
418                 builder.mCharacterBoundsArrayBuilder != null
419                         ? builder.mCharacterBoundsArrayBuilder.build()
420                         : null;
421         final float[] matrixValues = new float[9];
422         if (builder.mMatrixInitialized) {
423             System.arraycopy(builder.mMatrixValues, 0, matrixValues, 0, 9);
424         } else {
425             Matrix.IDENTITY_MATRIX.getValues(matrixValues);
426         }
427 
428         return new CursorAnchorInfo(builder.mSelectionStart, builder.mSelectionEnd,
429                 builder.mComposingTextStart, builder.mComposingText, builder.mInsertionMarkerFlags,
430                 builder.mInsertionMarkerHorizontal, builder.mInsertionMarkerTop,
431                 builder.mInsertionMarkerBaseline, builder.mInsertionMarkerBottom,
432                 characterBoundsArray, matrixValues);
433     }
434 
CursorAnchorInfo(int selectionStart, int selectionEnd, int composingTextStart, @Nullable CharSequence composingText, int insertionMarkerFlags, float insertionMarkerHorizontal, float insertionMarkerTop, float insertionMarkerBaseline, float insertionMarkerBottom, @Nullable SparseRectFArray characterBoundsArray, @NonNull float[] matrixValues)435     private CursorAnchorInfo(int selectionStart, int selectionEnd, int composingTextStart,
436             @Nullable CharSequence composingText, int insertionMarkerFlags,
437             float insertionMarkerHorizontal, float insertionMarkerTop,
438             float insertionMarkerBaseline, float insertionMarkerBottom,
439             @Nullable  SparseRectFArray characterBoundsArray, @NonNull float[] matrixValues) {
440         mSelectionStart = selectionStart;
441         mSelectionEnd = selectionEnd;
442         mComposingTextStart = composingTextStart;
443         mComposingText = composingText;
444         mInsertionMarkerFlags = insertionMarkerFlags;
445         mInsertionMarkerHorizontal = insertionMarkerHorizontal;
446         mInsertionMarkerTop = insertionMarkerTop;
447         mInsertionMarkerBaseline = insertionMarkerBaseline;
448         mInsertionMarkerBottom = insertionMarkerBottom;
449         mCharacterBoundsArray = characterBoundsArray;
450         mMatrixValues = matrixValues;
451 
452         // To keep hash function simple, we only use some complex objects for hash.
453         int hashCode = Objects.hashCode(mComposingText);
454         hashCode *= 31;
455         hashCode += Arrays.hashCode(matrixValues);
456         mHashCode = hashCode;
457     }
458 
459     /**
460      * Creates a new instance of {@link CursorAnchorInfo} by applying {@code parentMatrix} to
461      * the coordinate transformation matrix.
462      *
463      * @param original {@link CursorAnchorInfo} to be cloned from.
464      * @param parentMatrix {@link Matrix} to be applied to {@code original.getMatrix()}
465      * @return A new instance of {@link CursorAnchorInfo} whose {@link CursorAnchorInfo#getMatrix()}
466      *         returns {@code parentMatrix * original.getMatrix()}.
467      * @hide
468      */
createForAdditionalParentMatrix(CursorAnchorInfo original, @NonNull Matrix parentMatrix)469     public static CursorAnchorInfo createForAdditionalParentMatrix(CursorAnchorInfo original,
470             @NonNull Matrix parentMatrix) {
471         return new CursorAnchorInfo(original.mSelectionStart, original.mSelectionEnd,
472                 original.mComposingTextStart, original.mComposingText,
473                 original.mInsertionMarkerFlags, original.mInsertionMarkerHorizontal,
474                 original.mInsertionMarkerTop, original.mInsertionMarkerBaseline,
475                 original.mInsertionMarkerBottom, original.mCharacterBoundsArray,
476                 computeMatrixValues(parentMatrix, original));
477     }
478 
479     /**
480      * Returns a float array that represents {@link Matrix} elements for
481      * {@code parentMatrix * info.getMatrix()}.
482      *
483      * @param parentMatrix {@link Matrix} to be multiplied.
484      * @param info {@link CursorAnchorInfo} to provide {@link Matrix} to be multiplied.
485      * @return {@code parentMatrix * info.getMatrix()}.
486      */
computeMatrixValues(@onNull Matrix parentMatrix, @NonNull CursorAnchorInfo info)487     private static float[] computeMatrixValues(@NonNull Matrix parentMatrix,
488             @NonNull CursorAnchorInfo info) {
489         if (parentMatrix.isIdentity()) {
490             return info.mMatrixValues;
491         }
492 
493         final Matrix newMatrix = new Matrix();
494         newMatrix.setValues(info.mMatrixValues);
495         newMatrix.postConcat(parentMatrix);
496 
497         final float[] matrixValues = new float[9];
498         newMatrix.getValues(matrixValues);
499         return matrixValues;
500     }
501 
502     /**
503      * Returns the index where the selection starts.
504      * @return {@code -1} if there is no selection.
505      */
getSelectionStart()506     public int getSelectionStart() {
507         return mSelectionStart;
508     }
509 
510     /**
511      * Returns the index where the selection ends.
512      * @return {@code -1} if there is no selection.
513      */
getSelectionEnd()514     public int getSelectionEnd() {
515         return mSelectionEnd;
516     }
517 
518     /**
519      * Returns the index where the composing text starts.
520      * @return {@code -1} if there is no composing text.
521      */
getComposingTextStart()522     public int getComposingTextStart() {
523         return mComposingTextStart;
524     }
525 
526     /**
527      * Returns the entire composing text.
528      * @return {@code null} if there is no composition.
529      */
getComposingText()530     public CharSequence getComposingText() {
531         return mComposingText;
532     }
533 
534     /**
535      * Returns the flag of the insertion marker.
536      * @return the flag of the insertion marker. {@code 0} if no flag is specified.
537      */
getInsertionMarkerFlags()538     public int getInsertionMarkerFlags() {
539         return mInsertionMarkerFlags;
540     }
541 
542     /**
543      * Returns the horizontal start of the insertion marker, in the local coordinates that will
544      * be transformed with {@link #getMatrix()} when rendered on the screen.
545      * @return x coordinate that is compatible with {@link Layout#getPrimaryHorizontal(int)}.
546      * Pay special care to RTL/LTR handling.
547      * {@code java.lang.Float.NaN} if not specified.
548      * @see Layout#getPrimaryHorizontal(int)
549      */
getInsertionMarkerHorizontal()550     public float getInsertionMarkerHorizontal() {
551         return mInsertionMarkerHorizontal;
552     }
553 
554     /**
555      * Returns the vertical top position of the insertion marker, in the local coordinates that
556      * will be transformed with {@link #getMatrix()} when rendered on the screen.
557      * @return y coordinate that is compatible with {@link Layout#getLineTop(int)}.
558      * {@code java.lang.Float.NaN} if not specified.
559      */
getInsertionMarkerTop()560     public float getInsertionMarkerTop() {
561         return mInsertionMarkerTop;
562     }
563 
564     /**
565      * Returns the vertical baseline position of the insertion marker, in the local coordinates
566      * that will be transformed with {@link #getMatrix()} when rendered on the screen.
567      * @return y coordinate that is compatible with {@link Layout#getLineBaseline(int)}.
568      * {@code java.lang.Float.NaN} if not specified.
569      */
getInsertionMarkerBaseline()570     public float getInsertionMarkerBaseline() {
571         return mInsertionMarkerBaseline;
572     }
573 
574     /**
575      * Returns the vertical bottom position of the insertion marker, in the local coordinates
576      * that will be transformed with {@link #getMatrix()} when rendered on the screen.
577      * @return y coordinate that is compatible with {@link Layout#getLineBottom(int)}.
578      * {@code java.lang.Float.NaN} if not specified.
579      */
getInsertionMarkerBottom()580     public float getInsertionMarkerBottom() {
581         return mInsertionMarkerBottom;
582     }
583 
584     /**
585      * Returns a new instance of {@link RectF} that indicates the location of the character
586      * specified with the index.
587      * @param index index of the character in a Java chars.
588      * @return the character bounds in local coordinates as a new instance of {@link RectF}.
589      */
getCharacterBounds(final int index)590     public RectF getCharacterBounds(final int index) {
591         if (mCharacterBoundsArray == null) {
592             return null;
593         }
594         return mCharacterBoundsArray.get(index);
595     }
596 
597     /**
598      * Returns the flags associated with the character bounds specified with the index.
599      * @param index index of the character in a Java chars.
600      * @return {@code 0} if no flag is specified.
601      */
getCharacterBoundsFlags(final int index)602     public int getCharacterBoundsFlags(final int index) {
603         if (mCharacterBoundsArray == null) {
604             return 0;
605         }
606         return mCharacterBoundsArray.getFlags(index, 0);
607     }
608 
609     /**
610      * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation
611      * matrix that is to be applied other positional data in this class.
612      * @return a new instance (copy) of the transformation matrix.
613      */
getMatrix()614     public Matrix getMatrix() {
615         final Matrix matrix = new Matrix();
616         matrix.setValues(mMatrixValues);
617         return matrix;
618     }
619 
620     /**
621      * Used to make this class parcelable.
622      */
623     public static final @android.annotation.NonNull Parcelable.Creator<CursorAnchorInfo> CREATOR
624             = new Parcelable.Creator<CursorAnchorInfo>() {
625         @Override
626         public CursorAnchorInfo createFromParcel(Parcel source) {
627             return new CursorAnchorInfo(source);
628         }
629 
630         @Override
631         public CursorAnchorInfo[] newArray(int size) {
632             return new CursorAnchorInfo[size];
633         }
634     };
635 
636     @Override
describeContents()637     public int describeContents() {
638         return 0;
639     }
640 }
641